优先队列「建议收藏」

优先队列「建议收藏」优先队列比如现实生活中的排队,就符合这种先进先出的队列形式,但是像急诊医院排队,就不可能按照先到先治疗的规则,所以需要使用优先队列。实现优先队列其实都是基于下面这些实现的:可以看出来实现优先队列最

大家好,又见面了,我是你们的朋友全栈君。

优先队列  

比如现实生活中的排队,就符合这种先进先出的队列形式,但是像急诊医院排队,就不可能按照先到先治疗的规则,所以需要使用优先队列。

实现优先队列其实都是基于下面这些实现的:可以看出来实现优先队列最好的方式就是二叉堆。

优先队列「建议收藏」

 

 

 (1)二叉堆本质上是一种完全二叉树 

比如下面2棵树,左边的树是完全二叉树,右边不是,因为没有连续集中在左侧。定义1的意思是指

优先队列「建议收藏」

二叉堆的定义:

优先队列「建议收藏」

 

用数组来存储堆:如下图,父节点左孩子节点是本身索引的2倍,右孩子节点的索引是本身节点的2倍+1,这样只要知道其中一个节点的信息,就能迅速知道父节点或对应孩子节点的信息了。

优先队列「建议收藏」

 

最大堆

二叉堆分为2个类型,最大堆和最小堆,对于最大堆:最大堆任何一个父节点的值,都大于等于它左右孩子节点的值。

所以在插入新值68的时候,首先要满足作为完全二叉树的条件,就是最下层节点必须连续集中在左侧,所以放在11的位置,如下图:

优先队列「建议收藏」

如果这个二叉堆是最大堆,那么需要元素上游,和对应父节点进行比较,结果如下图:

优先队列「建议收藏」

如果我们要删除其中一个元素,比如根节点70,那我们需要将70和最下层的最右边的一个节点进行交换,然后删除70,如下:

 

优先队列「建议收藏」

 

但是此时明显不符合最大堆的定义,所以进行元素下沉:55和65都比28要大,但是我们选择和65交换,因为要满足最大堆的定义,这是风险最小的选择。

优先队列「建议收藏」

 

 

 

 代码实现:

namespace DataStructure
{
    /// <summary>
    /// 数组堆
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class MaxHeap<E> where E : IComparable<E>
    {

        private E[] heap;
        private int N;
        public MaxHeap(int capacity)
        {
            //之所以加1,是因为索引为0的地方是没有存值的。
            heap = new E[capacity + 1];
        }

        public MaxHeap() : this(10)
        {

        }

        public int Count { get { return N; } }
        public bool IsEmpty { get { return N == 0; } }

        public void Insert(E e)
        {
            //之所以减一,是因为二叉堆中的具体数是在索引1处开始。
            if (N == heap.Length - 1)
            {
                ResetCapacity(heap.Length * 2);
            }

            //首先保证此树是完全二叉树。
            heap[N + 1] = e;
            N++;
            Swim(N);
        }
        /// <summary>
        /// 元素上游
        /// </summary>
        /// <param name="k"></param>
        private void Swim(int k)
        {
            //k==1时代表找到了根节点,所以不用再比较了,不能上游了
            while (k > 1 && heap[k].CompareTo(heap[k / 2]) > 0)
            {
                Swap(k, k / 2);
                k = k / 2;
            }

        }

        private void Swap(int i, int j)
        {
            E e = heap[i];
            heap[i] = heap[j];
            heap[j] = e;
        }
        //删除最大元素
        public E RemoveMax()
        {
            if (IsEmpty)
                throw new Exception("堆为空");
            Swap(1, N);
            E max = heap[N];
            //设置默认值,让垃圾回收器GC进行回收。
            heap[N] = default;
            N--;
            Sink(1);
            if (N == (heap.Length - 1) / 4)
                ResetCapacity(heap.Length / 2);
            return max;
        }
        /// <summary>
        ///返回最大值
        /// </summary>
        /// <returns></returns>
        public E Max()
        {
            if (IsEmpty)
                throw new Exception("堆为空");
            return heap[1];
        }

        /// <summary>
        /// 元素下沉
        /// </summary>
        /// <param name="k"></param>
        private void Sink(int k)
        {
            //当前存在孩子节点的时候才能往下比较
            while (2 * k <= N)
            {
                int j = 2 * k;
                //j+1<=N表示存在右孩子节点,
                //如果存在右孩子节点,并且右孩子节点比左孩子节点大
                if (j + 1 <= N && heap[j + 1].CompareTo(heap[j]) > 0)
                {
                    //右孩子的位置
                    j++;
                }
                //如果当前节点大于右孩子节点,则跳出
                if (heap[k].CompareTo(heap[j]) >= 0)
                {
                    break;
                }

                Swap(k, j);
                //j代表交换完成之后元素所在的新的位置,赋给k,看看这个位置是否需要继续交换
                k = j;
            }

        }

        /// <summary>
        /// 数组扩容
        /// </summary>
        /// <param name="newLength"></param>
        private void ResetCapacity(int newLength)
        {
            E[] newData = new E[newLength];
            //将旧有数据复制到扩容后的新数组中
            heap.CopyTo(newData, 0);
            //赋值给原有数组 注意此时给data的是引用,data中数据更改newData同样会变
            heap = newData;
        }
        /// <summary>
        /// 重写输出方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("[");
            for (int i = 1; i <=N; i++)
            {
                stringBuilder.Append(heap[i]);
                if (i != N)
                    stringBuilder.Append(",");
            }
            stringBuilder.Append("]");
            return stringBuilder.ToString();
        }




    }
}

 

 调用:

