用 C# 实现优先队列

 SOURCE:http://www.cnblogs.com/skyivben/archive/2009/04/18/1438731.html

优先队列(priority queue) 是很重要的数据结构。我在做 ACM 题时就经常要用到她。C++ STL 就包括 priority_queue 。Java 也有 PriorityQueue 类。遗憾的是,.NET Framework Base Class Library 中并不包括优先队列。于是,我只好自己用 C# 语言写一个,如下所示:

using System;
using System.Collections.Generic;

namespace Skyiv.Util
{
  class PriorityQueue<T>
  {
    IComparer<T> comparer;
    T[] heap;

    public int Count { get; private set; }

    public PriorityQueue() : this(null) { }
    public PriorityQueue(int capacity) : this(capacity, null) { }
    public PriorityQueue(IComparer<T> comparer) : this(16, comparer) { }

    public PriorityQueue(int capacity, IComparer<T> comparer)
    {
      this.comparer = (comparer == null) ? Comparer<T>.Default : comparer;
      this.heap = new T[capacity];
    }

    public void Push(T v)
    {
      if (Count >= heap.Length) Array.Resize(ref heap, Count * 2);
      heap[Count] = v;
      SiftUp(Count++);
    }

    public T Pop()
    {
      var v = Top();
      heap[0] = heap[--Count];
      if (Count > 0) SiftDown(0);
      return v;
    }

    public T Top()
    {
      if (Count > 0) return heap[0];
      throw new InvalidOperationException("优先队列为空");
    }

    void SiftUp(int n)
    {
      var v = heap[n];
      for (var n2 = n / 2; n > 0 && comparer.Compare(v, heap[n2]) > 0; n = n2, n2 /= 2) heap[n] = heap[n2];
      heap[n] = v;
    }

    void SiftDown(int n)
    {
      var v = heap[n];
      for (var n2 = n * 2; n2 < Count; n = n2, n2 *= 2)
      {
        if (n2 + 1 < Count && comparer.Compare(heap[n2 + 1], heap[n2]) > 0) n2++;
        if (comparer.Compare(v, heap[n2]) >= 0) break;
        heap[n] = heap[n2];
      }
      heap[n] = v;
    }
  }
}

如上所示,这个 PriorityQueue<T> 泛型类提供四个公共构造函数,第一个是无参的构造函数,其余的构造函数允许指定优先队列中包括的初始元素数(capacity)、如何对键进行比较(comparer)。

这个程序使用堆(heap)来实现优先队列。所以,所需的空间是最小的。Count 属性和 Top 方法的时间复杂度是 O(1),Push 和 Pop 方法的时间复杂度都是 O(logN)。

我曾经用 List<T> 泛型类实现过一个优先队列,请参见我的博客 Timus1016. A Cube on the Walk 。虽然更简单,程序代码只有 23 行,但是效率不高,其 Push 和 Pop 方法的时间复杂度都是 O(N)。

posted on 2009-04-18 15:33 银河 阅读(1054) 评论(17)   编辑 收藏 网摘 所属分类: 算法



#2楼  2009-04-18 16:41 DiryBoy       
我之前用Reflector查一下WPF源代码,发现有的,没公开而已。
   回复   引用   查看     

#3楼  [ 楼主] 2009-04-18 16:52 银河       
@DiryBoy
没公开的话,我们也用不了。不过倒是可以参考她的源代码。
不过我估计其源代码会比较复杂,因为我估计 .NET Framework Base Class Library 会实现很多标准的优先队列用不上的内容,比如实现 IEnumerable<T> 接口之类的。
另外,.NET Framework 3.5 中也有 BigInteger 类,也是没有公开,所以也用不了。好消息是 BigInteger 将在 .NET Framework 4.0 中公开。

   回复   引用   查看     

#4楼  [ 楼主] 2009-04-18 17:47 银河       
@DiryBoy
我也用 Reflector 查了一下 .NET Framework 3.5 Base Class Library 的源代码,发现以下相关的类:
System.Workflow.Runtime.KeyedPriorityQueue<K,V,P>
System.Windows.Threading.PriorityQueue<T>
MS.Internal.PriorityQueue<T>
她们分别位于以下程序集:
Sysem.Workflow.Runtime, Version=3.0.0.0
WindowsBase, Version=3.0.0.0
PresentationCore, Version=3.0.0.0
她们都是 internal 的类,因此我们无法使用。
她们都是直接从 object 类派生的,都没有实现任何接口,也都比较简单。(看来在3楼的估计有误)
第二个类主要用于线程优先级调度。第三个类就是标准的优先队列了。



   回复   引用   查看     

#5楼  [ 楼主] 2009-04-18 18:08 银河       
using System;
using System.Collections.Generic;

namespace MS.Internal
{
  internal class PriorityQueue<T>
  {
    // Fields
    private IComparer<T> _comparer;
    private int _count;
    private T[] _heap;
    private const int DefaultCapacity = 6;

    // Methods
    internal PriorityQueue(int capacity, IComparer<T> comparer)
    {
      this._heap = new T[(capacity > 0) ? capacity : 6];
      this._count = 0;
      this._comparer = comparer;
    }

    private static int HeapLeftChild(int i)
    {
      return ((i * 2) + 1);
    }

    private static int HeapParent(int i)
    {
      return ((i - 1) / 2);
    }

    private static int HeapRightFromLeft(int i)
    {
      return (i + 1);
    }

