【堆】【最小堆】

// 堆实际上是一个完全二叉树
// 如果用0是堆的根节点,那么:
// 最子节点是20+1,右子节点是20+2
//
// 如果i是节点编号,那么其父节点是:
// i是奇数 (i-1)/2
// i是偶数(i-2)/2
//
// 因此,可以使用数组来实现堆!
//
// 大顶堆,就是 根节点最大,并且子树也是大顶堆
// 小顶堆,就是 根节点最小,并且子树也是小顶堆
//
// 所以,计算前k个最大数,使用小顶堆,因为堆中存放的是“当前”最大的k个数,后面的数,如果小于堆中最小数,就没有机会进入堆,如果大于最小数,则需要更新堆!
// 同理,计算前k个最小数,使用大顶堆。

// 从数据的存储结构看,最大堆/最小堆是一个数组
// 从数据的逻辑结构看,最大堆/最小堆是一个完全二叉树
//
// 最小堆的实现
// 插入:
// 插入元素时,插入到数组中的最后一个元素的后面,然后与该节点的父节点比较大小。
// 如果插入的元素小于父节点元素,那么与父节点交换位置。然后插入元素交换到父节点位置时,又与该节点的父节点比较,直到大于父节点元素或者到达堆顶。
// 该过程叫做上浮,即插入时上浮。
//
// 删除:
// 移除元素时,只能从堆顶移除元素,再取最后一个元素放到堆顶中。
// 然后堆顶节点与子节点比较时,先取子节点中的较小者,如果堆顶节点大于较小子节点,那么交换位置。此时堆顶节点元素交换到较小子节点上。然后再与其较小子节点比较,直到小于较小子节点或者到达叶子节点为止。
// 该过程叫做下沉,即移除元素时下沉。
//
/*
0
1 2
3 4 5
*/

代码实现

package main

import (
    "errors"
    "fmt"
)

// 堆实际上是一个完全二叉树
// 如果用0是堆的根节点,那么:
// 最子节点是2*0+1,右子节点是2*0+2
//
// 如果i是节点编号,那么其父节点是:
// i是奇数 (i-1)/2
// i是偶数(i-2)/2
//
// 因此,可以使用数组来实现堆!
//
// 大顶堆,就是 根节点最大,并且子树也是大顶堆
// 小顶堆,就是 根节点最小,并且子树也是小顶堆
//
// 所以,计算前k个最大数,使用小顶堆,因为堆中存放的是“当前”最大的k个数,后面的数,如果小于堆中最小数,就没有机会进入堆,如果大于最小数,则需要更新堆!
//       同理,计算前k个最小数,使用大顶堆。

// 从数据的存储结构看,最大堆/最小堆是一个数组
// 从数据的逻辑结构看,最大堆/最小堆是一个完全二叉树
//
// 最小堆的实现
// 插入:
// 插入元素时,插入到数组中的最后一个元素的后面,然后与该节点的父节点比较大小。
// 如果插入的元素小于父节点元素,那么与父节点交换位置。然后插入元素交换到父节点位置时,又与该节点的父节点比较,直到大于父节点元素或者到达堆顶。
// 该过程叫做上浮,即插入时上浮。
//
// 删除:
// 移除元素时,只能从堆顶移除元素,再取最后一个元素放到堆顶中。
// 然后堆顶节点与子节点比较时,先取子节点中的较小者,如果堆顶节点大于较小子节点,那么交换位置。此时堆顶节点元素交换到较小子节点上。然后再与其较小子节点比较,直到小于较小子节点或者到达叶子节点为止。
// 该过程叫做下沉,即移除元素时下沉。
//
/*
        0
     1      2
   3   4  5
*/
// 最小堆的实现
type MinHeap struct {
    size int
    nums []int
}

func NewMinHeap() *MinHeap {
    return &MinHeap{}
}

// 获得父节点下标
func GetParentIdx(i int) int {
    if i == 0 {
        return 0 // 根节点
    }
    return (i - 1) / 2
}

// 获得左子节点下标
func GetLeftChildIdx(i int) int {
    return 2*i + 1
}

// 获得右子节点下标
func GetRightChildIdx(i int) int {
    return 2*i + 2
}

func (h *MinHeap) isEmpty() bool {
    return h.size == 0
}

// 获得最小数,既堆顶元素
func (h *MinHeap) getMinValue() (int, error) {
    if h.isEmpty() {
        return 0, errors.New("getMinValue failed, minHeap is empty")
    }

    return h.nums[0], nil
}