按照二叉堆最大堆的定义,实现结果应为:

优先队列「建议收藏」

  class Program
    {
        static void Main(string[] args)
        {  
            MaxHeap<int> maxHeap = new MaxHeap<int>(); 
            int[] arr = { 3,2,1,5,4};  
            for (int i = 0; i < arr.Length; i++)
            {
                maxHeap.Insert(arr[i]);
                Console.WriteLine(maxHeap);
            }

            maxHeap.RemoveMax(); 
            Console.WriteLine(maxHeap);

        }
    }

 

结果:

[3]
[3,2]
[3,2,1]
[5,3,1,2]
[5,4,1,2,3]
[4,3,1,2]

 和预期一致。

 

最小堆

 最小堆任何一个父节点的值,都小于等于它左右孩子节点的值。

 

堆排序

 堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了(一般升序采用大顶堆,降序采用小顶堆)。

代码如下:

    class HeapSort1
    {
        public static void Sort(int[] arr)
        {
            int n = arr.Length;
            MaxHeap<int> maxHeap = new MaxHeap<int>(n);
            for (int i = 0; i < n; i++)
            {
                maxHeap.Insert(arr[i]);
            }
            //要想实现正序,那么最大值就是放后面,所以i--;
            for (int i = n - 1; i >= 0; i--)
            {
                arr[i] = maxHeap.RemoveMax();
            }  
        } 
    }

 

但是,当前代码的性能不是很好,他的时间复杂度:建堆(nlogn)+出堆(nlogn)=O(2nlogn),空间复杂度为O(n)

可以使用原地堆排序进行优化。

原地堆排序

如下图,符合完全二叉树,但是不符合最大堆定义。 图中的橙色节点都是叶子节点,我们可以从第一个非叶子节点开始进行考察,其中n代表数组的个数。

 优先队列「建议收藏」

 

 

class HeapSort2
    {
        public static void Sort(int[] arr)
        {
            int n = arr.Length;
            MaxHeap<int> maxHeap = new MaxHeap<int>(n);
            for (int i = 0; i < n; i++)
            {
                maxHeap.Insert(arr[i]);
            }
            //因为要从第一个非叶子节点开始,并且期间需要元素下沉来实现最大堆。
            for (int i = (n - 1 - 1) / 2; i >= 0; i--)
            {
                Sink(arr, i, n - 1);
            }
            //原地堆排序
            for (int i = n - 1; i >= 0; i--)
            {
                //交换元素
                Swap(arr, 0, i); 
                //交换之后再次比较
                Sink(arr, 0, i - 1); 
            } 
        }

        /// <summary>
        /// 元素下沉
        /// </summary>
        /// <param name="k"></param>
        private static void Sink(int[] arr, int k, int N)
        {
            //当前存在孩子节点的时候才能往下比较
            while (2 * k + 1 <= N)
            {
                int j = 2 * k + 1;
                //j+1<=N表示存在右孩子节点,
                //如果存在右孩子节点,并且右孩子节点比左孩子节点大
                if (j + 1 <= N && arr[j + 1].CompareTo(arr[j]) > 0)
                {
                    //右孩子的位置
                    j++;
                }
                //如果当前节点大于右孩子节点,则跳出
                if (arr[k].CompareTo(arr[j]) >= 0)
                {
                    break;
                }

                Swap(arr, k, j);
                //j代表交换完成之后元素所在的新的位置,赋给k,看看这个位置是否需要继续交换
                k = j;
            }

        }
        private static void Swap(int[] arr, int i, int j)
        {
            int e = arr[i];
            arr[i] = arr[j];
            arr[j] = e;
        }


    }

 

 

