群组
Flutter,iOS,Android,Python等,闲聊工作&技术&问题;
个人主页:https://waitwalker.cn
telegram群链接:https://t.me/joinchat/Ej0o0A1ntlq5ZFIMzzO5Pw

堆是二叉树的一种,分为最小堆:其根节点恒小于子节点;最大堆:其根节点恒大于子节点.堆总是一颗完全二叉树,除了最底层,其它层都被填满,且最底层尽可能从左往右填入.常见的堆有二叉堆,斐波那契堆等.
堆又名优先队列,但并不是队列.之前我们了解队列是满足FIFO的数据结构,而堆是按照元素的优先级来处理元素.通常我们最小堆或者最大堆都是我们给堆设定的优先级.
堆的存储结构一般用数组来实现,本文主要实现的最大堆的结构.

构造

// MARK: - 堆
class Heap<T:Comparable> {

    // 存储结构
    private(set) var elements:[T] = []

   // MARK: - 构造
    init() {

    }
}

节点索引

因为堆是一颗完全二叉树,其在数组中的存储结构也满足二叉树一些性质,比如一个索引为i的节点其左子树索引为2i + 1,其右子树索引为2i + 2;其父节点的索引为(i - 1) / 2.

/// 左子树索引
    ///
    /// - Parameter index: 当前节点索引
    /// - Returns: 左子树的索引
    func leftChildIndex(index:Int) -> Int {
        return 2 * index + 1
    }


    /// 右子树索引
    ///
    /// - Parameter index: 当前节点索引
    /// - Returns: 右子树的索引
    func rightChildIndex(index:Int) -> Int {
        return 2 * index + 2
    }


    /// 父节点索引
    ///
    /// - Parameter index: 当前节点索引
    /// - Returns: 父节点的索引
    func parentIndex(index:Int) -> Int {
        guard index > 0 else {
            return 0
        }
        return (index - 1) / 2
    }

插入

插入逻辑:
1)将新元素插入到数组的末尾
2)将新元素节点与其父节点进行比较,如果优先级大于父节点(也就是其值大于父节点),则新节点与父节点进行位置更换,继续新节点与父节点进行比较
3)递归

/***
        1)将新元素插入到数组的末尾
        2)将新元素节点与其父节点进行比较,如果优先级大于父节点(也就是其值大于父节点),则新节点与父节点进行位置更换,继续新节点与父节点进行比较
        3)递归
     */
    /// 插入操作
    ///
    /// - Parameter element: 待插入的元素
    func insert(element:T) -> Void {
        elements.append(element)
        guard elements.count > 1 else {
            return
        }
        shiftUp(index: elements.count - 1)
    }


    /// 上滤
    ///
    /// - Parameter index: 当前索引
    func shiftUp(index:Int) -> Void {
        let parentIndex = self.parentIndex(index: index)
        guard isHigherPriority(firstIndex: index, secondIndex: parentIndex) else {
            return
        }
        swapElement(with: index, and: parentIndex)

        // 向上继续比较
        shiftUp(index: parentIndex)
    }


    /// 最大堆判断优先级
    ///
    /// - Parameters:
    ///   - firstIndex: 新元素索引
    ///   - secondIndex: 父节点索引
    /// - Returns: 新节点是否具有优先级
    private func isHigherPriority(firstIndex:Int, secondIndex:Int) -> Bool {
        guard firstIndex < elements.count && secondIndex < elements.count  else {
            return false
        }
        return elements[firstIndex] > elements[secondIndex]
    }


    /// 交换两个元素
    ///
    /// - Parameters:
    ///   - firstIndex: 第一个元素索引
    ///   - secondIndex: 第二个元素索引
    private func swapElement(with firstIndex:Int, and secondIndex:Int) -> Void {
        guard firstIndex < elements.count && secondIndex < elements.count else {
            return
        }
        elements.swapAt(firstIndex, secondIndex)
    }

删除

删除逻辑:
1)删除操作通常将最大堆元素移除,及根节点移除,首先将根节点与最后的元素互换位置
2)删除最后的元素(即移到最后的根元素)
3)将移动到root位置的新根节点与其子节点进行比较,如果其小于子节点,则与子节点互换位置
4)递归

/// 删除操作
    func delete() -> Void {
        guard !isEmpty else {
            return
        }

        swapElement(with: 0, and: count - 1)

        elements.removeLast()

        guard !isEmpty else {
            return
        }

        shiftDown(elememtIndex: 0)
    }


    /// 下滤
    ///
    /// - Parameter elememtIndex: 元素索引
    func shiftDown(elememtIndex:Int) -> Void {
        // 获取当前节点与其子节点的高优先级的节点
        var tmpIndex = elememtIndex
        let leftIndex = leftChildIndex(index: elememtIndex)
        let rightIndex = rightChildIndex(index: elememtIndex)

        if leftIndex < count && isHigherPriority(firstIndex: leftIndex, secondIndex: rightIndex) {
            tmpIndex = leftIndex
        }

        if rightIndex < count && isHigherPriority(firstIndex: rightIndex, secondIndex: leftIndex) {
            tmpIndex = rightIndex
        }

        if elememtIndex == tmpIndex {
            return
        }

        swapElement(with: elememtIndex, and: tmpIndex)
        shiftDown(elememtIndex: tmpIndex)
    }

源码地址

        这里的实现代码已放到GitHub,测试用例详见DataStructureAlgorithmTests.swift文件

参考文献

https://zh.wikipedia.org/wiki/%E5%A0%86%E7%A9%8D
《数据结构与算法分析C语言描述》(Data Structures and Algorithm Analysis in C:Second Edition)–译者: 冯舜玺


  转载请注明: waitwalker

 上一篇
读《人生》 读《人生》
        读完路遥的人生之后,心情有点沉重。人生如戏,戏如人生。当我们旅途不顺时应该怎样调节;当我们面对这一系列的艰难抉择时,我们该怎么办;在一些诱惑面前
2019-05-02
下一篇 
AVL树 AVL树
群组Flutter,iOS,Android,Python等,闲聊工作&技术&问题;个人主页:https://waitwalker.cntelegram群链接:https://t.me/joinchat/Ej0o0A1ntlq
  目录