在 RxSwift 框架中,在 PriorityQueue.swift 文件中,使用数组实现了一个优先级队列 PriorityQueue

优先级队列(priority queue)是0个或者多个元素的集合,每个元素有一个优先级,可以在集合中查找或删除优先级最高的元素。对优先级队列执行的操作有:

  1. 查找。一般情况下,查找操作用来搜索优先权最大的元素。
  2. 插入一个新元素。
  3. 删除。一般情况下,删除操作用来删除优先权最大的元素。 对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。

RxSwift 是通过数组实现优先级队列的。在有元素入队列和出队列的之后必须对数组中的元素进行排序,才能在获取队列中优先级最高的元素时非常快速。RxSwift 使用的是最大堆最小堆)的排序算法,对数组中的元素进行排序的。

堆树

堆树(最大堆或者最小堆)的定义如下:

  1. 堆树是一棵完全二叉树;
  2. 堆树中某个节点的值总是不大于(或不小于)其子节点的值;
  3. 堆树中每个节点的子树都是堆树;

当父节点的值总是大于或等于任何一个子节点的值时为最大堆,当父节点的值总是小于或等于任何一个子节点的值时为最小堆

构造最大树

怎样将一个未排序的数组构造成堆树呢?现在以最大堆为例进行讲解(最小堆同理)。
加入我们拿到的未排序的数组为:

var numbers = [6, 2, 5, 4, 20, 13, 14, 15, 9, 7]
复制代码

数组对应的完全二叉树如下图所示:

如果堆树中的每个非子节点的值都大于它的两个(或一个)相近的子节点的值,那么整个堆树就满足了每个节点的值总是不小于其子节点的值。所以构造堆树的基本思路是:首先找到最后一个节点的父节点,从这个父节点开始调整树,保证父节点的值大于子节点的值。然后从这个父节点继续向前调整树,直到所有的非子节点的值都不小于于它的相邻的子节点的值,这样最大堆就构造完毕了

假设树中有n个节点,从0开始给节点编号,到n-1结束,对于编号为i的节点,其父节点为(i-1)/2;左子节点的编号为i2+1,右子节点的编号为i2+2。最后一个节点的编号为n-1,其父节点的编号为(n-2)/2,所有从编号为(n-2)/2的节点开始调整树。

如下图所示,最后一个节点为7,其父节点为20,从20这个节点开始构造最大堆;构造完毕之后,转移到下一个父节点,直到所有父节点都构造完毕。

思路已经梳理完成,下面我们看 RxSwift 具体是怎样实现的。

PriorityQueue的初始化方法中传入对比两个元素优先级和判断元素是否相等的方法。_elements用于保存队列中的元素。

struct PriorityQueue<Element> {private let _hasHigherPriority: (Element, Element) -> Boolprivate let _isEqual: (Element, Element) -> Boolfileprivate var _elements = [Element]()init(hasHigherPriority: @escaping (Element, Element) -> Bool, isEqual: @escaping (Element, Element) -> Bool) {_hasHigherPriority = hasHigherPriority_isEqual = isEqual}
}
复制代码

获取优先级最高的元素,即返回 _elements 最前的元素:

func peek() -> Element? {return _elements.first
}
复制代码

有元素进入队列时,首先将元素添加到数组_elements的末尾,相当于在堆树的末尾又添加了一个节点,这时需要根据这个节点的优先级和其父节点的优先级调整数组。因为在添加元素之前的树结构已经满足最大堆的要求,所以现在只需要关注最后一个节点和它的父节点的优先级,如果最后一个节点的优先级较高,则将最后一个节点和它的父节点进行调整。以此类推,一直向上调整到树的顶端。

