对于结点 i ,其子结点为 2i+1 与 2i+2 。算法
如今须要对如上二叉树作升序排序,总共分为三步:api
下面详细图解这个过程:数组
初始化大顶堆,首先选取最后一个非叶子结点(咱们只须要调整父节点和孩子节点之间的大小关系,叶子结点之间的大小关系无需调整)。设数组为arr,则第一个非叶子结点的下标为:i = Math.floor(arr.length/2 - 1) = 1,也就是数字4,如图中虚线框,找到三个数字的最大值,与父节点交换。函数
而后,下标 i 依次减1(即从第一个非叶子结点开始,从右至左,从下至上遍历全部非叶子节点)。后面的每一次调整都是如此:找到父子结点中的最大值,作交换。spa
这一步中数字六、1交换后,数字[1,5,4]组成的堆顺序不对,须要执行一步调整。所以须要注意,每一次对一个非叶子结点作调整后,都要观察是否会影响子堆顺序!操作系统
此次调整后,根节点为最大值,造成了一个大顶堆,将根节点与最后一个结点交换。3d
除开当前最后一个结点6(即最大值),将其他结点[4,5,3,1]组成新堆转化为大顶堆(注意观察,此时根节点之外的其余结点,都知足大顶堆的特征,因此能够从根节点4开始调整,即找到4应该处于的位置便可)。code
接下来反复执行步骤2,直到堆中元素个数为1:blog
堆中元素个数为1, 排序完成。排序
// 交换两个节点 function swap(A, i, j) { let temp = A[i]; A[i] = A[j]; A[j] = temp; } // 将 i 结点如下的堆整理为大顶堆,注意这一步实现的基础其实是: // 假设 结点 i 如下的子堆已是一个大顶堆,shiftDown函数实现的 // 功能是其实是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面 // 将写一个 for 循环,从第一个非叶子结点开始,对每个非叶子结点 // 都执行 shiftDown操做,因此就知足告终点 i 如下的子堆已是一大 //顶堆 function shiftDown(A, i, length) { let temp = A[i]; // 当前父节点 // j<length 的目的是对结点 i 如下的结点所有作顺序调整 for(let j = 2*i+1; j<length; j = 2*j+1) { temp = A[i]; // 将 A[i] 取出,整个过程至关于找到 A[i] 应处于的位置 if(j+1 < length && A[j] < A[j+1]) { j++; // 找到两个孩子中较大的一个,再与父节点比较 } if(temp < A[j]) { swap(A, i, j) // 若是父节点小于子节点:交换;不然跳出 i = j; // 交换后,temp 的下标变为 j } else { break; } } } // 堆排序 function heapSort(A) { // 初始化大顶堆,从第一个非叶子结点开始 for(let i = Math.floor(A.length/2-1); i>=0; i--) { shiftDown(A, i, A.length); } // 排序,每一次for循环找出一个当前最大值,数组长度减一 for(let i = Math.floor(A.length-1); i>0; i--) { swap(A, 0, i); // 根节点与最后一个节点交换 shiftDown(A, 0, i); // 从根节点开始调整,而且最后一个结点已经为当 // 前最大值,不须要再参与比较,因此第三个参数 // 为 i,即比较到最后一个结点前一个便可 } } let Arr = [4, 6, 8, 5, 9, 1, 2, 5, 3, 2]; heapSort(Arr); alert(Arr);
程序注释: 将 i 结点如下的堆整理为大顶堆,注意这一步实现的基础其实是:假设 结点 i 如下的子堆已是一个大顶堆,shiftDown函数实现的功能是其实是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面作第一次堆化时,heapSort 中写了一个 for 循环,从第一个非叶子结点开始,对每个非叶子结点都执行 shiftDown操做,因此就知足了每一次 shiftDown中,结点 i 如下的子堆已是一大顶堆。
复杂度分析:adjustHeap 函数中至关于堆的每一层只遍历一个结点,由于
具备n个结点的彻底二叉树的深度为[log2n]+1,因此 shiftDown的复杂度为 O(logn),而外层循环共有 f(n) 次,因此最终的复杂度为 O(nlogn)。
堆主要是用来实现优先队列,下面是优先队列的应用示例:
而实现优先队列采用普通数组、顺序数组和堆的不一样复杂度以下:
使用堆来实现优先队列,可使入队和出队的复杂度都很低。