// 插入时上浮
// 参数num,是插入的数字
func (h *MinHeap) SiftUp(num int) {
    fmt.Printf("SiftUp, nums is %v\n", num)
    h.nums = append(h.nums, num)
    childIndex := h.size // 开始的时候,插入到数组最后
    parentIndex := GetParentIdx(childIndex)

    // 最小堆,父节点要小于子节点
    for h.nums[parentIndex] > h.nums[childIndex] {
        h.nums[parentIndex], h.nums[childIndex] = h.nums[childIndex], h.nums[parentIndex]
        childIndex = parentIndex
        parentIndex = GetParentIdx(parentIndex) // 上浮
    }

    h.size++
}

// 然后堆顶节点与子节点比较时,先取子节点中的较小者,如果堆顶节点大于较小子节点,那么交换位置。
// 从rootIdx节点开始进行下沉操作,不删除节点
func (h *MinHeap) siftDown(rootIdx int) {
    //fmt.Printf("SiftDown, rootIdx is %v\n", rootIdx)
    var minChildIdx int
    for {
        leftChildIdx := GetLeftChildIdx(rootIdx)
        rightChildIdx := GetRightChildIdx(rootIdx)

        //fmt.Printf("rootIdx is %v, siftDown, leftChildIdx is %v, rightChildIdx is %v, minChildIdx is %v\n", rootIdx, leftChildIdx, rightChildIdx, minChildIdx)

        // go case不会穿透
        switch {
        case leftChildIdx > h.size-1: // 左子节点下标,已经超出堆的大小范围
            return
        case rightChildIdx > h.size - 1: // 左子节点下标,已经超出堆的大小范围(此时,左子节点仍然在范围内)
            if h.nums[rootIdx] > h.nums[leftChildIdx] {
                h.nums[rootIdx], h.nums[leftChildIdx] = h.nums[leftChildIdx], h.nums[rootIdx]  // 跟左子节点交换
            }
            return

            // 下面两个case寻找子节点中比较小的
        case h.nums[leftChildIdx] <= h.nums[rightChildIdx]:
            minChildIdx = leftChildIdx
        case h.nums[leftChildIdx] > h.nums[rightChildIdx]:
            minChildIdx = rightChildIdx
        }

        // 如果堆顶节点大于较小子节点,那么交换位置
        if h.nums[rootIdx] > h.nums[minChildIdx] {
            h.nums[rootIdx], h.nums[minChildIdx] = h.nums[minChildIdx], h.nums[rootIdx]    // 与较小者进行交换
            //fmt.Printf("rootIdx is %v, minChildIdx is %v\n", rootIdx, minChildIdx)
        }

        rootIdx = minChildIdx                                                          // 对较小的子节点进行相同的操作
    }
}

// 删除堆顶操作,将最后一个元素放到堆顶,进行下沉操作
func (h *MinHeap) SiftDown() (int, error) {
    minVal, err := h.getMinValue()
    if err != nil {
        return minVal, err
    }

    h.size --
    h.nums[0], h.nums[h.size] = h.nums[h.size], h.nums[0]

    h.siftDown(0)   // 从堆顶开始进行下沉操作
    return minVal, err
}

// 用指定的num,替换堆顶元素
// 然后对根节点进行下沉操作
func(h *MinHeap) Replace(num int) (int, error) {
    minVal, err := h.getMinValue()
    if err != nil {
        return minVal, err
    }

    h.nums[0] = num
    h.siftDown(0)
    return minVal, err
}

// 求前k个最大元素
func MaxKNums(nums []int, k int) (rsp []int) {
    // 先用前k个元素,构造堆
    heap := NewMinHeap()
    for i :=0; i<k; i++ {
        heap.SiftUp(nums[i])   // 建堆就是,将元素放入数组末尾,然后进行上浮操作
    }

    minVal, _ := heap.getMinValue()
    for _, v := range nums[k:] {
        // 如果比堆顶元素还小,你就肯定不够资格成为前k大元素
        if v > minVal {
            heap.Replace(v)                // 入堆
            minVal, _ = heap.getMinValue() // 重新获取堆顶值
        }
    }

    // 将堆中元素排好序,输出,每次删除堆顶元素(下沉操作)
    for i := 0; i<k; i++ {
        v, _ := heap.SiftDown()
        rsp = append(rsp,  v)
    }

    return
}

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