看上去好像很简单的样子··然后折磨了我好久····
主要是没仔细弄明白。
堆分为最小堆和最大堆,以二叉树的形式存在,最小堆即根节点为整个树的最小值,最大堆则是根节点为最大值。
建堆(以最大堆为例):
首先数据以数组形式存储(int a[]或vector<int> a),若二叉树的根节点从0开始计数,则节点 i 的左右子节点的下标分别为2 * i + 1和 2 * i + 2(忘了怎么算可以画一棵树,就知道了)。
void heap_down(int a[], int id, int numsSize) { int leftchild = id * 2 + 1; int rightchild = leftchild + 1; if(leftchild <= numsSize-1) { if(rightchild <= numsSize - 1) if(a[leftchild] < a[rightchild]) leftchild = rightchild;//根节点与左右子节点中最大的交换 if(a[id] < a[leftchild]) { swap(a[id], a[leftchild]); heap_down(a, leftchild, numsSize);//递归 } } }
到这里把向下建堆写好了,这个函数是每次能够建一个最大堆,我们堆排序还需要把堆顶元素与堆末尾元素交换,然后size--,再从堆顶向下建堆,循环size次完成堆排序,时间复杂度O(nlogn)。
void heap_sort(int a[], int numsSize) { for(int i = numsSize/2;i>=0;i--)//为什么一定要从后往前heap_down heap_down(a,i,numsSize); int size = numsSize-1;//这里要保存一下数组的长度,因为必须要循环这么多次才能把所有的元素都排序一遍 for(int j=0;j<=size;j++) { swap(a[0],a[numsSize-1]); numsSize--; heap_down(a,0,numsSize); } }
接下来到了我之前一直犯错的地方:
为什么一定要从往前进行heap_down操作?
其实只要画个图就显而易见了,如果是从前往后循环的调用heap_down,首先第一次就会将堆顶元素和它的左右子节点中的较大者交换,然后················堆顶元素就再也变不了了,所有根本无法建立最大堆了!
为什么惩罚自己如此愚蠢,花时间写了这么多字的blog,所以一定要记住这里的坑。
另外由于heap_down操作自身会递归向下调用,所以在heap_sort中只要进行size/2次操作就可以完成最大堆的建立,虽然总体时间上并没有什么提升。