优先队列

基于最大堆实现最大优先队列

完整代码:

最大最小数组堆:

namespace DataStructure
{
    /// <summary>
    /// 最大数组堆
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class MaxHeap<E> where E : IComparable<E>
    {

        private E[] heap;
        private int N;
        public MaxHeap(int capacity)
        {
            //之所以加1,是因为索引为0的地方是没有存值的。
            heap = new E[capacity + 1];
            N = 0;
        }

        public MaxHeap() : this(10)
        {
        }
        public int Count { get { return N; } }
        public bool IsEmpty { get { return N == 0; } }

        public void Insert(E e)
        {
            //之所以减一,是因为二叉堆中的具体数是在索引1处开始。
            if (N == heap.Length - 1)
            {
                ResetCapacity(heap.Length * 2);
            }

            //首先保证此树是完全二叉树。
            heap[N + 1] = e;
            N++;
            Swim(N);
        }
        /// <summary>
        /// 元素上游
        /// </summary>
        /// <param name="k"></param>
        private void Swim(int k)
        {
            //k==1时代表找到了根节点,所以不用再比较了,不能上游了
            while (k > 1 && heap[k].CompareTo(heap[k / 2]) > 0)
            {
                Swap(k, k / 2);
                k = k / 2;
            }

        }

        private void Swap(int i, int j)
        {
            E e = heap[i];
            heap[i] = heap[j];
            heap[j] = e;
        }
        //删除最大元素
        public E RemoveMax()
        {
            if (IsEmpty)
                throw new Exception("堆为空");
            Swap(1, N);
            E max = heap[N];
            //设置默认值,让垃圾回收器GC进行回收。
            heap[N] = default;
            N--;
            Sink(1);
            if (N == (heap.Length - 1) / 4)
                ResetCapacity(heap.Length / 2);
            return max;
        }
        /// <summary>
        ///返回最大值
        /// </summary>
        /// <returns></returns>
        public E Max()
        {
            if (IsEmpty)
                throw new Exception("堆为空");
            return heap[1];
        }

        /// <summary>
        /// 元素下沉
        /// </summary>
        /// <param name="k"></param>
        private void Sink(int k)
        {
            //当前存在孩子节点的时候才能往下比较
            while (2 * k <= N)
            {
                int j = 2 * k;
                //j+1<=N表示存在右孩子节点,
                //如果存在右孩子节点,并且右孩子节点比左孩子节点大
                if (j + 1 <= N && heap[j + 1].CompareTo(heap[j]) > 0)
                {
                    //右孩子的位置
                    j++;
                }
                //如果当前节点大于右孩子节点,则跳出
                if (heap[k].CompareTo(heap[j]) >= 0)
                {
                    break;
                }

                Swap(k, j);
                //j代表交换完成之后元素所在的新的位置,赋给k,看看这个位置是否需要继续交换
                k = j;
            }

        }

        /// <summary>
        /// 数组扩容
        /// </summary>
        /// <param name="newLength"></param>
        private void ResetCapacity(int newLength)
        {
            E[] newData = new E[newLength];
            //将旧有数据复制到扩容后的新数组中
            heap.CopyTo(newData, 0);
            //赋值给原有数组 注意此时给data的是引用,data中数据更改newData同样会变
            heap = newData;
        }
        /// <summary>
        /// 重写输出方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("[");
            for (int i = 1; i <= N; i++)
            {
                stringBuilder.Append(heap[i]);
                if (i != N)
                    stringBuilder.Append(",");
            }
            stringBuilder.Append("]");
            return stringBuilder.ToString();
        }




    }
     
    /// <summary>
    /// 最小数组堆
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class MinHeap<E> where E : IComparable<E>
    {

        private E[] heap;
        private int N;
        public MinHeap(int capacity)
        {
            //之所以加1,是因为索引为0的地方是没有存值的。
            heap = new E[capacity + 1];
            N = 0;
        }

        public MinHeap() : this(10)
        {
        }
        public int Count { get { return N; } }
        public bool IsEmpty { get { return N == 0; } }

        public void Insert(E e)
        {
            //之所以减一,是因为二叉堆中的具体数是在索引1处开始。
            if (N == heap.Length - 1)
            {
                ResetCapacity(heap.Length * 2);
            }

            //首先保证此树是完全二叉树。
            heap[N + 1] = e;
            N++;
            Swim(N);
        }
        /// <summary>
        /// 元素上游
        /// </summary>
        /// <param name="k"></param>
        private void Swim(int k)
        {
            //k==1时代表找到了根节点,所以不用再比较了,不能上游了
            while (k > 1 && heap[k].CompareTo(heap[k / 2]) < 0)
            {
                Swap(k, k / 2);
                k = k / 2;
            }

        }

        private void Swap(int i, int j)
        {
            E e = heap[i];
            heap[i] = heap[j];
            heap[j] = e;
        }
        //删除最小元素
        public E RemoveMin()
        {
            if (IsEmpty)
                throw new Exception("堆为空");
            Swap(1, N);
            E max = heap[N];
            //设置默认值,让垃圾回收器GC进行回收。
            heap[N] = default;
            N--;
            Sink(1);
            if (N == (heap.Length - 1) / 4)
                ResetCapacity(heap.Length / 2);
            return max;
        }
        /// <summary>
        ///返回最小值
        /// </summary>
        /// <returns></returns>
        public E Min()
        {
            if (IsEmpty)
                throw new Exception("堆为空");
            return heap[1];
        }

        /// <summary>
        /// 元素下沉
        /// </summary>
        /// <param name="k"></param>
        private void Sink(int k)
        {
            //当前存在孩子节点的时候才能往下比较
            while (2 * k <= N)
            {
                int j = 2 * k;
                //j+1<=N表示存在右孩子节点,
                //如果存在右孩子节点,并且右孩子节点比左孩子节点小
                if (j + 1 <= N && heap[j + 1].CompareTo(heap[j]) < 0)
                {
                    //右孩子的位置
                    j++;
                }
                //如果当前节点小于右孩子节点,则跳出
                if (heap[k].CompareTo(heap[j]) <= 0)
                {
                    break;
                }

                Swap(k, j);
                //j代表交换完成之后元素所在的新的位置,赋给k,看看这个位置是否需要继续交换
                k = j;
            }

        }

        /// <summary>
        /// 数组扩容
        /// </summary>
        /// <param name="newLength"></param>
        private void ResetCapacity(int newLength)
        {
            E[] newData = new E[newLength];
            //将旧有数据复制到扩容后的新数组中
            heap.CopyTo(newData, 0);
            //赋值给原有数组 注意此时给data的是引用,data中数据更改newData同样会变
            heap = newData;
        }
        /// <summary>
        /// 重写输出方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("[");
            for (int i = 1; i <= N; i++)
            {
                stringBuilder.Append(heap[i]);
                if (i != N)
                    stringBuilder.Append(",");
            }
            stringBuilder.Append("]");
            return stringBuilder.ToString();
        }
 
    }
}

 

