優先佇列,非堆莫屬。
一:堆結構
1: 性質
堆是一種很鬆散的序結構樹,只儲存了父節點和孩子節點的大小關係,並不規定左右孩子的大小,不像排序樹那樣嚴格,又因為堆是一種完全二叉
樹,設節點為 i, 則 i/2 是 i 的父節點,2i 是 i 的左孩子,2i+1 是 i 的右孩子,所以在實現方式上可以採用輕量級的陣列。

2:用途
如果大家玩過微軟的 MSMQ 的話,我們發現它其實也是一個優先佇列,還有剛才說的抓取 url,不過很遺憾,為什麼.net 類庫中沒有優先佇列,而 java1.5
中就已經支援了。
3:實現
<1>堆結構節點定義:
我們在每個節點上定義一個 level,表示該節點的優先順序,也是構建堆時採取的依據。

1 ///

2 /// 定義一個陣列來存放節點
3 ///

4 private List nodeList = new List();
5
6 #region 堆節點定義
7 ///

8 /// 堆節點定義
9 ///

10 public class HeapNode
11 {
12 ///

13 /// 實體資料
14 ///

15 public T t { get; set; }
16
17 ///

18 /// 優先順序別 1-10 個級別 (優先順序別遞增)
19 ///

20 public int level { get; set; }
21
22 public HeapNode(T t, int level)
23 {
24 this.t = t;
25 this.level = level;
26 }
27
28 public HeapNode() { }
29 }
30 #endregion

<2> 入隊操作
入隊操作時我們要注意幾個問題:
①:完全二叉樹的構建操作是 “從上到下,從左到右” 的形式,所以入隊的節點是放在陣列的最後,也就是樹中葉子層的有序最右邊空位。
②:當節點插入到最後時,有可能破壞了堆的性質,此時我們要進行 “上濾操作”,當然時間複雜度為 O(lgN) 。

當我將節點 “20” 插入到堆尾的時候,此時破壞了堆的性質,從圖中我們可以清楚的看到節點 “20” 的整個上濾過程,有意思吧,還有一點
就是:獲取插入節點的父親節點的演演算法是:parent=list.count/2-1 。這也得益於完全二叉樹的特性。

1 #region 新增操作
2 ///

3 /// 新增操作
4 ///

5 public void Eequeue(T t, int level = 1)
6 {
7 //將當前節點追加到堆尾
8 nodeList.Add(new HeapNode(t, level));
9
10 //如果只有一個節點,則不需要進行篩操作
11 if (nodeList.Count == 1)
12 return;
13
14 //獲取最後一個非葉子節點
15 int parent = nodeList.Count / 2 – 1;
16
17 //堆調整
18 UpHeapAdjust(nodeList, parent);
19 }
20 #endregion
21
22 #region 對堆進行上濾操作,使得滿足堆性質
23 ///

24 /// 對堆進行上濾操作,使得滿足堆性質
25 ///

