优先级队列(堆)  堆排序

编程入门 行业动态 更新时间:2024-10-03 06:37:28

<a href=https://www.elefans.com/category/jswz/34/1769791.html style=优先级队列(堆)  堆排序"/>

优先级队列(堆)  堆排序

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。 JDK1.8中的PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整!

堆的概念!

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质!
  1. 堆中某个节点的值,总是不大于或者不小于其父节点

  1. 堆总是一颗完全二叉树

小根堆(最小堆)

大根堆(最大堆)

从堆的概念,我们可以知道,堆是一颗完全二叉树,因此可以层序的规则,采用顺序的方式来高校存储!!

接下来,我们来思考一下:对于集合{27,15,19,18,28,34,65,49,25,37}中的数据,如何将其创建成堆呢??请看笔者接下来的代码:

public class TestHeap {public int[] elem;public int usedSize;public TestHeap(){this.elem=new int[10];}public void initElem(int[] array){for (int i = 0; i < array.length; i++) {elem[i]=array[i];usedSize++;}}public void createHeap(){for (int parent=(usedSize-1-1)/2;parent>=0;parent--){shiftDown(parent,usedSize);}}//通过向下调整的方式建立大根堆private void shiftDown(int parent,int len){int child=2*parent+1;//最起码要有左孩子while (child<len){//一定有左孩子的情况下if (child<len&&elem[child]<elem[child+1]){child++;}//child下标一定是左右孩子最大值的下标if (elem[child]>elem[parent]){int tmp=elem[child];elem[child]=elem[parent];elem[parent]=tmp;parent=child;child=2*parent+1;}else {break;}}}
}

那么,有了上述的方法,我们在main 方法中,有着:

  public static void main(String[] args) {TestHeap testHeap=new TestHeap();int[] array={27,15,19,18,28,34,65,49,25,37};testHeap.initElem(array);testHeap.createHeap();}

对于上述代码,若有不懂得,调试即可!!

另外,上述代码,最后建立的是大根堆!!若是想要建立小根堆,则改改代码即可(大于小于号的更改)

有了上述的基础,那么我们可以来根深入的了解一下:堆的插入!!

堆的插入!

在进行堆的插入之前,我们肯定会有一个堆,假设,我们已经有了一个堆,并且还是大根堆!!

当我们想要插入80,则放到当前这棵树的最后位置!!

放到最后位置处,我们还要再次调整为大根堆(向上调整)

通过比较当前插入的数据与根节点的大小,若比根节点大,则进行交换,然后在接着与根节点进行比较!!一直到小于根节点或者没有可比较的元素的时候停止!

