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

简介:

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

/// <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

<2> 入队操作

      入队操作时我们要注意几个问题:

     ①:完全二叉树的构建操作是“从上到下,从左到右”的形式,所以入队的节点是放在数组的最后,也就是树中叶子层的有序最右边空位。

     ②:当节点插入到最后时,有可能破坏了堆的性质,此时我们要进行“上滤操作”,当然时间复杂度为O(lgN)。

当我将节点“20”插入到堆尾的时候,此时破坏了堆的性质,从图中我们可以清楚的看到节点“20”的整个上滤过程,有意思吧,还有一点

就是:获取插入节点的父亲节点的算法是:parent=list.count/2-1。这也得益于完全二叉树的特性。

#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

<3> 出队操作

       从图中我们可以看出,优先级最大的节点会在一阵痉挛后上升到堆顶,出队操作时,我们采取的方案是:弹出堆顶元素,然后将叶子层中

的最右子节点赋给堆顶,同样这时也会可能存在破坏堆的性质,最后我们要被迫进行下滤操作。

我图中可以看出:首先将堆顶20弹出,然后将7赋给堆顶,此时堆性质遭到破坏,最后我们清楚的看到节点7的下滤过程,从摊还分析的角度上

来说,下滤的层数不超过2-3层,所以整体上来说出队的时间复杂度为一个常量O(1)。

#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

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

相关文章
|
算法
带你读《图解算法小抄》五、优先队列
带你读《图解算法小抄》五、优先队列
|
2月前
|
运维 监控 算法
企业局域网监控软件中 Java 优先队列算法的核心优势
企业局域网监控软件是数字化时代企业网络安全与高效运营的基石,犹如一位洞察秋毫的卫士。通过Java实现的优先队列算法,它能依据事件优先级排序,确保关键网络事件如异常流量、数据泄露等被优先处理,保障系统稳定与安全。代码示例展示了如何定义网络事件类并使用PriorityQueue处理高优先级事件,尤其在面对疑似风险时迅速启动应急措施。这一核心技术助力企业在复杂网络环境中稳健前行,护航业务腾飞。
66 32
|
9月前
|
算法 C++
数据结构与算法===优先队列
数据结构与算法===优先队列
数据结构与算法===优先队列
|
6月前
|
大数据 UED 开发者
实战演练:利用Python的Trie树优化搜索算法,性能飙升不是梦!
在数据密集型应用中,高效搜索算法至关重要。Trie树(前缀树/字典树)通过优化字符串处理和搜索效率成为理想选择。本文通过Python实战演示Trie树构建与应用,显著提升搜索性能。Trie树利用公共前缀减少查询时间,支持快速插入、删除和搜索。以下为简单示例代码,展示如何构建及使用Trie树进行搜索与前缀匹配,适用于自动补全、拼写检查等场景,助力提升应用性能与用户体验。
91 2
|
8月前
|
算法 安全 调度
逆天改命!Python高级数据结构堆(Heap)与优先队列,让你的算法效率飙升至宇宙级!
【7月更文挑战第8天】Python的heapq模块和queue.PriorityQueue实现了堆和优先队列,提供高效算法解决方案。堆用于Dijkstra算法求解最短路径,例如在图论问题中;PriorityQueue则在多线程下载管理中确保高优先级任务优先执行。这两个数据结构提升效率,简化代码,是编程中的强大工具。
90 0
|
9月前
|
算法
详尽分享经典算法题每日演练——第一题百钱买百鸡
详尽分享经典算法题每日演练——第一题百钱买百鸡
65 0
|
9月前
|
算法 Java 调度
Java数据结构与算法:优先队列
Java数据结构与算法:优先队列
|
9月前
|
算法 C++
【洛谷 P1090】[NOIP2004 提高组] 合并果子(贪心算法+哈夫曼编码+优先队列)
该编程题目要求设计算法,将不同种类的果子合并成一堆,使得消耗的体力最小。给定果子种类数`n`(1至10000)和每种果子的数量,需输出合并的最小体力值。使用优先队列(最小堆),每次取出两个数量最少的果子堆合并,并更新总体力消耗。样例输入为3种果子(1, 2, 9),输出最小体力耗费为15。提供的AC代码采用C++实现,通过优先队列优化合并顺序。
131 0
|
9月前
|
存储 算法 Java
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
|
10月前
|
存储 搜索推荐 Java
【数据结构排序算法篇】----桶排序【实战演练】
【数据结构排序算法篇】----桶排序【实战演练】
107 5