堆排其实就是选择排序,只不过用了完全二叉树特性。
堆排思想 : 利用完全二叉树特性建堆和重复选择调整来得到有序数组。
完全二叉树有什么特性呢? 节点左对齐 ---> 层序遍历不会出现空,可以用数组表达(访问效率高)
那么可以将它映射到数组上,并且遵循一个规律: 设i为当前节点索引,
i->left = 2*i+1 i->right = 2*i+2 可得-->
i = (i->left-1)/2 = (i->right-2)/2 = (i->left-2 +1)/2 = (i->left-2)/2 + 1/2(计算机整除为0) = (i->child-1)/2
堆排序种类:
大根堆 : parent > left && right ---> 升序
小根堆 : parent < left && right ---> 降序
堆排序最经典的问题就是topK问题。
堆排过程(假如要降序) :
1. 建小堆
建小堆过程: 1.找到最后一个非叶子节点(叶子节点不用调整,因为向下调整前提是有孩子),进行向下调整
2.依次向前将所有非叶子节点调整完,小堆也就构建好了
1.选最后一个非叶子节点 3,向下调整
2.继续找倒数第二个非叶子节点向下调整,重复直到全部非叶子节点调整完成
此时,小堆建立完成,堆顶为数组中最小值。
2. 向下调整 `
向下调整过程: 1.小堆顶部是最小值,将其与最后一个节点交换,最小值固定
2.将根节点与最小的孩子节点比较,若是不是最小值则交换,继续调整递归比较,找到合适的位置,再次形成小堆
3.循环1,2步骤直到所有值固定
1.交换1和3,固定1
2.向下调整形成小堆
3.重复1,2步骤直到固定所有值
C/C++代码demo:
1 #include<algorithm> 2 //向下调整 3 //和孩子比较,小了退出,大了交换 4 void AdjustDown(int* arr,int n,int root) 5 { 6 int parent = root; 7 int child = root*2 + 1; 8 //交换边界: 交换到最后的位置了 9 while(child<n) 10 { 11 //找最小的孩子 12 if(child+1<n && arr[child]>arr[child+1]) 13 { 14 ++child; 15 } 16 //大了交换,并更新父子节点 17 if(arr[parent]>arr[child]) 18 { 19 swap(arr[parent], arr[child]); 20 parent = child; 21 child = 2*parent+1; 22 } 23 //小了退出 24 else 25 { 26 break; 27 } 28 } 29 } 30 31 //堆排序 --- 降序,造小堆 32 // 下标获取方法: 33 // i->left = i*2 + 1 i->right = i*2 + 2 34 // i = (i->child-1) * 2 35 void HeapSort(int* arr,int n) 36 { 37 //1.建堆 --- 从最后一个非叶子节点依次向下调整 38 //最后一个非叶子节点确定方法: n-1的父亲,因为左对齐,所以此位置前面都有孩子 39 for(int i=(n-2)/2;i>=0;--i) 40 AdjustDown(arr,n,i); 41 42 //2.向下调整 --- 重复交换,固定,向下调整直到全部固定 43 for(int end=n-1;i>0;--i){ 44 swap(arr[0],arr[end]) 45 AdjustDown(arr,n-1,i); 46 } 47 }