d-ary heap简介:

d-ary heap 是泛化版本的binary heap(d=2),d-ary heap每个非叶子节点最多有d个孩子结点。

d-ary heap拥有如下属性:

  1. 类似complete binary tree,除了树的最后一层,其它层全部填满结点,且增加结点方式由左至右。
  2. 类似binary heap,它也分两类最大堆和最小堆。

下面给出一个3-ary heap示例:

3-ary max heap - root node is maximum of all nodes10/      |     \7       9      8/  |  \    /4   6   5  73-ary min heap -root node is minimum of all nodes10/    |    \12     11    13/ | \14 15 18 

具有n个节点的完全d叉树的高度由logdn给出。

d-ary heap的应用:

d-ary heap常用于进一步实现优先级队列,d-ary heap实现的优先级队列比用binary heap实现的优先队列在添加新元素的方面效率更高。binary heap:O(log2n) vs d-ary heap: O(logkn) ,当d > 2 时,logkn < log2n 。但是d-ary heap实现的优先级队列缺点是提取优先级队列首个元素比binary heap实现的优先队列需要消耗更多性能。binary heap:O(log2n) vs d-ary heap:O((d-1)logdn),当 d > 2 时,(d-1)logdn > log2n ,通过对数换底公式可证。结果看起来喜忧参半,那么什么情况下特别适合使用d-ary heap呢?答案就是游戏中常见的寻路算法。就以A*和Dijkstra algorithm举例。两者一般都需要一个优先级队列(有某些A*算法不适用优先级队列,比如迭代加深A*),而这些算法在取出队列首个元素时,往往要向队列中添加更多的临近结点。也就是添加结点次数远远大于提取次数。那么正好,d-ary heap可以取长补短。另外,d-ary heap比binary heap 对缓存更加友好,更多的子结点相邻在一起。故在实际运行效率往往会更好一些。

d-ary heap及优先级队列的实现:

我们用数组实现d-ary heap,数组以0为起始,可以得到如下规律:

  • 若该结点为非根结点,那么使用该结点的索引i可以取得其的父结点索引,父结点为(i-1)/d;
  • 若该结点的索引为i,那么它的孩子结点索引分别为(d*i)+1 , (d*i)+2 …. (d*i)+d;
  • 若heap大小为n,最后一个非叶子结点的索引为(n-1)/d;(注:本文给出的实现并没有使用该规则)

构建d-ary heap堆:本文给出的实现侧重于进一步实现优先级队列,并采用最小堆(方便适配寻路算法)。所以把一个输入数组堆化,并不是核心操作,为了方便撰写代码以及加强可读性,构建堆算法采用从根结点至下方式,而不是从最后一个非叶子结点向上的方式。优点显而易见,代码清晰,不需要使用递归且不需要大量if else语句来寻找最小的孩子结点。只要孩子结点的值小于其父节点将其交换即可。缺点显而易见,交换次数增加从而降低效率。

public void BuildHeap() {for (int i = 1; i < numberOfItems; i++)       {int bubbleIndex = i;ar node = heap[i];while (bubbleIndex != 0)        {int parentIndex = (bubbleIndex-1) / D;if (node.CompareTo(heap[parentIndex]) < 0)            {heap[bubbleIndex] = heap[parentIndex];heap[parentIndex] = node;bubbleIndex = parentIndex;} else           {break;}}}}

Push:向优先级队列中添加新的元素,若添加node为空,抛出异常,若空间不足,则扩展空间。最后调用内部函数DecreaseKey加入新的结点到d-ary heap。

public void Push(T node) {if (node == null) throw new System.ArgumentNullException("node");if (numberOfItems == heap.Length)     {Expand();}DecreaseKey(node, (ushort)numberOfItems);numberOfItems++;
}

DecreaseKey:传入的index为当前队列中现有元素的数量。这个函数是私有的,因为对于优先级队列来说并不需要提供改接口。这里我们使用了一个优化技巧,暂不保存待加入的结点到数组,直到我们找到了它在数组中的合适位置,这样可以节省不必要的交换。

