前端时间玩小爬虫的时候,我把url都是放在内存队列里面的,有时我们在抓取url的时候,通过LCS之类的相似度比较,发现某些url是很重要的,
需要后端解析服务器优先处理,针对这种优先级比较大的url,普通的队列还是苦逼的在做FIFO操作,现在我们的需求就是优先级大的优先服务,要做
优先队列,非堆莫属。
一:堆结构
1:性质
堆是一种很松散的序结构树,只保存了父节点和孩子节点的大小关系,并不规定左右孩子的大小,不像排序树那样严格,又因为堆是一种完全二叉
树,设节点为i,则i/2是i的父节点,2i是i的左孩子,2i+1是i的右孩子,所以在实现方式上可以采用轻量级的数组。
2:用途
如果大家玩过微软的MSMQ的话,我们发现它其实也是一个优先队列,还有刚才说的抓取url,不过很遗憾,为什么.net类库中没有优先队列,而java1.5
中就已经支持了。
3:实现
堆结构节点定义:
我们在每个节点上定义一个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
入队操作
入队操作时我们要注意几个问题:
①:完全二叉树的构建操作是“从上到下,从左到右”的形式,所以入队的节点是放在数组的最后,也就是树中叶子层的有序最右边空位。
②:当节点插入到最后时,有可能破坏了堆的性质,此时我们要进行“上滤操作”,当然时间复杂度为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
出队操作
从图中我们可以看出,优先级最大的节点会在一阵痉挛后上升到堆顶,出队操作时,我们采取的方案是:弹出堆顶元素,然后将叶子层中
的最右子节点赋给堆顶,同样这时也会可能存在破坏堆的性质,最后我们要被迫进行下滤操作。
我图中可以看出:首先将堆顶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
最后我还扩展了一个弹出并下降节点优先级的方法,好吧,这个方法大家自己琢磨琢磨,很有意思的,实际应用中使用到了。
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
/// 定义一个实体
///
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
/// 优先队列的出队操作
///
///
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
/// 获取元素并下降到指定的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
}
}
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.e1idc.net