算法笔记1:堆排序和桶排序

143 阅读3分钟

堆排序

目录

  1. 堆结构就是用数组实现的完全二叉树结构
  2. 定义:完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
  3. 定义:完全二叉树中如果每颗子树的最小值都在顶部就是小根堆
  4. 堆结构的heapInsert(插入)操作和heapify(取出)操作
  5. 堆结构的增大和减少
  6. 优先队列结构,就是堆结构

堆是完全二叉树,堆分为大根堆和小根堆,用完全二叉树模拟大小根堆,同一棵树,左右两边哪个数大都可以,不影响。

把数组模拟成完全二叉树,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
    }
}

堆排序特点

  1. 时间复杂度:N * logN
  2. 空间复杂度: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)
    }
}

题目

  1. 已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过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)

桶排序应用举例:

  1. 人的年龄的排序,因为年龄是有限的,假设最大年龄就是200岁,准备长度201的数组(词频表)即可排序。
  2. 字母的排序,准备长度为26的数组(词频表)