    internal void Pop()
    {
      if (this._count > 1)
      {
        int i = 0;
        for (int j = PriorityQueue<T>.HeapLeftChild(i); j < this._count; j = PriorityQueue<T>.HeapLeftChild(i))
        {
          int index = PriorityQueue<T>.HeapRightFromLeft(j);
          int num4 = ((index < this._count) && (this._comparer.Compare(this._heap[index], this._heap[j]) < 0)) ? index : j;
          this._heap[i] = this._heap[num4];
          i = num4;
        }
        this._heap[i] = this._heap[this._count - 1];
      }
      this._count--;
    }

    internal void Push(T value)
    {
      if (this._count == this._heap.Length)
      {
        T[] localArray = new T[this._count * 2];
        for (int j = 0; j < this._count; j++)
        {
          localArray[j] = this._heap[j];
        }
        this._heap = localArray;
      }
      int i = this._count;
      while (i > 0)
      {
        int index = PriorityQueue<T>.HeapParent(i);
        if (this._comparer.Compare(value, this._heap[index]) >= 0)
        {
          break;
        }
        this._heap[i] = this._heap[index];
        i = index;
      }
      this._heap[i] = value;
      this._count++;
    }

    // Properties
    internal int Count
    {
      get
      {
        return this._count;
      }
    }

    internal T Top
    {
      get
      {
        return this._heap[0];
      }
    }
  }
}

   回复   引用   查看     

#6楼  [ 楼主] 2009-04-18 18:13 银河       
上面5楼就是用 Reflector 得到的 MS.Internal.PriorityQueue<T> 类的源程序代码。也是用堆来实现优先队列,不过它用的是最小堆,而不是最大堆。也就是说,该优先队列中最小的元素最优先,而不是象通常的优先队列一样最大的元素最优先。使用时要注意传递给构造方法的第二个参数(IComparer<T> comparer)要和平常的用法相反就行了。
   回复   引用   查看     

#7楼  2009-04-18 20:25 Sphinxdwood       
就是用C#实现一个堆喽?
   回复   引用   查看     

#8楼  2009-04-18 20:39 JimLiu       
我以前发过一个,LZ可以交流交流
   回复   引用   查看     

#9楼  [ 楼主] 2009-04-18 20:39 银河       
@Sphinxdwood
不错,就是用 C# 实现一个堆,然后用这个堆实现一个优先队列。

   回复   引用   查看     

#10楼  [ 楼主] 2009-04-18 20:54 银河       
@JimLiu
能给出你的文章的 URL 吗?谢谢!
   回复   引用   查看     

#11楼  2009-04-18 21:51 JimLiu       
@银河
http://www.cnblogs.com/jimnox/archive/2008/09/09/1287872.html
这是第一篇
http://www.cnblogs.com/jimnox/archive/2008/09/10/1288086.html
这是改进
   回复   引用   查看     

#12楼  [ 楼主] 2009-04-18 22:14 银河       
@JimLiu
谢谢。
你的程序的思路和我的程序以及 MS.Internal.PriorityQueue<T> 类都是一样的,都是使用堆来实现优先队列。
另外,你的第二篇中通过增加带一个布尔值的构造函数来决定是大者优先还是小者优先。虽然这两种情况是最常用的,但是如果改为增加一个实现 IComparer<T> 接口的类来决定如何比较大小的构造函数更好。这样,没有实现 IComparable<T> 的类或者结构也可以使用优先队列了。
   回复   引用   查看     

#13楼  2009-04-18 22:54 junchu [未注册用户]
我觉得既然知道STL的priority_queue, 那可以参照它的方式, 将heap一系列操作封装成一堆函数算法. 而不是单独在某个类型中实现.
   回复   引用     

#14楼  [ 楼主] 2009-04-18 23:27 银河       
@junchu
我仅仅是知道 C++ STL 中有 priority_queue 而已。对 C++ STL 并不熟悉。
将 heap 的一系列操作封装成一堆函数算法是很好的主意。有空的时候研究一下试试。

   回复   引用   查看     

#15楼  2009-04-18 23:33 JimLiu       
@银河
其实我本来的目标是传个比较函数进去的,这样比较习惯,STL中的sort就这样,用成习惯了。
STL中的PQ要求重载小于运算符,并且一定要返回严格结果,否则可能抛出异常,这也算接口约束吧,考虑到这一点所以最初我设计为元素实现IComparable<T>
   回复   引用   查看     

#16楼  2009-04-19 18:12 非主流程序员       
发现同学们写代码都不喜欢加注释!!!!!!!!!!!!!!!!!!!
如果我想根据ID查找相应的item,并改变item.value怎么办?
   回复   引用   查看     

#17楼  [ 楼主] 2009-04-19 18:27 银河       
@非主流程序员
这正是我在下一篇随笔:
http://www.cnblogs.com/skyivben/archive/2009/04/19/1439154.html
中实现的 KeyedPriorityQueue 类所做的事情。
在那篇随笔中,需要实现一个内存管理器,而 KeyedPriorityQueue 类用来保存已经分配的“内存块”。当操作系统发出一个访问指定编号(Id)的内存块的请求时,就需要根据该编号(Id)在该优先队列中查找“内存块”(bool ContainsKey(int id))。如果找到该“内存块”,还需要更新其到期时间(Update(Block v))。
不写注释是因为代码本身已经是自注释的了,变量名和方法名都是有意义的。随笔中也做了少量的说明。“堆(heap)”是著名的数据结构,可以在任何一本讲数据结构和算法的书中找到详细的说明。注释也无法解释得更清楚。

   回复   引用   查看     
#1楼  2009-04-18 16:20 Jeffrey Zhao       

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值