mutating func enqueue(_ element: Element) {_elements.append(element)bubbleToHigherPriority(_elements.count - 1)
}// 从下标为 initialUnbalancedIndex 处向高的优先级处调整元素
private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) {// 确保 initialUnbalancedIndex 在 _elements 的索引范围内precondition(initialUnbalancedIndex >= 0)precondition(initialUnbalancedIndex < _elements.count)var unbalancedIndex = initialUnbalancedIndexwhile unbalancedIndex > 0 {// unbalancedIndex 为未排序的索引// parentIndex 为未排序节点的父节点的索引let parentIndex = (unbalancedIndex - 1) / 2guard _hasHigherPriority(_elements[unbalancedIndex], _elements[parentIndex]) else { break }#if swift(>=3.2)_elements.swapAt(unbalancedIndex, parentIndex)#elseswap(&_elements[unbalancedIndex], &_elements[parentIndex])#endifunbalancedIndex = parentIndex}
}
复制代码

将优先级最高的元素出队列,也就是将数组中的第一个元素移除,同时我们也有可能需要移除数组中的任意一个的元素。进而我们可以将问题归结为:怎样移除指定索引处的元素。

当要移除指定索引的元素时,首先将指定索引处的元素和数组的最后一个元素交换位置,再将最后一个元素移除,这样原本在数组最后的元素移到了指定的索引处,这样有可能破坏了的最大堆的规则,所以要将指定位置的元素根据其优先级向下浮动,让它的子节点中值最大的一个和其交换位置,确保指定索引的优先级大于它所有子节点的优先级,然后将指定索引处的元素根据优先级向上浮动,确保指定索引处的元素优先级小于或等于其父节点的优先级。代码如下:

// 优先级最高的元素出队列
mutating func dequeue() -> Element? {guard let front = peek() else {return nil}removeAt(0)return front
}// 移除任意一个元素
mutating func remove(_ element: Element) {for i in 0 ..< _elements.count {if _isEqual(_elements[i], element) {removeAt(i)return}}
}// 移除指定索引的元素
private mutating func removeAt(_ index: Int) {let removingLast = index == _elements.count - 1if !removingLast {#if swift(>=3.2)_elements.swapAt(index, _elements.count - 1)#elseswap(&_elements[index], &_elements[_elements.count - 1])#endif}_ = _elements.popLast()if !removingLast {bubbleToHigherPriority(index)bubbleToLowerPriority(index)}
}// 向低优先级冒泡
private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) {precondition(initialUnbalancedIndex >= 0)precondition(initialUnbalancedIndex < _elements.count)var unbalancedIndex = initialUnbalancedIndexwhile true {//let leftChildIndex = unbalancedIndex * 2 + 1let rightChildIndex = unbalancedIndex * 2 + 2var highestPriorityIndex = unbalancedIndexif leftChildIndex < _elements.count && _hasHigherPriority(_elements[leftChildIndex], _elements[highestPriorityIndex]) {highestPriorityIndex = leftChildIndex}if rightChildIndex < _elements.count && _hasHigherPriority(_elements[rightChildIndex], _elements[highestPriorityIndex]) {highestPriorityIndex = rightChildIndex}guard highestPriorityIndex != unbalancedIndex else { break }#if swift(>=3.2)_elements.swapAt(highestPriorityIndex, unbalancedIndex)#elseswap(&_elements[highestPriorityIndex], &_elements[unbalancedIndex])#endifunbalancedIndex = highestPriorityIndex}
}
复制代码

到此,RxSwift 的优先级队列 PriorityQueue 的所有功能已经实现,它使用最大堆排序,插入和删除元素的时间复杂度都是O(log(n))。

RxSwift PriorityQueue 优先级队列的实现相关推荐

  1. Java中的PriorityQueue优先级队列

    以前的博客中介绍过队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,此时出队列时需要优先级高的元素先出队列,这个时候传统的队列显然不能胜任,Java中有一个新的实现类继 ...

  2. java 中PriorityQueue优先级队列使用方法

    1.前言 优先级队列是不同于先进先出队列的另一种队列.每次从队列中取出的是具有最高优先权的元素. PriorityQueue是从JDK1.5开始提供的新的数据结构接口. 如果想实现按照自己的意愿进行优 ...

  3. java中PriorityQueue优先级队列使用方法

    优先级队列是不同于先进先出队列的另一种队列.每次从队列中取出的是具有最高优先权的元素. PriorityQueue是从JDK1.5开始提供的新的数据结构接口. 如果不提供Comparator的话,优先 ...

  4. 优先级队列之PriorityQueue

    title: 优先级队列之PriorityQueue tags: JCF PriorityQueue 优先级队列 堆排序 categories: jcf date: 2017-11-02 21:45: ...

  5. 优先级队列(Priority Queue)

    优先级队列(Priority Queue) 注:队列是一种特征为FIFO的数据结构,每次从队列中取出的是最早加入队列中的元素.但是,许多应用需要另一种队列,每次从队列中取出的应是具有最高优先权的元素, ...

  6. 优先级队列PriorityQueue在算法问题中的使用

    文章目录 优先级队列介绍 与优先级队列有关的习题 [179. 最大数] [918. 环形子数组的最大和] [1094. 拼车] [264. 丑数 II] 前k个出现频率最高的数字 用优先级队列合并k个 ...

  7. java队列优先级_优先级队列-Java的PriorityQueue与最小堆有何不同?

    来自Java文档 表示为平衡二进制堆的优先级队列:queue [n]的两个子级是queue [2 * n + 1]和queue [2 *(n + 1)]. 优先级队列由比较器或元素的自然顺序进行排序. ...

  8. Java优先级队列PriorityQueue

    1.优先级队列概述 PriorityQueue,即优先队列.优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素).这里牵涉到了大 ...

  9. 优先级队列PriorityQueue

    优先级队列PriorityQueue 概述 前面以Java ArrayDeque为例讲解了Stack和Queue,其实还有一种特殊的队列叫做PriorityQueue,即优先队列.优先队列的作用是能保 ...