最大最小优先队列:

namespace DataStructure
{
    /// <summary>
    /// 最大优先队列  IQueue:是自定义的一个接口
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class MaxPQ<E> : IQueue<E> where E : IComparable<E>
    {
        private MaxHeap<E> heap;

        public int Count { get { return heap.Count; } }

        public bool IsEmpty { get { return heap.IsEmpty; } }

        public MaxPQ(int capacity)
        {
            heap = new MaxHeap<E>(capacity);
        }
        public MaxPQ()
        {
            heap = new MaxHeap<E>();
        }

        public void Enqueue(E e)
        {
            heap.Insert(e);
        }

        public E Dequeue()
        {
            return heap.RemoveMax();
        }

        public E Peek()
        {
            return heap.Max();
        }
    }

    /// <summary>
    /// 最小优先队列  
    /// </summary>
    /// <typeparam name="E"></typeparam>
    class MinPQ<E> : IQueue<E> where E : IComparable<E>
    {
        private MinHeap<E> heap;

        public int Count { get { return heap.Count; } }

        public bool IsEmpty { get { return heap.IsEmpty; } }

        public MinPQ(int capacity)
        {
            heap = new MinHeap<E>(capacity);
        }
        public MinPQ()
        {
            heap = new MinHeap<E>();
        }

        public void Enqueue(E e)
        {
            heap.Insert(e);
        }

        public E Dequeue()
        {
            return heap.RemoveMin();
        }

        public E Peek()
        {
            return heap.Min();
        }
    }
}

 

 

 

(1)在一百万个元素中找出前10个最小的元素。

最好用优先队列,因为其他那些排序方法需要把1百万个数据先放到内存中才能进行排序,通过优先队列,来一个数据,就处理一个,不需要那么多的内存,只需要开辟10个内存来储存即可。

