加载中...

第九题 优先队列


前端时间玩小爬虫的时候,我把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,表示该节点的优先级,也是构建堆时采取的依据。

<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

最后我还扩展了一个弹出并下降节点优先级的方法,好吧,这个方法大家自己琢磨琢磨,很有意思的,实际应用中使用到了。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Diagnostics;
  6. using System.Threading;
  7. using System.IO;
  8. namespace ConsoleApplication2
  9. {
  10. public class Program
  11. {
  12. public static void Main()
  13. {
  14. PriorityQueue<Url> heap = new PriorityQueue<Url>();
  15. //随机插入20个节点
  16. for (int i = 1; i < 20; i++)
  17. {
  18. var rand = new Random().Next(1, 20);
  19. Thread.Sleep(10);
  20. heap.Eequeue(new Url() { Data = "test" + i }, i);
  21. }
  22. while (true)
  23. {
  24. var node = heap.Dequeue();
  25. if (node == null)
  26. break;
  27. Console.WriteLine("当前url的优先级为:{0},数据为:{1}", node.level, node.t.Data);
  28. }
  29. Console.Read();
  30. }
  31. }
  32. #region 定义一个实体
  33. /// <summary>
  34. /// 定义一个实体
  35. /// </summary>
  36. public class Url
  37. {
  38. public string Data { get; set; }
  39. }
  40. #endregion
  41. public class PriorityQueue<T> where T : class
  42. {
  43. /// <summary>
  44. /// 定义一个数组来存放节点
  45. /// </summary>
  46. private List<HeapNode> nodeList = new List<HeapNode>();
  47. #region 堆节点定义
  48. /// <summary>
  49. /// 堆节点定义
  50. /// </summary>
  51. public class HeapNode
  52. {
  53. /// <summary>
  54. /// 实体数据
  55. /// </summary>
  56. public T t { get; set; }
  57. /// <summary>
  58. /// 优先级别 1-10个级别 (优先级别递增)
  59. /// </summary>
  60. public int level { get; set; }
  61. public HeapNode(T t, int level)
  62. {
  63. this.t = t;
  64. this.level = level;
  65. }
  66. public HeapNode() { }
  67. }
  68. #endregion
  69. #region 添加操作
  70. /// <summary>
  71. /// 添加操作
  72. /// </summary>
  73. public void Eequeue(T t, int level = 1)
  74. {
  75. //将当前节点追加到堆尾
  76. nodeList.Add(new HeapNode(t, level));
  77. //如果只有一个节点,则不需要进行筛操作
  78. if (nodeList.Count == 1)
  79. return;
  80. //获取最后一个非叶子节点
  81. int parent = nodeList.Count / 2 - 1;
  82. //堆调整
  83. UpHeapAdjust(nodeList, parent);
  84. }
  85. #endregion
  86. #region 对堆进行上滤操作,使得满足堆性质
  87. /// <summary>
  88. /// 对堆进行上滤操作,使得满足堆性质
  89. /// </summary>
  90. /// <param name="nodeList"></param>
  91. /// <param name="index">非叶子节点的之后指针(这里要注意:我们
  92. /// 的筛操作时针对非叶节点的)
  93. /// </param>
  94. public void UpHeapAdjust(List<HeapNode> nodeList, int parent)
  95. {
  96. while (parent >= 0)
  97. {
  98. //当前index节点的左孩子
  99. var left = 2 * parent + 1;
  100. //当前index节点的右孩子
  101. var right = left + 1;
  102. //parent子节点中最大的孩子节点,方便于parent进行比较
  103. //默认为left节点
  104. var max = left;
  105. //判断当前节点是否有右孩子
  106. if (right < nodeList.Count)
  107. {
  108. //判断parent要比较的最大子节点
  109. max = nodeList[left].level < nodeList[right].level ? right : left;
  110. }
  111. //如果parent节点小于它的某个子节点的话,此时筛操作
  112. if (nodeList[parent].level < nodeList[max].level)
  113. {
  114. //子节点和父节点进行交换操作
  115. var temp = nodeList[parent];
  116. nodeList[parent] = nodeList[max];
  117. nodeList[max] = temp;
  118. //继续进行更上一层的过滤
  119. parent = (int)Math.Ceiling(parent / 2d) - 1;
  120. }
  121. else
  122. {
  123. break;
  124. }
  125. }
  126. }
  127. #endregion
  128. #region 优先队列的出队操作
  129. /// <summary>
  130. /// 优先队列的出队操作
  131. /// </summary>
  132. /// <returns></returns>
  133. public HeapNode Dequeue()
  134. {
  135. if (nodeList.Count == 0)
  136. return null;
  137. //出队列操作,弹出数据头元素
  138. var pop = nodeList[0];
  139. //用尾元素填充头元素
  140. nodeList[0] = nodeList[nodeList.Count - 1];
  141. //删除尾节点
  142. nodeList.RemoveAt(nodeList.Count - 1);
  143. //然后从根节点下滤堆
  144. DownHeapAdjust(nodeList, 0);
  145. return pop;
  146. }
  147. #endregion
  148. #region 对堆进行下滤操作,使得满足堆性质
  149. /// <summary>
  150. /// 对堆进行下滤操作,使得满足堆性质
  151. /// </summary>
  152. /// <param name="nodeList"></param>
  153. /// <param name="index">非叶子节点的之后指针(这里要注意:我们
  154. /// 的筛操作时针对非叶节点的)
  155. /// </param>
  156. public void DownHeapAdjust(List<HeapNode> nodeList, int parent)
  157. {
  158. while (2 * parent + 1 < nodeList.Count)
  159. {
  160. //当前index节点的左孩子
  161. var left = 2 * parent + 1;
  162. //当前index节点的右孩子
  163. var right = left + 1;
  164. //parent子节点中最大的孩子节点,方便于parent进行比较
  165. //默认为left节点
  166. var max = left;
  167. //判断当前节点是否有右孩子
  168. if (right < nodeList.Count)
  169. {
  170. //判断parent要比较的最大子节点
  171. max = nodeList[left].level < nodeList[right].level ? right : left;
  172. }
  173. //如果parent节点小于它的某个子节点的话,此时筛操作
  174. if (nodeList[parent].level < nodeList[max].level)
  175. {
  176. //子节点和父节点进行交换操作
  177. var temp = nodeList[parent];
  178. nodeList[parent] = nodeList[max];
  179. nodeList[max] = temp;
  180. //继续进行更下一层的过滤
  181. parent = max;
  182. }
  183. else
  184. {
  185. break;
  186. }
  187. }
  188. }
  189. #endregion
  190. #region 获取元素并下降到指定的level级别
  191. /// <summary>
  192. /// 获取元素并下降到指定的level级别
  193. /// </summary>
  194. /// <returns></returns>
  195. public HeapNode GetAndDownPriority(int level)
  196. {
  197. if (nodeList.Count == 0)
  198. return null;
  199. //获取头元素
  200. var pop = nodeList[0];
  201. //设置指定优先级(如果为 MinValue 则为 -- 操作)
  202. nodeList[0].level = level == int.MinValue ? --nodeList[0].level : level;
  203. //下滤堆
  204. DownHeapAdjust(nodeList, 0);
  205. return nodeList[0];
  206. }
  207. #endregion
  208. #region 获取元素并下降优先级
  209. /// <summary>
  210. /// 获取元素并下降优先级
  211. /// </summary>
  212. /// <returns></returns>
  213. public HeapNode GetAndDownPriority()
  214. {
  215. //下降一个优先级
  216. return GetAndDownPriority(int.MinValue);
  217. }
  218. #endregion
  219. }
  220. }


还没有评论.