26 /// 27 /// 非葉子節點的之後指標(這裡要注意:我們
28 /// 的篩操作時針對非葉節點的)
29 /// 30 public void UpHeapAdjust(List nodeList, int parent)
31 {
32 while (parent >= 0)
33 {
34 //當前 index 節點的左孩子
35 var left = 2 * parent + 1;
36
37 //當前 index 節點的右孩子
38 var right = left + 1;
39
40 //parent 子節點中最大的孩子節點,方便於 parent 進行比較
41 //預設為 left 節點
42 var max = left;
43
44 //判斷當前節點是否有右孩子
45 if (right < nodeList.Count) 46 { 47 //判斷 parent 要比較的最大子節點 48 max = nodeList[left].level < nodeList[right].level ? right : left; 49 } 50 51 //如果 parent 節點小於它的某個子節點的話,此時篩操作 52 if (nodeList[parent].level < nodeList[max].level) 53 { 54 //子節點和父節點進行交換操作 55 var temp = nodeList[parent]; 56 nodeList[parent] = nodeList[max]; 57 nodeList[max] = temp; 58 59 //繼續進行更上一層的過濾 60 parent = (int)Math.Ceiling(parent / 2d) - 1; 61 } 62 else 63 { 64 break; 65 } 66 } 67 } 68 #endregion <3> 出隊操作
從圖中我們可以看出,優先順序最大的節點會在一陣痙攣後上升到堆頂,出隊操作時,我們採取的方案是:彈出堆頂元素,然後將葉子層中
的最右子節點賦給堆頂,同樣這時也會可能存在破壞堆的性質,最後我們要被迫進行下濾操作。

我圖中可以看出:首先將堆頂 20 彈出,然後將 7 賦給堆頂,此時堆性質遭到破壞,最後我們清楚的看到節點 7 的下濾過程,從攤還分析的角度上
來說,下濾的層數不超過 2-3 層,所以整體上來說出隊的時間複雜度為一個常量 O(1) 。

1 #region 優先佇列的出隊操作
2 ///

3 /// 優先佇列的出隊操作
4 ///

5 ///
6 public HeapNode Dequeue()
7 {
8 if (nodeList.Count == 0)
9 return null;
10
11 //出佇列操作,彈出資料頭元素
12 var pop = nodeList[0];
13
14 //用尾元素填充頭元素
15 nodeList[0] = nodeList[nodeList.Count – 1];
16
17 //刪除尾節點
18 nodeList.RemoveAt(nodeList.Count – 1);
19
20 //然後從根節點下濾堆
21 DownHeapAdjust(nodeList, 0);
22
23 return pop;
24 }
25 #endregion
26
27 #region 對堆進行下濾操作,使得滿足堆性質
28 ///

29 /// 對堆進行下濾操作,使得滿足堆性質
30 ///

31 /// 32 /// 非葉子節點的之後指標(這裡要注意:我們
33 /// 的篩操作時針對非葉節點的)
34 /// 35 public void DownHeapAdjust(List nodeList, int parent)
36 {
37 while (2 * parent + 1 < nodeList.Count) 38 { 39 //當前 index 節點的左孩子 40 var left = 2 * parent + 1; 41 42 //當前 index 節點的右孩子 43 var right = left + 1; 44 45 //parent 子節點中最大的孩子節點,方便於 parent 進行比較 46 //預設為 left 節點 47 var max = left; 48 49 //判斷當前節點是否有右孩子 50 if (right < nodeList.Count) 51 { 52 //判斷 parent 要比較的最大子節點 53 max = nodeList[left].level < nodeList[right].level ? right : left; 54 } 55 56 //如果 parent 節點小於它的某個子節點的話,此時篩操作 57 if (nodeList[parent].level < nodeList[max].level) 58 { 59 //子節點和父節點進行交換操作 60 var temp = nodeList[parent]; 61 nodeList[parent] = nodeList[max]; 62 nodeList[max] = temp; 63 64 //繼續進行更下一層的過濾 65 parent = max; 66 } 67 else 68 { 69 break; 70 } 71 } 72 } 73 #endregion 最後我還擴充套件了一個彈出並下降節點優先順序的方法,好吧,這個方法大家自己琢磨琢磨,很有意思的,實際應用中使用到了。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Threading; using System.IO; namespace ConsoleApplication2 {     public class Program     {         public static void Main()         {             PriorityQueue heap = new PriorityQueue();

            //隨機插入 20 個節點
            for (int i = 1; i < 20; i++)             {                 var rand = new Random().Next(1, 20);                 Thread.Sleep(10);                 heap.Eequeue(new Url() { Data = "test" + i }, i);             }             while (true)             {                 var node = heap.Dequeue();                 if (node == null)                     break;                 Console.WriteLine("當前 url 的優先順序為:{0}, 資料為:{1}", node.level, node.t.Data);             }             Console.Read();         }     }     #region 定義一個實體     ///

    /// 定義一個實體
    ///

    public class Url
    {
        public string Data { get; set; }
    }
    #endregion

    public class PriorityQueue where T : class
    {
        ///

        /// 定義一個陣列來存放節點
        ///

        private List nodeList = new List();

        #region 堆節點定義
        ///

        /// 堆節點定義
        ///

        public class HeapNode
        {
            ///

            /// 實體資料
            ///

            public T t { get; set; }

            ///

            /// 優先順序別 1-10 個級別 (優先順序別遞增)
            ///

            public int level { get; set; }

            public HeapNode(T t, int level)
            {
                this.t = t;
                this.level = level;
            }

            public HeapNode() { }
        }
        #endregion

        #region  新增操作
        ///

        /// 新增操作
        ///

        public void Eequeue(T t, int level = 1)
        {
            //將當前節點追加到堆尾
            nodeList.Add(new HeapNode(t, level));

            //如果只有一個節點,則不需要進行篩操作
            if (nodeList.Count == 1)
                return;

            //獲取最後一個非葉子節點
            int parent = nodeList.Count / 2 – 1;

            //堆調整
            UpHeapAdjust(nodeList, parent);
        }
        #endregion

        #region 對堆進行上濾操作,使得滿足堆性質
        ///

        /// 對堆進行上濾操作,使得滿足堆性質
        ///

        ///         /// 非葉子節點的之後指標(這裡要注意:我們
        /// 的篩操作時針對非葉節點的)
        ///         public void UpHeapAdjust(List nodeList, int parent)
        {
            while (parent >= 0)
            {
                //當前 index 節點的左孩子
                var left = 2 * parent + 1;

                //當前 index 節點的右孩子
                var right = left + 1;

                //parent 子節點中最大的孩子節點,方便於 parent 進行比較
                //預設為 left 節點
                var max = left;

                //判斷當前節點是否有右孩子
                if (right < nodeList.Count)                 {                     //判斷 parent 要比較的最大子節點                     max = nodeList[left].level < nodeList[right].level ? right : left;                 }                 //如果 parent 節點小於它的某個子節點的話,此時篩操作                 if (nodeList[parent].level < nodeList[max].level)                 {                     //子節點和父節點進行交換操作                     var temp = nodeList[parent];                     nodeList[parent] = nodeList[max];                     nodeList[max] = temp;                     //繼續進行更上一層的過濾                     parent = (int)Math.Ceiling(parent / 2d) - 1;                 }                 else                 {                     break;                 }             }         }         #endregion         #region 優先佇列的出隊操作         ///

        /// 優先佇列的出隊操作
        ///

        ///
        public HeapNode Dequeue()
        {
            if (nodeList.Count == 0)
                return null;

            //出佇列操作,彈出資料頭元素
            var pop = nodeList[0];

            //用尾元素填充頭元素
            nodeList[0] = nodeList[nodeList.Count – 1];

            //刪除尾節點
            nodeList.RemoveAt(nodeList.Count – 1);

            //然後從根節點下濾堆
            DownHeapAdjust(nodeList, 0);

            return pop;
        }
        #endregion

        #region  對堆進行下濾操作,使得滿足堆性質
        ///

        /// 對堆進行下濾操作,使得滿足堆性質
        ///

        ///         /// 非葉子節點的之後指標(這裡要注意:我們
        /// 的篩操作時針對非葉節點的)
        ///         public void DownHeapAdjust(List nodeList, int parent)
        {
            while (2 * parent + 1 < nodeList.Count)             {                 //當前 index 節點的左孩子                 var left = 2 * parent + 1;                 //當前 index 節點的右孩子                 var right = left + 1;                 //parent 子節點中最大的孩子節點,方便於 parent 進行比較                 //預設為 left 節點                 var max = left;                 //判斷當前節點是否有右孩子                 if (right < nodeList.Count)                 {                     //判斷 parent 要比較的最大子節點                     max = nodeList[left].level < nodeList[right].level ? right : left;                 }                 //如果 parent 節點小於它的某個子節點的話,此時篩操作                 if (nodeList[parent].level < nodeList[max].level)                 {                     //子節點和父節點進行交換操作                     var temp = nodeList[parent];                     nodeList[parent] = nodeList[max];                     nodeList[max] = temp;                     //繼續進行更下一層的過濾                     parent = max;                 }                 else                 {                     break;                 }             }         }         #endregion         #region 獲取元素並下降到指定的 level 級別         ///

        /// 獲取元素並下降到指定的 level 級別
        ///

        ///
        public HeapNode GetAndDownPriority(int level)
        {
            if (nodeList.Count == 0)
                return null;

            //獲取頭元素
            var pop = nodeList[0];

            //設定指定優先順序(如果為 MinValue 則為 — 操作)
            nodeList[0].level = level == int.MinValue ? –nodeList[0].level : level;

            //下濾堆
            DownHeapAdjust(nodeList, 0);

            return nodeList[0];
        }
        #endregion

        #region 獲取元素並下降優先順序
        ///

        /// 獲取元素並下降優先順序
        ///

        ///
        public HeapNode GetAndDownPriority()
        {
            //下降一個優先順序
            return GetAndDownPriority(int.MinValue);
        }
        #endregion
    }
}