浮华过后,真金始现

一切问题最终都是时间问题,一切烦恼其实都是自寻烦恼
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

[转]C#数据结构:堆之外传--实现优先级队列集合

Posted on 2008-06-23 15:36  Kolor  阅读(918)  评论(0编辑  收藏  举报
堆是古往今来唯一被泱泱中华的文人骚客们题诗作赋过的数据结构。什么,看官您不信?咱可有诗为证。诗曰:

Heap啊,Heap,
上头尖来下头粗。
有朝一日倒过来,
下头尖来上头粗。

看官您又有高论了?嗯...,上头尖来下头粗?这分明就是一坨嘛。咳咳...,看官您的观点还真...别致。总而言之呢,堆就是上头尖来下头粗的...一坨。
堆实际上是一棵完全二叉树。为了维持“上头尖来下头粗”的形状和有序性,我们规定老子要在儿子的上面,而且比儿子的键值要小。丫丫个呸,这简直就是大头儿子小头爸爸嘛。这样,它和二叉查找树比较起来有个好处,就是假如你想知道堆中“头”最小的爸爸是谁,压根不用找,最顶上那个就是。毛主席教导我们,看问题要一分为二。凡事有好就有坏,堆所带来的坏处就是除了可以很轻松的查找堆中最小值外,基本上不能做别的了。当然,插入和删除这种基本操作还是可以做的。
堆的插入操作就是暂时认为要插入的值是“头”最大的儿子,把它先放入最下层下一个可用位置上。然后让其与老子比较,看谁的“头”大。持续这个过程,让其一直朝着最顶层的根祖宗方向往上冒,直到找到合适位置。这个过程称为“percolate up”。
而删除操作则反其道而行之。我们知道,找到最小项很容易,难点在于如何删除它。当最小项“死亡”后,我们会发现,在根位置找不到“头”最小的祖宗了,形成了一个空结点。这还了得。为了维持“上头尖来下头粗”的形状,我们得找个祖宗放到该位置。于是我们把堆中最后一项放入空结点。如果最后一项应该放在这,操作就完成了。然而,这几乎不可能,除非堆的大小是2或3。那么只能沿着儿子结点往下移动了。但是有两个儿子哎,选哪个?废话,当然是选“头”较小的那个啦,这样才显得像父子,看见“大头”就来气。就这样,一直移动到合适位置。这个过程则称为“percolate down”。
那我们该怎么找父亲儿子呢?这就得依靠完全二叉树的特性了。完全二叉树是完全填满的树,只有最底层可能有例外。最底层按从左到右的次序填入,中间不能有空结点。如图所示:
图1
图2
图3
图4
图1是一棵满二叉树;图2是一棵完全二叉树;图3和图4都不是完全二叉树,因为这两个中间都有空结点。很显然,满二叉树也是完全二叉树。看看图1和图2是不是很像“上头尖来下头粗”的一坨,:-)。
完全二叉树有许多有用的特性。比如,N个结点的完全二叉树的高度至多是 logN 。不需要 left 和 right 链接等。这样,我们可以将一棵完全二叉树以按层遍历的次序存储在数组中(这符合原则4)。我们把根祖宗放入数组索引 1 的位置,其他结点依次放入。为什么不放在索引 0 呢?因为除了根结点外,每个结点都有父节点。为了避免特殊情况,我们保留位置 0 为哑结点,使其可以当作根结点的父亲。此外,还有其他好处,不再一一列举。这样,我们对数组中任意索引 i 的结点,均可以在索引 2i 处找到它的左儿子。如果该位置超出了树中结点数,那我们就知道它没有左儿子。同理,它的右儿子在索引 2i+1 处。当然,我们也要测试其是否存在。它老子则在 i/2 索引处。
如果只需要插入,删除和找最小值三种操作,那么我们应该毫不犹豫的使用堆。那什么时候会有这种情况?比方说,喜欢我的MM有一个加强排。这么多人每天都围在你身边嗡嗡来嗡嗡去,咋应付啊?于是,我定个标准:每次,我都只找队伍里面最清纯的MM。这下,整个世界清净了。以后,当遇见新美女时,就把她放到队伍合适的位置。那些跟咱Say 88的MM,则一概从队伍里面开除。这个时候,这个队伍用堆来排列最合适不过。这听起来好像优先级队列啊。没错,堆还有一个别名,正是优先级队列。
很遗憾,.NET并没有提供优先级队列集合。我们只能自己编写,下面是完整源代码。
PriorityQueue完整源码
  1using System;
  2using System.Collections.Generic;
  3
  4namespace Lucifer.DataStructure
  5{
  6    public class PriorityQueue<T>
  7    {
  8        #region 私有变量
  9
10        private int theSize;
11        private T[] theArray;
12        private IComparer<T> theComparer;
13
14        private const int defaultCapacity = 100;
15
16        #endregion
17
18        #region 构造函数
19
20        public PriorityQueue()
21            : this(Comparer<T>.Default)
22        {
23        }
24
25        public PriorityQueue(IComparer<T> comparer)
26        {
27            this.theArray = new T[defaultCapacity];
28            this.theComparer = comparer;
29        }
30
31        public PriorityQueue(IEnumerable<T> collection)
32        {
33            this.theComparer = Comparer<T>.Default;
34
35            ICollection<T> currentCollection = collection as ICollection<T>;
36
37            if (currentCollection != null)
38            {
39                this.theSize = currentCollection.Count;
40                this.theArray = new T[(theSize + 2) * 11 / 10];
41                currentCollection.CopyTo(theArray, 1);
42            }
43            else
44            {
45                int i = 1;
46                foreach (T item in collection)
47                {
48                    this.theArray[i++] = item;
49                    theSize++;
50                }
51            }
52
53            this.BuildHeap();
54        }
55
56        #endregion
57
58        #region 公有方法
59        /// <summary>
60        /// 添加 item 到优先级队列中。
61        /// </summary>
62        /// <param name="item">要添加的值。</param>
63        public void Add(T item)
64        {
65            if (theSize + 1 == theArray.Length)
66            {
67                Array.Resize<T>(ref theArray, theArray.Length == 0 ? defaultCapacity : (theArray.Length * 2));
68            }
69            /*
70            * 向上过滤。
71            */
72            int hole = ++theSize;
73            theArray[0] = item;
74            for (; theComparer.Compare(item, theArray[hole / 2]) < 0; hole /= 2)
75            {
76                theArray[hole] = theArray[hole / 2];
77            }
78            theArray[hole] = item;
79        }
80
81        /// <summary>
82        /// 清空优先级队列。
83        /// </summary>
84        public void Clear()
85        {
86            Array.Clear(theArray, 0, theSize);
87            this.theSize = 0;
88        }
89
90        /// <summary>
91        /// 移除优先级队列中最小项。
92        /// </summary>
93        /// <returns>返回优先级队列中被移除的最小项。</returns>
94        public T Remove()
95        {
96            T item = this.GetMin();
97            theArray[1] = theArray[theSize--];
98            this.PercolateDown(1);
99
100            return item;
101        }
102
103        /// <summary>
104        /// 获得优先级队列中最小项。
105        /// </summary>
106        /// <returns>返回优先级队列中最小项。</returns>
107        /// <exception>抛出 InvalidOperationException 异常,当队列为空时。</exception>
108        public T GetMin()
109        {
110            if (this.IsEmpty())
111            {
112                throw new InvalidOperationException("The queue is empty.");
113            }
114
115            return theArray[1];
116        }
117
118        /// <summary>
119        /// 判断优先级队列是否为空。
120        /// </summary>
121        /// <returns>如果是空队列,返回 true 。否则,返回 false 。</returns>
122        public bool IsEmpty()
123        {
124            return theSize == 0;
125        }
126
127        #endregion
128
129        #region 公有属性
130
131        /// <summary>
132        /// 获得优先级队列中项的数量。
133        /// </summary>
134        public int Count
135        {
136            get
137            {
138                return theSize;
139            }
140        }
141
142        #endregion
143
144        #region 私有方法
145        /*
146        * 构造一个堆。
147        */
148        private void BuildHeap()
149        {
150            for (int i = theSize / 2; i > 0; i--)
151            {
152                this.PercolateDown(i);
153            }
154        }
155
156        /*
157        * 向下过滤。
158        */
159        private void PercolateDown(int hole)
160        {
161            int child;
162            T item = theArray[hole];
163            for (; hole * 2 <= theSize; hole = child)
164            {
165                child = hole * 2;
166                /*
167                * 父结点并不总是有两个子结点。
168                */
169                if (child != theSize && theComparer.Compare(theArray[child + 1], theArray[child]) < 0)
170                {
171                    child++;
172                }
173
174                if (theComparer.Compare(theArray[child], item) < 0)
175                    theArray[hole] = theArray[child];
176                else
177                    break;
178            }
179            theArray[hole] = item;
180        }
181
182        #endregion
183    }
184}


此外,还有一些别的堆。比如斜堆,偶堆,斐波那契堆等,不再阐述。有兴趣的朋友可以参考其他资料。