堆排序
二叉堆
二叉堆是完全二叉树或者近似完全二叉树。
1.大顶堆: 所有节点的子节点都比自身小的堆
2.小顶堆: 所有节点的子节点都比自身大的堆
一般用数组来表示堆,假设节点I是数组A中下标为i的节点
Parent(i)下标: (i-1)/2
Left(i)下标: 2*i + 1
Right(i)下标: 2*i + 2
含有N个元素的堆A的高度是: Floor(log<2>N)
堆排序
堆排序利用了大顶堆(或小顶堆)堆顶记录的关键字最大(或最小)这一特性,
使得在当前无序区中选取最大(或最小)关键字变得简单
在堆排序算法中,用得是大顶堆,小顶堆通常在构造优先级队列时使用
实际应用中,排序往往采用快排,而不是堆排,堆排主要用在优先级队列和top-K等问题上
其基本思想为(大顶堆):
1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
2)将堆顶元素R[1]与最后一个元素R[n]交换,R[n]为数组中的最大值,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),有序区将收容最大值,次大值,依次进行,且满足R[1,2...n-1]<=R[n];
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
基本过程
1.将无序数组转换为一个大顶堆
2.将大顶堆的顶部元素与数组的最后一个元素交换
3.将交换后的堆调整为一个大顶堆
4.将堆顶与数组倒数第二个元素交换
5.重复以上过程,直至堆中只有一个元素
从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从R[1...n]中选择最大记录,需比较n-1次,然后从R[1...n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。
javascript实现
function fHeapSort(arr) { //构建初始堆,将无序数组转化为堆 var len = arr.length; //数组元素的顺序对应堆的从上到下,从左到右,因此前Math.floor(len / 2)个节点为非叶子节点,即堆顶,只需对所有堆顶完成调整即可 //调整从后往前,即从最右下的堆顶开始调整,往左往上调整堆顶 for (var i = Math.floor(len / 2); i--;) { fAdjustHeap(arr, i, len); } for (var i = len;i--;) { //将堆顶与数组的第i个元素交换 var temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; //交换后,重新构造大顶堆,构造大顶堆的起点一直的堆顶,但是长度却一直减小,只对无序区进行排序 fAdjustHeap(arr, 0, i); } return arr; } function fAdjustHeap(arr, nParent, len) { //从父节点,左孩子,右孩子三个节点中选取最大者,与父节点交换, //对于子节点非孩子节点,与父节点交换后,其自身的大顶堆可能被破坏, //需要向下继续调整,直至到达堆边界,即叶子节点 var temp = arr[nParent]; //数组中的父节点的索引乘以2加1为其左孩子节点 //如果发生交换,则其孩子堆也需要调整一次,找到其孩子堆的左孩子,步长为2*nChild+1,循环进行调整 var nChild = 2 * nParent + 1; while (nChild < len) { //nChild + 1为右孩子节点,若右孩子大,则选择它 if (nChild + 1 < len && arr[nChild] < arr[nChild + 1]) { nChild++; } //若父节点大,则无需交换,其孩子堆也无需调整,跳出循环 if (temp >= arr[nChild]) { break; } //将大孩子赋值给父节点,由于上步是使用temp进行比较大小,相当于将temp与子节点进行交换,所以此处只需要将子赋值为父,不必将父与子交换 arr[nParent] = arr[nChild]; //parent跟着步长走,向下游走去,并逐级用大孩子覆盖父节点,直到父节点为大顶节点 nParent = nChild; nChild = 2 * nParent + 1 } //将最后的大顶节点与开始的调整节点交换 arr[nParent] = temp; }