在这里,我们主要进行的是:插入元素(向上调整)

    public void offer(int val){if (isFull()){elem= Arrays.copyOf(elem,2*elem.length);//扩容}elem[usedSize++]=val;shiftUp(usedSize-1);}public boolean isFull(){return usedSize== elem.length;}private void shiftUp(int child){int parent=(child-1)/2;while (child>0){if (elem[child]>elem[parent]){int tmp=elem[child];elem[child]=elem[parent];elem[parent]=tmp;child=parent;parent=(child-1)/2;}else {break;}}}

经过上述的代码,我们可以看出:堆的插入总共需要两个步骤:

  1. 先将元素放入到底层空间中(注意:空间不够时需要扩容) 

  1. 将最后新插入的节点向上调整,直到满足堆的性质

接下来,我们进入:堆的删除环节!

堆的删除!

注意:堆的删除一定删除的是堆顶元素。具体如下: 1. 将堆顶元素对堆中最后一个元素交换 2. 将堆中有效数据个数减少一个 3. 对堆顶元素进行向下调整

    public void pop(){if (isEmpty()){return;}swap(elem,0,usedSize-1);usedSize--;shiftDown(0,usedSize);}public boolean isEmpty(){return usedSize==0;}private void swap(int[] array,int i,int j){int tmp=array[i];array[i]=array[j];array[j]=tmp;}//通过向下调整的方式建立大根堆private void shiftDown(int parent,int len){int child=2*parent+1;//最起码要有左孩子while (child<len){//一定有左孩子的情况下if (child<len&&elem[child]<elem[child+1]){child++;}//child下标一定是左右孩子最大值的下标if (elem[child]>elem[parent]){int tmp=elem[child];elem[child]=elem[parent];elem[parent]=tmp;parent=child;child=2*parent+1;}else {break;}}}

上述便是对于堆的所有代码,经过上面,我们也可以看出来:堆是建立在二叉树的基础上的!!所有,如果没有较好的二叉树基础,那么堆也是……白瞎!跟笔者这个小菜鸡一样!!

下面,我们来做几个练习题,巩固一下吧!!

面试题 17.14. 最小K个数

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:

输入: arr = [1,3,5,7,2,4,6,8], k = 4

输出: [1,2,3,4]

提示:

  • 0 <= len(arr) <= 100000

  • 0 <= k <= min(100000, len(arr))

经过前面内容的讲解,那么, 对于该题,想必能够迎刃而解了吧!!那么。接下来,请看笔者的代码吧:

    public int[] smallestk(int[] array,int k){int[] ret= new int[k];if (array==null || k==0){return ret;}
//建立小根堆Queue<Integer> minHeap =new PriorityQueue<>(array.length);for (int x: array) {minHeap.offer(x);}for (int i = 0; i < k; i++) {ret[i]=minHeap.poll();
//每次从小根堆中弹出一个元素,并且把这个元素放到ret[i]中}return ret;}

对于这个题目,其实难点还是在于思路!!

求最小K个数,我们需要建立大根堆!!将数据的前K个数,建立大根堆(堆顶元素是最大的元素),当遍历第K+1个元素时候,若小于堆顶元素,说明此时堆顶元素,一定不是前K个最小值,则进行交换!!

对于这些数据而言:27 15 19 18 28,根据前3个数据,建立大根堆:

但是,由于第4个元素,18比27小,所以,需要发生替换!!

前K个最大元素——建立小根堆!

小根堆中前K个最大的值,堆顶元素是这K个最大值里面最小的!当遍历到数组元素大于堆顶的时候,说明,此时堆顶的元素一定不是前K个最大的值!

那么,接下来,请看笔者的代码:

    //前K个最大的元素public static int[] maxK(int[] arr,int k){int[] ret=new int[k];if (arr==null || k==0){return ret;}Queue<Integer> minHeap=new PriorityQueue<>(k);//遍历数组的前K个,放到堆当中for (int i = 0; i < k; i++) {minHeap.offer(arr[i]);//忘最小堆中放入前K个元素}//遍历剩下的K-1个元素,每次和堆顶的元素进行比较//堆顶元素小的时候,堆出堆for (int i = 0; i < arr.length; i++) {int val=minHeap.peek();if (val<arr[i]){minHeap.poll();//出队列minHeap.offer(arr[i]);//把另一个元素放到堆里面}}for (int i = 0; i < k; i++) {ret[i]=minHeap.poll();}return ret;}
Top-k问题

TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都 不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下: 1. 用数据集合中前K个元素来建堆 前k个最大的元素,则建小堆 前k个最小的元素,则建大堆 2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素 将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

对于27,15,19,28,18,34,65,49,25,37这些数据而言,从小到大排序,那么,建立小根堆还是大根堆合适??要求对数组本身进行排序!!(显而易见的是大根堆合适)

小根堆的根节点是最小的,但是无法确定左右两个节点的大小,大根堆的做法是:先把数组变成一个大根堆,其次,再将0号下标元素与最后一个待排序的元素进行交换,然后在变为大根堆,此时最大的一个元素就在最后位置……以此类推,这就是堆排序!!

更多推荐

优先级队列(堆)  堆排序

本文发布于:2024-02-28 14:57:45,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1769954.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:优先级   队列

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!