堆排序(下):最大堆

二叉堆,简称堆 Heap

尖的完全二叉树。也有三叉堆以及普通堆,但大部分时候堆就是指二叉堆

  • 二叉堆的定义

一棵完全二叉树
父节点的值 >= 子节点的值,则称为最大二叉堆

父节点的值 <= 子节点的值,则称为最小二叉堆

注意:并没有要求左右节点的大小顺序

  • 举例

[35,26,48,10,59,64,17,23,45,31]

image.png

最大堆的性质

  • 堆序性 heap order

任意节点 >= 它的所有后代,最大值在堆的根上

  • 完全树

只有最底层不满,且节点尽可能的往左靠

最小堆的性质

  • 堆序性 heap order

任意节点 <= 它的所有后代,最小值在堆的根上

  • 完全树

只有最底层不满,且节点尽可能的往左靠

堆的 API

image.png

API - heapify 如何把完全二叉树变成堆

完全二叉树可以用数组存储

思路 (siftDown)

image.png

从最后一个节点开始,逐个向前,把每个节点与其后代比较,最大的放在上面

注意一个节点有可能需要调整多次(递归)

由于每次调整都是把数字下降,所以叫 siftDown

  • 代码
const array = [35, 26, 48, 10, 59, 64, 17, 23, 45, 31]
const heapify = array => {
    for (let i = parseInt((array.length - 1) / 2); i >= 0; i--) {
        siftDown(array, i, array.length)
    }
    return array
}
siftDown = (heap, i, length) => {
    const left = 2 * i + 1, right = 2 * i + 2
    let greater = left
    if (greater >= length) {return}
    if (right < length && heap[right] > heap[greater]) {
        greater = right
    }
    if(heap[greater]>heap[i]){
        console.log(`换 ${heap[greater]} ${heap[i]}`);
    [heap[greater],heap[i]] = [heap[i],heap[greater]]
    siftDown(heap, greater, length)
}
}
heapify(array)
// [64, 59, 48, 45, 31, 35, 17, 23, 10, 26]
image.png

问答

  • 为什么要从后往前

为了从易到难

  • 为什么从 59 开始

因为所以叶子节点都可以跳过

  • 什么时候递归

调整父子之后,子节点所在的子树要再调整一次

API - insert(heap, item) 如何向堆中插入一个值

要保证插入之后,依然得到一个堆

思路 (siftUp)

image.png
  • 代码
const heap = [64,59,48,45,31,35,17,23,10,26]
const insert = (heap, item) => {
    heap.push(item) //  把新值放到最后一个
    siftUp(heap, heap.length-1) //  开始上升
}
siftUp = (heap, i) => {
    if(i===0){return}
    const parent = parseInt((i-1)/2)
    if(heap[i]>heap[parent]){
        console.log(`换 ${heap[i]} ${heap[parent]}`);
        [heap[i],heap[parent]]=[heap[parent],heap[i]]
        siftUp(heap, parent)
    } }

insert(heap, 60)
console.log(heap) // [64, 60, 48, 45, 59, 35, 17, 23, 10, 26, 31]
image.png

API - extractMax(heap) 如何弹出堆顶的值

要保证弹出后,剩下的元素依然组成堆

思路 (extractMax)

image.png
  • 代码
const heap = [64, 60, 48, 45, 59, 35, 17, 23, 10, 26, 31]

const extractMax = (heap, start, end) => {
    [heap[start], heap[end - 1]] = [heap[end - 1], heap[start]]
    const max = heap[end - 1]
    siftDown(heap, start, end - 1)  // 将 start 沉下去
    return max
}

const siftDown = (heap, i, length) => {
    const left = 2 * i + 1,
        right = 2 * i + 2
    let greater = left
    if (greater >= length) return
    if (right < length && heap[right] > heap[greater]) {
        greater = right
    }
    if (heap[greater] > heap[i]) {
        console.log(`交换 ${heap[greater]} ${heap[i]}`);
        [heap[greater], heap[i]] = [heap[i], heap[greater]]
        siftDown(heap, greater, length)
    }
}

max = extractMax(heap, 0, heap.length)
heap.pop() // 删掉最后一个多余的最大值
console.log(max, heap)
// 64, [60, 59, 48, 45, 31, 35, 17, 23, 10, 26]
image.png

堆排序

  • 思路(结合前面的知识可以很简单的写出堆排序)


    image.png
  • 代码

array = [9,5,1,4,7,8,3,2,6]

const heapSort = arr => {
    // 第一步:数组变成堆 O(N*logN)
    const heap = heapify(arr)
    // 第二步:不停把最大的放到最后 O(N*logN)
    for(let i=0; i<heap.length-1; i++){
        // extractMax 自动把 max 放到最后
        extractMax(heap,0,heap.length-i)
    }
    return heap
}

const heapify = array => {
    for (let i = parseInt((array.length - 1) / 2); i >= 0; i--) {
        siftDown(array, i, array.length)
    }
    return array
}

const siftDown = (heap, i, length) => {
    const left = 2 * i + 1, right = 2 * i + 2
    let greater = left
    if (greater >= length) {return}
    if (right < length && heap[right] > heap[greater]) {
        greater = right
    }
    if (heap[greater] > heap[i]) {
        console.log(`换 ${heap[greater]} ${heap[i]}`);
        [heap[greater], heap[i]] = [heap[i], heap[greater]]
        siftDown(heap, greater, length)
    }
}
const extractMax = (heap, start, end) => {
    [heap[start], heap[end - 1]] = [heap[end - 1], heap[start]]
    const max = heap[end - 1]
    siftDown(heap, start, end - 1)  // 将 start 沉下去
    return max
}

heapSort(array)
console.log(array)
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
//   O(2*N*logN) O(N*logN)
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,444评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,867评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,157评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,312评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,673评论 3 289
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,802评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,470评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,696评论 2 250
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,187评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,538评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,188评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,127评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,889评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,741评论 2 274

推荐阅读更多精彩内容