堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
1. 算法步骤
1、创建一个堆 H[0……n-1];
2、把堆首(最大值)和堆尾互换;
3、把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
4、重复步骤 2,直到堆的尺寸为 1。
2. 动图演示
3. 时间复杂度
堆排序的平均时间复杂度为 Ο(nlogn)。
4. 算法实现
堆排序C语言
#include <stdio.h> #include <stdlib.h> void swap(int* a, int* b) { int temp = *b; *b = *a; *a = temp; } void max_heapify(int arr[], int start, int end) { //建立父节点指标和子节点指标 int dad = start; int son = dad * 2 + 1; while (son <= end) //若子节点指标在范围内才做比较 { if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的 son++; if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数 return; else //否则交换父子内容再继续子节点和孙节点比较 { swap(&arr[dad], &arr[son]); dad = son; son = dad * 2 + 1; } } } void heap_sort(int arr[], int len) { int i; //初始化,i从最後一个父节点开始调整 for (i = len / 2 - 1; i >= 0; i--) max_heapify(arr, i, len - 1); //先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕 for (i = len - 1; i > 0; i--) { swap(&arr[0], &arr[i]); max_heapify(arr, 0, i - 1); } } int main() { int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 }; int len = (int) sizeof(arr) / sizeof(*arr); heap_sort(arr, len); int i; for (i = 0; i < len; i++) printf("%d ", arr[i]); printf("\n"); return 0; }
堆排序C++
#include <iostream> #include <algorithm> using namespace std; void max_heapify(int arr[], int start, int end) { //建立父节点指标和子节点指标 int dad = start; int son = dad * 2 + 1; while (son <= end) //若子节点指标在范围内才做比较 { if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的 son++; if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数 return; else //否则交换父子内容再继续子节点和孙节点比较 { swap(arr[dad], arr[son]); dad = son; son = dad * 2 + 1; } } } void heap_sort(int arr[], int len) { //初始化,i从最後一个父节点开始调整 for (int i = len / 2 - 1; i >= 0; i--) max_heapify(arr, i, len - 1); //先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完毕 for (int i = len - 1; i > 0; i--) { swap(arr[0], arr[i]); max_heapify(arr, 0, i - 1); } } void main() { int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 }; int len = (int) sizeof(arr) / sizeof(*arr); heap_sort(arr, len); for (int i = 0; i < len; i++) cout << arr[i] << ' '; cout << endl; system("pause"); }
堆排序PHP
function hsort(array &$arr, $len){ $idx = $len - 1; //创建堆操作,并使得堆有序 for($k=floor($len/2); $k>=0; $k--){ sink($arr, $k, $idx); } //排序操作 while($idx>0){ //获取最大值操作 list($arr[0], $arr[$idx]) = [$arr[$idx], $arr[0]]; $idx--; //堆调整下沉 sink($arr, 0, $idx); } } //堆调整下沉,小数字下沉 function sink(array &$arr, $low, $high){ //从$low开始找子节点 while($low*2+1<=$high){ //获取low的直接左子节点 $j = $low*2+1; //判断$low位置节点子节点中的较大子节点 if($j<$high && $arr[$j]<$arr[$j+1]){ $j++; } if($arr[$low]>$arr[$j]){ break; } //交换low节点和子节点的值 list($arr[$low], $arr[$j]) = [$arr[$j], $arr[$low]]; //继续排查下沉后子节点是否需要进行下沉操作 $low = $j; } } $a = [4,3,6,7,5,1,9,0,2]; hsort($a, count($a)); print_r($a);
堆排序JAVA
/** * 选择排序-堆排序 * @param array 待排序数组 * @return 已排序数组 */ public static int[] heapSort(int[] array) { //这里元素的索引是从0开始的,所以最后一个非叶子结点array.length/2 - 1 for (int i = array.length / 2 - 1; i >= 0; i--) { adjustHeap(array, i, array.length); //调整堆 } // 上述逻辑,建堆结束 // 下面,开始排序逻辑 for (int j = array.length - 1; j > 0; j--) { // 元素交换,作用是去掉大顶堆 // 把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置 swap(array, 0, j); // 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。 // 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因 // 而这里,实质上是自上而下,自左向右进行调整的 adjustHeap(array, 0, j); } return array; } /** * 整个堆排序最关键的地方 * @param array 待组堆 * @param i 起始结点 * @param length 堆的长度 */ public static void adjustHeap(int[] array, int i, int length) { // 先把当前元素取出来,因为当前元素可能要一直移动 int temp = array[i]; for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树 // 让k先指向子节点中最大的节点 if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子树,并且右子树大于左子树 k++; } //如果发现结点(左右子结点)大于根结点,则进行值的交换 if (array[k] > temp) { swap(array, i, k); // 如果子节点更换了,那么,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断 i = k; } else { //不用交换,直接终止循环 break; } } } /** * 交换元素 * @param arr * @param a 元素的下标 * @param b 元素的下标 */ public static void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; }
堆排序JavaScript
var len; // 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量 function buildMaxHeap(arr) { // 建立大顶堆 len = arr.length; for (var i = Math.floor(len/2); i >= 0; i--) { heapify(arr, i); } } function heapify(arr, i) { // 堆调整 var left = 2 * i + 1, right = 2 * i + 2, largest = i; if (left < len && arr[left] > arr[largest]) { largest = left; } if (right < len && arr[right] > arr[largest]) { largest = right; } if (largest != i) { swap(arr, i, largest); heapify(arr, largest); } } function swap(arr, i, j) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } function heapSort(arr) { buildMaxHeap(arr); for (var i = arr.length-1; i > 0; i--) { swap(arr, 0, i); len--; heapify(arr, 0); } return arr; }
堆排序C#语言
/// <summary> /// 堆排序 /// </summary> /// <param name="arr">待排序数组</param> static void HeapSort(int[] arr) { int vCount = arr.Length; int[] tempKey = new int[vCount + 1]; // 元素索引从1开始 for (int i = 0; i < vCount; i++) { tempKey[i + 1] = arr[i]; } // 初始数据建堆(从含最后一个结点的子树开始构建,依次向前,形成整个二叉堆) for (int i = vCount / 2; i >= 1; i--) { Restore(tempKey, i, vCount); } // 不断输出堆顶元素、重构堆,进行排序 for (int i = vCount; i > 1; i--) { int temp = tempKey[i]; tempKey[i] = tempKey[1]; tempKey[1] = temp; Restore(tempKey, 1, i - 1); } //排序结果 for (int i = 0; i < vCount; i++) { arr[i] = tempKey[i + 1]; } } /// <summary> /// 二叉堆的重构(针对于已构建好的二叉堆首尾互换之后的重构) /// </summary> /// <param name="arr"></param> /// <param name="rootNode">根结点j</param> /// <param name="nodeCount">结点数</param> static void Restore(int[] arr, int rootNode, int nodeCount) { while (rootNode <= nodeCount / 2) // 保证根结点有子树 { //找出左右儿子的最大值 int m = (2 * rootNode + 1 <= nodeCount && arr[2 * rootNode + 1] > arr[2 * rootNode]) ? 2 * rootNode + 1 : 2 * rootNode; if (arr[m] > arr[rootNode]) { int temp = arr[m]; arr[m] = arr[rootNode]; arr[rootNode] = temp; rootNode = m; } else { break; } } }
堆排序Python
def big_endian(arr,start,end): root=start child=root*2+1 #左孩子 while child<=end: #孩子比最后一个节点还大,也就意味着最后一个叶子节点了,就得跳出去一次循环,已经调整完毕 if child+1<=end and arr[child]<arr[child+1]: #为了始终让其跟子元素的较大值比较,如果右边大就左换右,左边大的话就默认 child+=1 if arr[root]<arr[child]: #父节点小于子节点直接交换位置,同时坐标也得换,这样下次循环可以准确判断:是否为最底层, #是不是调整完毕 arr[root],arr[child]=arr[child],arr[root] root=child child=root*2+1 else: break def heap_sort(arr): #无序区大根堆排序 first=len(arr)//2 - 1 for start in range(first,-1,-1): #从下到上,从左到右对每个节点进行调整,循环得到非叶子节点 big_endian(arr,start,len(arr)-1) #去调整所有的节点 for end in range(len(arr)-1,0,-1): arr[0],arr[end]=arr[end],arr[0] #顶部尾部互换位置 big_endian(arr,0,end-1) #重新调整子节点的顺序,从顶开始调整 return arr def main(): l=[3,1,4,9,6,7,5,8,2,10] print(heap_sort(l)) if __name__=="__main__": main()
堆排序Go语言
func heapSort(arr []int) []int { arrLen := len(arr) buildMaxHeap(arr, arrLen) for i := arrLen - 1; i >= 0; i-- { swap(arr, 0, i) arrLen -= 1 heapify(arr, 0, arrLen) } return arr } func buildMaxHeap(arr []int, arrLen int) { for i := arrLen / 2; i >= 0; i-- { heapify(arr, i, arrLen) } } func heapify(arr []int, i, arrLen int) { left := 2*i + 1 right := 2*i + 2 largest := i if left < arrLen && arr[left] > arr[largest] { largest = left } if right < arrLen && arr[right] > arr[largest] { largest = right } if largest != i { swap(arr, i, largest) heapify(arr, largest, arrLen) } } func swap(arr []int, i, j int) { arr[i], arr[j] = arr[j], arr[i] }