我们可以把数据规模缩小来进行测试,如下:

优先队列「建议收藏」

 

我们可以先找出3个元素,然后将剩余的元素和选出来的元素进行比较,如果存在比这3个元素小的,则替换,否则继续比较。

   class Program
    {

        static void Main(string[] args)
        {
            //找最小的三个数
            MaxPQ<int> maxHeap = new MaxPQ<int>(3);
            int[] arr = { 3, 2, 1, 5, 4 };
            for (int i = 0; i < arr.Length; i++)
            {
                if (maxHeap.Count < 3)
                {
                    maxHeap.Enqueue(arr[i]);
                }
                //如果当前数比最大数还小,则去除最大数,
                else if (arr[i] < maxHeap.Peek())
                {
                    //去除队列中最大的数
                    maxHeap.Dequeue();
                    maxHeap.Enqueue(arr[i]);
                }

            }

        }
    }

 

 

 

 

 

 

 

 

 

 

  

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/155691.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • SQL——coalesce函数详解「建议收藏」

    SQL——coalesce函数详解「建议收藏」最近写SQL的过程中,学习到一个非常有用的函数:coalesce。特别是在做统计的时候,这个函数作为条件可以兼顾到一些特殊情况。这里做一下总结和分享。用途:(1):将控制替换成其他值;(2):返回第一个非空值表达式COALESCE是一个函数,(expression_1,expression_2,…,expression_n)依次参考各参数表达式,遇到非null值即停止并返…

    2022年8月20日
    8
  • Matlab fmincon函数用法

    Matlab fmincon函数用法这个函数在之前优化工具箱一文中已经介绍过,由于其应用广泛,所以这里通过实例单独整理一下其用法。一、基本介绍求解问题的标准型为minF(X)s.tAX<=bAeqX=beqG(x)<=0Ceq(X)=0VLB<=X<=VUB其中X为n维变元向量,G(x)与Ceq(X)均为非线性函数组成的向量,其它变量的含…

    2022年4月30日
    50
  • UpdatePanel的简单用法(非嵌套)「建议收藏」

    UpdatePanel的简单用法(非嵌套)「建议收藏」ScriptManager和UpdatePanel控件联合使用可以实现页面局部异步刷新的效果。UpdatePanel用来设置页面中局部异步刷新的区域,它必须依赖于ScriptManager,因为ScriptManager控件提供了客户端脚本生成与管理UpdatePanel的功能。

    2022年7月23日
    9
  • 数组元素的下标超出所定义的_数组元素的下标超出所定义的

    数组元素的下标超出所定义的_数组元素的下标超出所定义的问题错误信息:数组成员引用下标超出定义范围原因使用数组成员的时候,下标超出了数组最大个数。解决方法仅用于自己编写程序,所以如果是别人做好的程序,运行出现错误,你又没代码的话那就没用了。解决思路就是正确使用数组下标,不要超过数组最大成员数。下面是两种笨方法:方法一在使用数组成员的时候,检查数组的最大成员数。例如:如果真(取数组成员数(数组名)>0)确定数组有成员,之后再引用。方法二菜单的工具-系统配置-编译,勾选“是否启用快速数组访问方式”。(调试时仍然会

    2022年10月19日
    1
  • 编程开发工具一览:新手到大神,程序员都用什么写代码?「建议收藏」

    编程开发工具一览:新手到大神,程序员都用什么写代码?「建议收藏」俗话说的好:工欲善其事,必先利其器。一款好的开发工具对程序员来说是至关重要的,可以降低开发成本、提高开发的效率和代码质量。所以今天分享一些主流的编程开发工具,基本都是我曾经或正在使用的,附带一些使用感受。编程开发工具一览本文大纲:本地编辑器Notepad其实就是Windows系统自带的记事本啦,致敬经典!别小瞧记事本,其实它也能作为一款最原始最纯洁的代码编辑器来使用。比如我初学前端时,就用记事本编辑网页代码,然后在保存文件时修改后缀为.html,双击就能运行了。正因为..

    2022年5月29日
    83
  • 宇宙简史|生物学家也要了解的物理

    宇宙简史|生物学家也要了解的物理本文转载自"环球地理志",己获授权“盘古开天辟地”天地混沌如鸡子盘古生其中万八千岁、天地开辟阳清为天、阴浊为地盘古在其中一日九变神于天、圣于地▼◤围绕北落师门的圆盘状宇…

    2022年5月8日
    76

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号