最新文章

  1. 计算机组原理ppt,计算机组原理第三章.ppt
  2. Android--多点触控事件捕捉
  3. html 打开页面光标自动选中输入框_Python自动部署码云:
  4. zero copy图解
  5. 【实战高保真】电商saas全套原型、店铺管理、店铺装修、商品管理、会员管理、维权管理、会员管理、营销管理、财务管理、渠道管理、saas系统、Axure高保真后台管理原型、rp源文件、axure原型
  6. java 栈和队列实现迷宫代码_用栈结构实现队列结构,用队列结构实现栈结构
  7. 数值计算详细笔记(二):非线性方程解法
  8. 如何评价周志华老师的新书《机器学习理论导引》“宝箱书”?
  9. 财富智慧 幸福人生——《菁英财商训练营》首场活动在深圳龙岗文博宫举行
  10. 三角脉冲信号的表达式_脉冲发生器产生一个单三角脉冲,其波形如图所示,例1写出电压U 与.pdf...
  11. tableau和oracle dv,比较Power BI和Tableau,好比用奔驰对比奥迪
  12. oracle语句查询时间范围
  13. 判断SDCard是否存在
  14. linux 模拟hba卡闪断,服务器HBA卡常见问题
  15. python调用包的路径_Python3 模块、包调用路径详解
  16. Navicat Premium操作Mongodb
  17. JCBD - 4 - JCBD - CRUD操作 即:JDBC常用接口详解
  18. RSSNewser 1.0
  19. java 集成ibm mq 教程_Spring Boot JMS与IBM WebSphere MQ集成配置
  20. (P36-P39)右值和右值引用、右值引用的作用以及使用、未定引用类型的推导、右值引用的传递

热门文章

  1. 基于jquery.fixedheadertable 表格插件左侧固定 对齐
  2. POJ 2429 GCD LCM Inverse
  3. 清理apache日志
  4. [再寄小读者之数学篇](2014-05-27 矩阵的迹与 Jacobian)
  5. 苹果修复已遭在野利用的 iOS 和 macOS 0day
  6. 每日codewars题之判断一个数是否是水仙花数
  7. Spring boot入门(三):集成AdminLTE(Freemarker),结合generate代码生成器,利用DataTable和PageHelper分页...
  8. java生成自定义标志、大小的二维码
  9. 品味ZooKeeper之Watcher机制_2
  10. PHP性能调优,PHP慢日志---善用php-fpm的慢执行日志slow log,分析php性能问题