前端時間玩小爬蟲的時候,我把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 } }