数据结构-优先级队列

avatar
作者
猴君
阅读量:0

一,堆

1,一棵完全二叉树,顺序存储(数组)层序遍历。非完全二叉树则不用数组存储,因为空间利用率低

2,分为小根堆(每棵子树都是小根堆)和大根堆。以大根堆举例,一整棵树的每一棵子树的孩子节点都小于父亲节点。

3,孩子下标2i+1和2i+2,父亲节点的下标(i-1)/2

二,用堆实现优先级队列:

首先优先级队列的本质是一个数组,因此定义一个数组,和一个记录数组中有多少元素整型变量useSize

1,将一个数组调整为大根堆

我们需要求出最后一个子节点的父节点,然后向前遍历,将每一个父节点就进行向下调整。

向下调整:需要知道需要调整的父节点的下标和调整的范围(如果是建立大根堆,调整的范围就是到最后一个下标),知道父亲节点后求出左子节点,然后判断是否有右子节点,如果有那么判断array[child+1]与array[child]的大小关系,如果关系是大于,那么child++,这样保证child所指向的是较大的子孩子。然后将较大的子孩子和父节点的值进行比较,如果孩子节点大于父亲节点,那么两者交换,因为如果两者交换的话,会影响被交换为子节点的节点作为父亲节点时,与它自己的子节点的大小关系,所以交换后要parent=child; child=parent*2+1;然后再次重复循环上述操作,直到孩子节点是越界,则代表调整完毕。但如果父亲节点大于孩子节点就可以直接跳出循环了,因为父子节点之间不需要交换,而子节点以下的节点本身就是调整好的所以不需要再次调整,直接跳出循环即可。

private void swap(int i,int j){     int tmp=elem[i];     elem[i]=elem[j];     elem[j]=tmp; }  public void siftDown(int parent,int end){     int child=parent*2+1;     while (child<end){         if (child+1<end && elem[child]<elem[child+1]){             child++;         }         if (elem[child]>elem[parent]){             swap(child,parent);             parent=child;             child=2*parent+1;         }else {             break;         }     } }  public void createHeap(){     for (int parent =(useSize-1-1)/2 ; parent >=0; parent--) {         siftDown(parent,useSize);     } }

向下调整的时间复杂度是O(log2(n))

建堆的时间复杂度为O(N)

2,添加元素offer

首先判断是否满了,如果满了,就需要进行扩容。我们将添加的元素放到数组的最后,这时就需要将最后一个数值进行调整,使其实现大根堆。这是我们就需要向上调整。

向上调整:

我们得到子节点的下标后,推算出父亲节点的下标,然后对父亲节点和子节点进行比较。如果子节点大于父亲节点,那么子节点和父亲节点的值进行交换。然后将子节点的下标等于父亲节点的下标,父亲节的下标重新根据子节点进行计算,从而得到新的子节点和父亲节点,然后再次进行比较,循环上述行为,直到父亲节点大于0时结束。在上述子结点的值与父亲节点的值比较大小时,如果不大于则直接break跳出循环。

public void offer(int val){    if (isFull()){        elem= Arrays.copyOf(elem,2*elem.length);    }    elem[useSize]=val;    useSize++;    siftUp(useSize-1); } private void siftUp(int child){    int parent=(child-1)/2;    while (parent>=0){        if (elem[child]>elem[parent]){            swap(child,parent);            child=parent;            parent=(child-1)/2;        }else {            break;        }    } }

总结:

时间复杂度:O(log2(n))

3,删除一个元素

首先这是一个队列,所以符合先进先出的原则,所以删除时,最先删除的是首元素。这时我们可以将首元素和尾元素进行交换,然后useSize减减,这时首元素不符合大根堆的原则,所以将首元素进行向下调整。

public int poll(){     if (isEmpty()){         return -1;     }     int old=elem[0];     swap(0,useSize-1);     useSize--;     siftDown(0,useSize);     return old; }

总结:

