优先队列「建议收藏」

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

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

优先队列  

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

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

优先队列「建议收藏」

 

 

 (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]); } } } }

 

 

 

 

 

 

 

 

 

 

  

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • linux 渗透工具_适用于Linux的十大最佳渗透测试工具[通俗易懂]

    linux 渗透工具_适用于Linux的十大最佳渗透测试工具[通俗易懂]linux渗透工具ThisarticlecoverssomeofthebestpenetrationtestingtoolsforLinuxCybersecurityisabigconcernforbothsmallandbigorganizations.Inanagewheremoreandmorebusinessesaremov…

  • MySQL字符与数字转换_MySQL字符与数字间的转换「建议收藏」

    MySQL字符与数字转换_MySQL字符与数字间的转换「建议收藏」MYSQL字符数字转换1.将字符的数字转成数字,比如’0’转成0可以直接用加法来实现例如:将pony表中的d进行排序,可d的定义为varchar,可以这样解决select*fromponyorderby(d+0)2.在进行ifnull处理时,比如ifnull(a/b,’0′)这样就会导致a/b成了字符串,因此需要把’0’改成0,即可解决此困扰。3.比较数字和varchar…

  • 手动清除fun.xls.exe病毒的方法[通俗易懂]

    手动清除fun.xls.exe病毒的方法[通俗易懂](无法显示隐藏文件以及无法双击打开分区)用杀毒软件杀毒,所有驱动盘上的文件夹表现为不可见,实际为文件夹隐藏了。如何判断是中了该种病毒,可以通过在命令行下键入:cdC:’dir/ah如果有fun.x

  • 两位数乘法速算(无意中发现)

    两位数乘法速算(无意中发现)比如目前计算12*34=?现在拿ab*cd=?举例子步骤:就是b*d的个位数放在?的个位上。。。。。。。。。。。。。。。。。①然后如果bd有十位那么先记下来(心里默记)。。。。。。。。。。。②然后计算bc+a*d+②结果得到的个位数写在①前面。。。。。。。。③然后把上一步剩下的结果除了个位数以后的保留下来。。。。。。。。。④然后…

  • vagrant系列三:vagrant搭建的php7环境

    vagrant系列三:vagrant搭建的php7环境

  • JAVA队列( Queue ) 详解[通俗易懂]

    JAVA队列( Queue ) 详解[通俗易懂]什么是队列?队列是一种特殊的线性表,遵循先入先出、后入后出的基本原则,一般来说,它只允许在表的前端进行删除操作,而在表的后端进行插入操作,但是java的某些队列运行在任何地方插入删除;比如我们常用的LinkedList集合,它实现了Queue接口,因此,我们可以理解为LinkedList就是一个队列;java队列特性队列主要分为阻塞和非阻塞,有界和无界、单向链表和双向链表之分;阻塞和非阻塞阻塞队列入列(删除元素)时,如果元素数量超过队列总数…

发表回复

您的电子邮箱地址不会被公开。

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