堆排序
目录
- 堆结构就是用数组实现的完全二叉树结构
- 定义:完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
- 定义:完全二叉树中如果每颗子树的最小值都在顶部就是小根堆
- 堆结构的heapInsert(插入)操作和heapify(取出)操作
- 堆结构的增大和减少
- 优先队列结构,就是堆结构
堆是完全二叉树,堆分为大根堆和小根堆,用完全二叉树模拟大小根堆,同一棵树,左右两边哪个数大都可以,不影响。
把数组模拟成完全二叉树,i位置的左孩子是:2*i+1,右孩子是:2*i+2,父是:(i-1)/2。
堆的增,删,改,都是依赖heapInsert和heapify这两个函数。
大根堆插入
// 前置交换函数
function swap(arr, index, fatherIndex) {
const temp = arr[index]
arr[index] = arr[fatherIndex]
arr[fatherIndex] = temp
}
// 某个数现在处于index位置,往上继续移动
function heapInsert(arr, index) {
// 如果当前位置树大于父位置的数
while (arr[index] > arr[(index - 1) / 2] && index > 0) {
// index位置的数和父亲的位置的数,交换
swap(arr, index, (index - 1) / 2)
// index 变成父亲位置的index
index = (index - 1) / 2
}
}
大根堆取出操作
/**
1. 取出index中的数
2. 把最末尾的数移动到index位置
3. 把这个index位置的数往下沉,直到下沉不动为止
*/
// 某个数在index位置,试着往下移动
function heapify(arr, index, heapSize) {
// 左孩子下标
let left = (index * 2) + 1
while (left < heapSize) { // 下方还有孩子的时候
// 两个孩子中较大的一个孩子的index,给largest
let largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left
// 父亲和孩子之间,谁的值大,谁的下标index给largest
largest = arr[largest] > arr[index] ? largest : index
if (largest === index) {
break
}
swap(arr, largest, index)
index = largest
left = index * 2 + 1
}
}
堆排序特点
- 时间复杂度:N * logN
- 空间复杂度:O1
大根堆排序过程
/**
1. 给我们一个数组arr
2. 把数组整理成大根堆
3. 取出顶部最大的数,拿最末尾一个数替补
4. 剩余的数,重新整理成大根堆
*/
function heapSort(arr, index) {
if (arr == null || arr.length < 2) {
return
}
for (let i = 0; i < arr.length; i++) { // O(N)
heapInsert(arr, i) // 整理成大/小根堆 O(logN)
}
let heapSize = arr.length
swap(arr, 0, --heapSize) // 交换,此时:数组的最后一个数已经有序了
while (heapSize > 0) {
heapify(arr, 0, heapSize)
swap(arr, 0, --heapSize)
}
}
题目
- 已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组长度来说比较小,请选择一个合适的算法进行排序。
解:
/**
思路:
1. 假设k为6,那么准备一个长度为7的小根堆,
2. 求出小根堆的最大值,然后弹出
3. 然后加一个数,再继续求出小根堆的最大值
4. 如此反复,直到最后
*/
// 小根堆
function Heap() {
}
function SortArrayDistanceLessK() {
const heap = new Heap()
let index = 0
for (; index <= Math.min(arr.length, k); i++) {
heap.add(arr[index]) // 这个add,默认排好序,是堆已经实现的功能
}
let i = 0
for (; i < arr.length; i++) {
heap.add(arr[index])
arr[i] = heap.poll() // 弹出顶部一个,并且自动把剩余的数仍然整理成堆
}
while (!heap.isEmpty()) {
arr[i++] = heap.poll()
}
}
比较器
所有比较器统一的潜台词:返回负数的时候,第一个参数排前面;返回正数的时候,第二个参数排前面;返回0的时候,无所谓。
桶排序
桶排序复杂度:O(N)
桶排序应用举例:
- 人的年龄的排序,因为年龄是有限的,假设最大年龄就是200岁,准备长度201的数组(词频表)即可排序。
- 字母的排序,准备长度为26的数组(词频表)