美文网首页
【堆】【最小堆】

【堆】【最小堆】

作者: 月下蓑衣江湖夜雨 | 来源:发表于2020-08-19 19:18 被阅读0次

    // 堆实际上是一个完全二叉树
    // 如果用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
    }
    
    

    相关文章

      网友评论

          本文标题:【堆】【最小堆】

          本文链接:https://www.haomeiwen.com/subject/kkpdjktx.html