时间复杂度:O(log2(n))

4,堆排序

大根堆下标为0的元素一定是最大的,所以可以将第一个元素和最后一个下标的元素交换,然后将第一个元素向下调整重新调整为大根堆,调整的终点是前一次调整的数组长度-1(已经选出了最大的元素,现在需要在剩余的元素中找到第二大的,所以向下调整大范围不包括已经排好序的数据),然后然后循环上述行为。使数组中的每一个元素都得到调整从而就可以得到顺序了。

public void heapSort(){     int endIndex=useSize-1;     while (endIndex>0){         swap(0,endIndex);         siftDown(0,endIndex);         endIndex--;     } }

总结:

时间复杂度:O(n*log2(n))

空间复杂度:O(1)

三,优先级队列

1,概括:

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线 程不安全的,PriorityBlockingQueue是线程安全的,本文主要介绍PriorityQueue。

2,.ClassCastException异常

当优先级队列中放置的元素是不能进行比较时,会抛出异常。由下图可知,再调用offer方法时会调用到siftUpComparable()方法,这个方法需要元素之间进行比较,所以会抛出ClassCastException异常

3,NullPointerException异常

不能插入null,会抛出NullPointerException异常

4,优先级队列默认构建的是小根堆

5,删除和添加元素时间复杂度都是O(log2(n))

6,构造函数

优先级队列有三种构造函数,分别为无参(默认容量为11),有参,和参数为优先级队列的对象。

7,比较器(大根堆)

默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器。直接实现Comparator接口,然后重写该接口中的compare方法即可。

class InCmp implements Comparator<Integer>{     @Override     public int compare(Integer o1, Integer o2) {         return o2.compareTo(o1);     }//大根堆
PriorityQueue<Integer>priorityQueue=new PriorityQueue<>(new InCmp());

四,练习

找到前k个最小的数字

方法一:

将所有元素均放到优先级队列中,然后poll前三k,并将前k个放到临时数组中。

public int[]smallestK(int [] arr,int k){     PriorityQueue<Integer> queue=new PriorityQueue<>();     for (int i = 0; i < arr.length; i++) {         queue.offer(arr[i]);     }     int[] tmp=new int[k];     for (int i = 0; i <k; i++) {         tmp[i]=queue.poll();     }     return tmp; }

这种方法的思维逻辑较为简单,但是时间复杂度比较大,效率不高。因为你需要将每一个元素添加到优先级队列中,然后删除k个元素,而添加和删除的时间复杂度均为O(log2(n)),所以这种方法的时间复杂度为O((n+k)*log2(n)).

方法二:(优化)

当要寻找前k个较小的数字时,可以建立一个大小为k的大根堆优先级数列。将数组中前k个放到优先级队列中,然后数组从k处开始向后遍历,当优先级队列的首元素比数组中的元素小大时,将有首元素弹出,然后添加数组中的这个元素。循环结束后,则优先级队列中的元素就是前k个较小的数字,所以将优先级队列里面的元素弹出放到临时数组中,并返回。

这时,之间复杂度为:O(nlog2(n))

public int[]smallestK2(int [] arr,int k){     int[] tmp=new int[k];     if (k==0){         return tmp;     }     PriorityQueue<Integer> maxHead=new PriorityQueue<>(new InCmp());     //1,先把看个元素放到元素中     for (int i = 0; i <k; i++) {         maxHead.offer(arr[i]);     }     //2,遍历剩下的元素     for (int i = k; i < arr.length; i++) {         int val=maxHead.peek();         if (val>arr[i]){             maxHead.poll();             maxHead.offer(arr[i]);         }     }      for (int i = 0; i < k; i++) {         tmp[i]=maxHead.poll();     }     return tmp; } 

当然,这种方法也可以寻找第k大的数字或者第k小的数字,只需要注意如果想找大的……(第k个数字/前k个较大的),就需要建立一个小根堆,反之,则需要建立一个大根堆

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!