一、堆
堆排序是利用堆这种数据结构来设计的一种排序算法,是一种选择排序(每轮排序会选出一个最大/最小值),最坏、最好、平均时间复杂度均为O(nlogn),是不稳定的排序。
使用堆排序,可以是一个动态的过程,若题目中只需某个排序位的值,则只需排序置该位置,不用全排序好再去拿,只需要针对部分元素进行排序,这样可以降低复杂度。
堆排序过程中,是看不到树的结构的,因为使用完全二叉树的性质,用数组表示对应的树结构,又叫顺序存储。
顺序存储二叉树的特点:
- 第n个元素父节点为(n-1)/2
- 第n个元素左子节点为2*n+1
- 第n个元素右子节点为2*n+2
- 最后一个非叶子结点为Math.floor(arr.length/2)-1
堆 是具有以下性质的完全二叉树:
- 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列
- 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排
堆排序的基本思想:
1、将待排序序列构造成一个大顶堆(数组)
2、此时整个序列的最大值就是堆顶的根节点
3、将其与末尾元素进行交换,此时尾元素就是最大值
4、再将剩余n-1个元素重新构建成一个堆,这样可以得到n个元素的次最大值。如此反复,可以得到一个有序序列。
二、如何构建堆并排序
下面以数组[3,5,8,6,10]进行堆排序:
步骤一:构造初始堆
1、(操作的为数组)
将给定无序序列构造成一个大顶堆
2、此时从最后一个非叶子结点开始调整,从左往右,从上往下进行调整
叶节点不做调整,第一个非叶子结点:arr.length/2-1 =5/2-1 = 1,元素5的结点
比较非叶子结点的左右结点,先让6,10比较,得到大的,再和当前结点5作比较,发现10大于5,则交换位置。
3、找到第二个非叶子结点3,[3,10,8]中,元素10最大,则交换3,10
4、交换使得子根[3,6,5]结构排乱,所以再对其进行调整,[3,6,5]中6最大,将3,6位置进行调换
于是便将一个无序序列构建成为一个大顶堆
步骤二:将堆顶元素与末尾元素进行交换
将堆顶元素与末尾元素进行交换,交换后使末尾元素变为最大。再继续上述步骤,得到第二大元素,如此反复交换便可完成排序堆的构造。
1、将堆顶元素10与末尾元素交换
2、重新调整结构,使其满足堆定义
3、再将堆顶元素8与末尾元素3进行交换,得到第二大元素8
4、如此反复,最终使得整个序列有序
三、总结:
1、将无序序列构建成一个堆,根据升序降序决定是大顶堆还是小顶堆
2、将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端
3、重新调整结构,使其满足堆定义,然后继续交换堆顶与当前末尾元素,如此反复,直至整个序列有序
四、代码
var Heap = function(nums) {
let heapSize=nums.length
buildMaxHeap(nums,heapSize) // 构建好了一个大顶堆
// 进行下沉 大顶堆是最大元素下沉到末尾
for(let i=nums.length-1;i>=0;i--){
swap(nums,0,i)
--heapSize // 下沉后的元素不参与到大顶堆的调整
// 重新调整大顶堆
maxHeapify(nums, 0, heapSize);
}
console.log(nums)
// 自下而上构建一颗大顶堆
function buildMaxHeap(nums,heapSize){
for(let i=Math.floor(heapSize/2)-1;i>=0;i--){
maxHeapify(nums,i,heapSize)
}
}
// 从左向右,自上而下的调整节点
function maxHeapify(nums,i,heapSize){
let l=i*2+1
let r=i*2+2
let largest=i
if(l < heapSize && nums[l] > nums[largest]){
largest=l
}
if(r < heapSize && nums[r] > nums[largest]){
largest=r
}
if(largest!==i){
swap(nums,i,largest) // 进行节点调整
// 继续调整下面的非叶子节点
maxHeapify(nums,largest,heapSize)
}
}
function swap(a, i, j){
let temp = a[i];
a[i] = a[j];
a[j] = temp;
}
};