首页 > 代码库 > 经典算法题每日演练——第九题 优先队列

经典算法题每日演练——第九题 优先队列

原文:经典算法题每日演练——第九题 优先队列

 

      前端时间玩小爬虫的时候,我把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 HeapNode11         {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         #endregion21 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                 else63                 {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         #endregion26 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                 else68                 {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 = "http://www.mamicode.com/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    }}

技术分享

经典算法题每日演练——第九题 优先队列