JS排序算法总结:(七)堆排序
目的:掌握 堆排序 的 基本思想与过程、代码实现、时间复杂度
1、基本思想与过程:
(1)将数组看做是一棵二叉树,并且是完全二叉树,将其变为最大二叉堆,即每一个父节点均大于其相连的子节点;
(2)将堆顶元素与末尾元素交换,并重新调整为最大堆,这时,堆中末尾元素为最大元素,即数组中的最大元素已经乖乖在最后一个位置了;
(3)重复 2 过程,直至所有元素都变得有序。
2、代码实现:
// 堆排序的主程序 function heapSort(arr){ // 1、构建最大堆 buildMaxHeap(arr) // 2、从堆尾出发,依次 交换堆尾与堆头的值;并将 除堆尾外的 剩余堆 重新调整为最大堆 for(var i = arr.length-1 ; i>=0 ; i--){ swap(arr,0,i) maxHeapify(arr,0,i) //此时 i 作为 heapSize 传入 maxHeapify,即 忽略了最后一个已经排好的元素 } return arr } function swap(a,i,j){ var temp = a[i] a[i] = a[j] a[j] = temp } function buildMaxHeap(arr){ // 从 末尾节点的父节点 开始调整,直至根节点 var iParent = Math.floor(arr.length/2)-1 for(var i = iParent ; i>=0 ; i--){ maxHeapify(arr,i,arr.length) } } function maxHeapify(arr,index,heapSize){ // heapSize 会一直变小,直至为0 var iMax = index var iLeft = 2*index+1 var iRight = 2*(index+1) if(iLeft < heapSize && arr[iLeft] > arr[iMax]){ iMax = iLeft } if(iRight < heapSize && arr[iRight] > arr[iMax]){ // 注意 这里 是 iMax 而不是 index!! iMax = iRight } if(iMax != index){ swap(arr,iMax,index) maxHeapify(arr,iMax,heapSize) // 注意 这里是iMax 而不是 index ! 因为新的 arr[index] 作为 父节点 已经在此轮完成堆调整了 } }
上面的代码实现里,用到了递归调用 maxHeapify() 函数,通常来说,递归主要用在分治法中,而这里并不需要分治。递归调用需要压栈/清栈,和迭代相比,性能上有略微的劣势。
接下来我们跑个题,聊一下,递归和迭代,递归和迭代都是循环中的一种,他们的区别是:
(1)递归是重复调用函数自身实现循环,在使用递归时,必须有一个明确的递归结束条件,称为递归出口。一般分为以下两个阶段:
a)递推:把复杂的问题的求解推到比原问题简单一些的问题的求解;
b)回归:当获得最简单的情况后,逐步返回,依次得到复杂的解。
(2)迭代是函数内某段代码实现循环,而迭代与普通循环的区别是:循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。
(3)空间利用率:
递归是将一个问题分解为若干相对小一点的问题,遇到递归出口再原路返回,因此必须保存相关的中间值,这些中间值压入栈保存,问题规模较大时会占用大量内存。
迭代是逐渐逼近,用新值覆盖旧值,直到满足条件后结束,不保存中间值,空间利用率高。
(4)应用:
递归可以解决很多问题:如背包问题,汉诺塔问题,斐波那契数列等;
迭代经典例子就是实数的累加,比如计算1-100所有实数的和。
一般情况下,递归中一定有迭代,但是迭代中不一定有递归,大部分可以相互转换。因此,能用迭代的不用递归,递归调用函数,计算有重复,浪费空间,并且递归太深容易造成堆栈的溢出。
所以呢,还是迭代好,那我们将递归改成迭代吧~
function maxHeapify(arr,index,heapSize){ while(true){ var iMax = index var iLeft = 2*index+1 var iRight = 2*(index+1) if(iLeft < heapSize && arr[iLeft] > arr[iMax]){ iMax = iLeft } if(iRight < heapSize && arr[iRight] > arr[iMax]){ iMax = iRight } if(iMax != index){ swap(arr,iMax,index) index = iMax }else{ break; } } }
3、时间复杂度:非常稳定,为O(n㏒n)复杂度,最好情况与最坏情况一样。