private void DecreaseKey (T node, ushort index){if(index < numberOfItems){if(node.CompareTo(heap[index]) > 0 ){throw new System.Exception("New node key greater than orginal key");}}int bubbleIndex = index;while (bubbleIndex != 0)         {// Parent node of the bubble nodeint parentIndex = (bubbleIndex-1) / D;if (node.CompareTo(heap[parentIndex]) < 0 ) {// Swap the bubble node and parent node// (we don't really need to store the bubble node until we know the final index though// so we do that after the loop instead)heap[bubbleIndex] = heap[parentIndex];bubbleIndex = parentIndex;} else {break;}}heap[bubbleIndex] = node;
}

Pop:弹出优先级队列top元素,调用内部函数ExtractMin。

public T Pop () {return ExtractMin();
}

ExtractMin:返回当前root node,更新numberOfItems,重新堆化。把最后一个叶子结点移动到root node,结点依照规则上浮。这里使用了同样的优化技巧。不必把最后一个叶子结点保存到数组0的位置,等到确定其最终位置再把它存入数组。这样做的好处节省交换次数。

private T ExtractMin()
{T returnItem = heap[0];numberOfItems--;if (numberOfItems == 0) return returnItem;// Last item in the heap arrayvar swapItem = heap[numberOfItems];int swapIndex = 0, parent;while (true) {parent = swapIndex;var curSwapItem = swapItem;int pd = parent * D + 1;// If this holds, then the indices used// below are guaranteed to not throw an index out of bounds// exception since we choose the size of the array in that wayif (pd <= numberOfItems)            {for(int i = 0;i<D-1;i++){if (pd+i < numberOfItems && (heap[pd+i].CompareTo(curSwapItem) < 0)){curSwapItem = heap[pd+i];swapIndex = pd+i;}}if (pd+D-1 < numberOfItems && (heap[pd+D-1].CompareTo(curSwapItem) < 0))                     {swapIndex = pd+D-1;}}// One if the parent's children are smaller or equal, swap them// (actually we are just pretenting we swapped them, we hold the swapData// in local variable and only assign it once we know the final index)if (parent != swapIndex) {heap[parent] = heap[swapIndex];} else {break;}}// Assign element to the final positionheap[swapIndex] = swapItem;// For debugging
            Validate ();return returnItem;}

时间复杂度分析:

  • 对于用d ary heap实现的优先级队列,若队列拥有n个元素,其对应堆的高度最大为logdn ,添加新元素时间复杂度为O(logdn)
  • 对于用d ary heap实现的优先级队列,若队列拥有n个元素,其对应堆的高度最大为logdn,要在d个孩子结点当中选取最小或最大结点,层层不断上浮。故删除队首元素时间复杂度为(d-1)logdn
  • 对于把数组转化为d ary heap,采用从最后一个非叶子结点向上的方式,其时间复杂度为O(n),分析思路和binary heap一样。举例说明,对于拥有n个结点的4 ary heap,高度为1子树的有(3/4)n,高度为2的子树有(3/16)n... 处理高度为1的子树需要O(1),处理高度为2的子树需要O(2)... 累加公式为  $\sum_{k=1}^{log_{4}^{n}}{\frac{3}{4^{k}}}nk$ ,根据比值收敛法可知这个无穷级数是收敛的,故复杂度仍为O(n)。那么对于本文给出的自顶向下的方式,其复杂度又如何呢?答案为O($dlog_{d}^{n}n$),具体的运算过程(详见下一条),理论上时间复杂度要高于采用从最后一个非叶子结点向上的方式。但两者实际效率相差多少需进行实际测试。
  • 本文的buildheap算法,第i层的结点至多需要比较和交换i次,且第i层结点数di,由此可得时间统计范式为$\sum_{i=1}^{log_{d}^{n}}{d^{i}}i$,以d=4为例 $\sum_{i=1}^{log_{4}^{n}}{4^{i}}i$。需要求前i项和Si关于i的表达式,Si= 1*4 +2*42+3*43+.....+ i*4i ,那么4Si=1*42+2*43+......+i*4i+1,用4Si-Si进行错位相减,得知3Si=i*4i+1 - (4+42+......+4i) 。痛快,后者是一个等比数列。这样整个式子最后表达为$Si=\frac{4}{9}+\frac{1}{3}(i-\frac{1}{3})4^{i+1}$,我们知道i值为logdn,代入可得O($dlog_{d}^{n}n$)。

总结:

通过使用System.Diagnostics.Stopwatch 进行多次测试,发现d=4 时,push和pop的性能都不错,d=4很多情况下Push都比d=2的情况要好一些。push可以确定性能确实有所提高,pop不能确定到底是好了还是坏了,实验结果互有胜负。说到底System.Diagnostics.Stopwatch并不是精确测试,里面还有.net的噪音。

附录:

优先级队列完整程序

Q&A:

Q:

我的寻路算法想要使用C++或Java标准库自带的PriorityQueue,两者都没有提供DecreaseKey函数,带来的问题是我无法更新队列里元素key,没有办法进行边放松,如何处理?

A:

笔者文章DecreaseKey也是私有的,没有提供给PriorityQueue的使用者。为什么不提供呢?因为即便提供了寻路算法如何给出DecreaseKey所需的index呢?我们知道需要更新的元素在优先级队列中,但是index并不知道,要获取index就需要进行搜索(或者使用额外数据结构辅助)。使用额外的数据结构辅助确定index必然占用更多内存空间,使用搜索确定index必然消耗更多时间尤其是当队列中元素很多时。诀窍根本不改变它。而是将该节点的  "新建副本 " (具有新的更好的成本) 添加到优先级队列中。由于成本较低, 该节点的新副本将在队列中的原始副本之前提取, 因此将在前面进行处理。后面遇到的重复结点直接忽略即可,并且很多情况还没等到处理重复结点时我们已经找到路径了。我们所额外负担的就是优先级队列中存在一些多余对象。这种负担非常小,而且实现起来简便。

参考文献:

https://www.geeksforgeeks.org/k-ary-heap/

http://en.wikipedia.org/wiki/Binary_heap

https://en.wikipedia.org/wiki/D-ary_heap

欢迎评论区交流,批评,指正~

原创文章,转载请标明出处,谢谢~

转载于:https://www.cnblogs.com/tangzhenqiang/p/9508667.html

d-ary heap实现一个快速的优先级队列(C#)相关推荐

  1. python优先级排序_Python实现一个优先级队列的方法

    问题 怎样实现一个按优先级排序的队列? 并且在这个队列上面每次 pop 操作总是返回优先级最高的那个元素 解决方案 下面的类利用 heapq 模块实现了一个简单的优先级队列: import heapq ...

  2. 《Python Cookbook 3rd》笔记(1.5):实现一个优先级队列

    实现一个优先级队列 问题 怎样实现一个按优先级排序的队列?并且在这个队列上面每次pop操作总是返回优先级最高的那个元素. 解法 下面的类利用 heapq 模块实现了一个简单的优先级队列: import ...

  3. 二叉堆详解实现优先级队列

    二叉堆详解实现优先级队列 文章目录 二叉堆详解实现优先级队列 一.二叉堆概览 二.优先级队列概览 三.实现 swim 和 sink 四.实现 delMax 和 insert 五.最后总结 二叉堆(Bi ...

  4. 【STL学习】优先级队列Priority Queue详解与C++编程实现

    优先级队列Priority Queue介绍 优先级队列是一个拥有权值观念的queue.它允许在底端添加元素.在顶端去除元素.删除元素. 优先级队列内部的元素并不是按照添加的顺序排列,而是自动依照元素的 ...

  5. C++数据结构与算法(九) 树,优先级队列,最大堆的实现

    树: 用来表示具有结构层次的数据,应用: 软件工程技术:模块化技术 根: 子树: 在树中,每个元素都代表一个节点. 树的级: 根是一级,根的孩子是二级,一次往下,有三级,四级... 树的高度(深度): ...

  6. Go实战 | 一文带你搞懂从单队列到优先级队列的实现

    大家好,我是渔夫子,今天跟大家聊聊在我们项目中的优先级队列的实现.   优先级队列概述 队列,是数据结构中实现先进先出策略的一种数据结构.而优先队列则是带有优先级的队列,即先按优先级分类,然后相同优先 ...

  7. 【数据结构Python描述】优先级队列描述“银行VIP客户插队办理业务”及“被插队客户愤而离去”的模型实现

    文章目录 一.支持插队模型的优先级队列 队列ADT扩充 队列记录描述 方法理论步骤 `update(item, key, value)` `remove(item)` 二.支持插队模型的优先级队列实现 ...

  8. 二叉堆(TopK问题,优先级队列)

    目录 实现一个大根堆 优先级队列 Comparable和Compator区别 compareTo方法 TopK问题 TopK问题常见题型为求最大(最小)的K个值. 我们一般拿堆来解决. 堆:二叉堆首先 ...

  9. 优先级队列 c语言,队列优先级

    优先级队列比队列更专业的数据结构.像普通队列,优先级队列中有相同的方法,但在使用上是有比较大的区别的.在优先级队列数据项都受到键值排序,以便与最低键的值,数据项在前方,键的最高值的数据项在后方,反之亦 ...

最新文章

  1. CMD——ping及用其检测网络故障
  2. pfSense设置多WAN后,解决网银无法登陆问题
  3. SharePoint 2010 与 SQL Server 2012 报表服务集成
  4. 使用 cout 输出数据之控制输出格式(二)
  5. 正则表达式(基础、常用)----JavaScript
  6. Java静态域与静态方法
  7. 社会工程学***的八种常用方法
  8. frp + nginx 配置多人共用的http 内网穿透服务
  9. 近7成开发者无开源收入、最想操作系统开源、Java最受欢迎 | 揭晓中国开源开发者现状...
  10. linux软件包管理系统的意义,Linux系统的软件包管理——RPM
  11. 什么叫pmt测试分析_圆偏振发光光谱仪——南方科技大学分析测试中心设备介绍第51期...
  12. 数据结构上机实践第八周项目6- 猴子选大王(数组版)
  13. 正好股票资讯大盘平衡被打破
  14. 台北故宫博物院收藏:气势开张,米芾行草书法真迹《真酥帖》赏析
  15. demonstration记忆_记忆英语单词方法20种
  16. go用函数字符串名调用函数
  17. 单点登录(SSO)、CAS介绍
  18. 自己收款码实现个人网站支付
  19. css表格nth左对齐,使用CSS nth-child选择单个表格单元格
  20. 高并发下的幂等策略分析

热门文章

  1. SpringCloud系列之版本选择
  2. 阿呆喵广告过滤 v1.9.0.1 官网版
  3. IObit Uninstaller 10Pro BD
  4. Wikipedia iOS客户端源码
  5. [Done]FindBugs: boxing/unboxing to parse a primitive
  6. Idea中maven 只从本地仓库导入jar包,取消联网下载的问题
  7. Flatten()详解
  8. 使用仿射变换将一幅图像放置到另一幅图像中
  9. 手工搭建多层(多隐藏层)BP神经网络
  10. Qt 资源图片删除后,错误 needed by `debug/qrc_image.cpp'. Stop. 的终极解决办法