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

