經典算法題每日演練——第九題 優先隊列


 

      前端時間玩小爬蟲的時候,我把url都是放在內存隊列里面的,有時我們在抓取url的時候,通過LCS之類的相似度比較,發現某些url是很重要的,

需要后端解析服務器優先處理,針對這種優先級比較大的url,普通的隊列還是苦逼的在做FIFO操作,現在我們的需求就是優先級大的優先服務,要做

優先隊列,非堆莫屬。

一:堆結構

   1:性質

      堆是一種很松散的序結構樹,只保存了父節點和孩子節點的大小關系,並不規定左右孩子的大小,不像排序樹那樣嚴格,又因為堆是一種完全二叉

樹,設節點為i,則i/2是i的父節點,2i是i的左孩子,2i+1是i的右孩子,所以在實現方式上可以采用輕量級的數組。

2:用途

    如果大家玩過微軟的MSMQ的話,我們發現它其實也是一個優先隊列,還有剛才說的抓取url,不過很遺憾,為什么.net類庫中沒有優先隊列,而java1.5

中就已經支持了。

3:實現

 <1>堆結構節點定義:

       我們在每個節點上定義一個level,表示該節點的優先級,也是構建堆時采取的依據。

 1         /// <summary>
 2         /// 定義一個數組來存放節點
 3         /// </summary>
 4         private List<HeapNode> nodeList = new List<HeapNode>();
 5 
 6         #region 堆節點定義
 7         /// <summary>
 8         /// 堆節點定義
 9         /// </summary>
10         public class HeapNode
11         {
12             /// <summary>
13             /// 實體數據
14             /// </summary>
15             public T t { get; set; }
16 
17             /// <summary>
18             /// 優先級別 1-10個級別 (優先級別遞增)
19             /// </summary>
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         /// <summary>
 3         /// 添加操作
 4         /// </summary>
 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         /// <summary>
24         /// 對堆進行上濾操作,使得滿足堆性質
25         /// </summary>
26         /// <param name="nodeList"></param>
27         /// <param name="index">非葉子節點的之后指針(這里要注意:我們
28         /// 的篩操作時針對非葉節點的)
29         /// </param>
30         public void UpHeapAdjust(List<HeapNode> 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         /// <summary>
 3         /// 優先隊列的出隊操作
 4         /// </summary>
 5         /// <returns></returns>
 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         /// <summary>
29         /// 對堆進行下濾操作,使得滿足堆性質
30         /// </summary>
31         /// <param name="nodeList"></param>
32         /// <param name="index">非葉子節點的之后指針(這里要注意:我們
33         /// 的篩操作時針對非葉節點的)
34         /// </param>
35         public void DownHeapAdjust(List<HeapNode> 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

最后我還擴展了一個彈出並下降節點優先級的方法,好吧,這個方法大家自己琢磨琢磨,很有意思的,實際應用中使用到了。

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<Url> heap = new PriorityQueue<Url>();

            //隨機插入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 定義一個實體
    /// <summary>
    /// 定義一個實體
    /// </summary>
    public class Url
    {
        public string Data { get; set; }
    }
    #endregion

    public class PriorityQueue<T> where T : class
    {
        /// <summary>
        /// 定義一個數組來存放節點
        /// </summary>
        private List<HeapNode> nodeList = new List<HeapNode>();

        #region 堆節點定義
        /// <summary>
        /// 堆節點定義
        /// </summary>
        public class HeapNode
        {
            /// <summary>
            /// 實體數據
            /// </summary>
            public T t { get; set; }

            /// <summary>
            /// 優先級別 1-10個級別 (優先級別遞增)
            /// </summary>
            public int level { get; set; }

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

            public HeapNode() { }
        }
        #endregion

        #region  添加操作
        /// <summary>
        /// 添加操作
        /// </summary>
        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 對堆進行上濾操作,使得滿足堆性質
        /// <summary>
        /// 對堆進行上濾操作,使得滿足堆性質
        /// </summary>
        /// <param name="nodeList"></param>
        /// <param name="index">非葉子節點的之后指針(這里要注意:我們
        /// 的篩操作時針對非葉節點的)
        /// </param>
        public void UpHeapAdjust(List<HeapNode> 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 優先隊列的出隊操作
        /// <summary>
        /// 優先隊列的出隊操作
        /// </summary>
        /// <returns></returns>
        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  對堆進行下濾操作,使得滿足堆性質
        /// <summary>
        /// 對堆進行下濾操作,使得滿足堆性質
        /// </summary>
        /// <param name="nodeList"></param>
        /// <param name="index">非葉子節點的之后指針(這里要注意:我們
        /// 的篩操作時針對非葉節點的)
        /// </param>
        public void DownHeapAdjust(List<HeapNode> 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級別
        /// <summary>
        /// 獲取元素並下降到指定的level級別
        /// </summary>
        /// <returns></returns>
        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 獲取元素並下降優先級
        /// <summary>
        /// 獲取元素並下降優先級
        /// </summary>
        /// <returns></returns>
        public HeapNode GetAndDownPriority()
        {
            //下降一個優先級
            return GetAndDownPriority(int.MinValue);
        }
        #endregion
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM