优先队列

优先队列。

为什么不从表、栈、队列这种最简单的结构开始呢?我觉得没必要,自己看没必要,别人更没必要。

优先队列,即在普通队列的 ADT 上加了一重逻辑,让符合某种条件的元素优先出队。一般以数值大小为条件,所以也分为最大化优先队列和最小化优先队列。

优先队列的核心实现是一个堆。

堆是一种特殊的数据结构。它是一棵二叉树,元素必须从左至右填入最后一层,然后再创建新一层。因此堆满足很多有趣的性质:

1)若堆的元素个数为 N,则高度不超过 [log N] + 1。

2)优先队列可以用数组表示。在这种情况下,节点 N 的父节点在 N/2 处,左子节点在 N*2 处,右子节点在 N*2+1 处。

这两条性质是优先队列高效 ADT 实现的基础。

优先队列在堆的基础上规定:父节点不能大于、或小于子节点——这取决于是最大化堆、还是最小化堆。

优先队列的思想其实也很简单。首先构造一个堆(这个不用解释了吧)。
我做的是最小化堆,并在 Q.data[0] 处做了一个哑元。这样的好处之一是完全符合 N、N/2、N*2、N*2+1 的序号规则(试想一下 0/2、0*2、0*2+1 …… 寒毛耸立)。其二是入队的工作非常方便。

入队的主要思想是“上滤”。何为上滤?看看下面一个优先队列:

4
-5
--7
--10
-6
可以看得出来这个堆是符合优先队列的 ADT 的。现在为了插入元素 3,我们必须在数组最后的一项创建一个空穴。

4
-5
--7
--10
-6
--[3]
这个 3 的引入彻底破坏了 ADT。为了让 3 符合规则,这一步,我们需要将它与 6 交换位置。

4
-5
--7
--10
-[3]
--6
依旧不符合,我们再上滤,直到根。

[3]
-5
--7
--10
-4
--6
终于符合优先队列的 ADT 了!这也是用一个哑元的用意——不用判断是否到达根,只要哑元足够小,到了根自然会停下来。

但是,有一个问题,就是重复元的问题。如果重复元比原有元上滤得多,那么将破坏队列的性质(先进先出)。于是我们在比较的时候,要特别留意。

给出我的 Enqueue 代码(没有实际测试重复元问题,只是理论上验证了先进先出的性质)。
 
 
时间要求高的地方可以免除检查,恩~

还有出队。出队似乎很简单,由于一切都已就绪,那么返回根元素就可以了。但这就在根元素处形成了一个空穴,必须依赖于“下滤”——与“上滤”的思想基本一致。

给出代码。
  
出队麻烦得多,因为没有哑元的辅助,从代码上看效率要略低于入队。

至于优先队列的 IncreaseKey 与 DecreaseKey,完全可以用上滤、下滤的思想完成,不再赘述。只是必须知道元素所在数组下标,这就丧失了优先队列 ADT 的纯洁性。
另外,Delete 操作可以先 DecreaseKey,再 Dequeue。(这个过程我没有具体实现,其实也就是上滤的操作)。

P.S. 优先队列与“堆排序”有非常深的关系。后来我尝试使用这个例程对 100000 个数据进行排序,效果还不错;当然比起快排还是嫌慢了点。

 

 

 

最后给个完整代码:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值