数据结构——优先级队列和堆

目录

一、堆

1.概念

2.堆的存储方式

3.性质

4.模拟实现堆(以小根堆为例)

(1).堆的调整

(2).堆的创建

(3).建堆的时间复杂度

(4).堆的插入和删除

5.堆的应用

(1).PriorityQueue的实现

(2).堆排序

(3).Top-k问题

二、优先级队列

1.概念

2.PriorityQueue详解

(1).常用接口 

(2).PriorityQueue的构造方法

(3).常用函数

(4).扩容

(5).注意

三、优先队列(PriorityQueue)和堆(Heap)的区别


数据结构——优先级队列和堆_第1张图片

一、堆

1.概念

一堆元素集合k={k0,k1...k(n-1)},把所有元素按照完全二叉树的顺序存储方式,存储在一个一维数组中,并且满足ki<=k(2i+1)且ki<=k(2i+2),则称为小堆(反之则为大堆)。
因此在堆的数据结构中,我们将根节点最大的堆称为大根堆或最大堆,将根节点最小的堆称为小根堆或最小堆 。数据结构——优先级队列和堆_第2张图片

数据结构——优先级队列和堆_第3张图片

2.堆的存储方式

堆是一棵完全二叉树,因此可以像层序遍历一样采用顺序存储的方式来存储,更加高效。

注意:对于非完全二叉树则不适合使用顺序存储的方式。在还原二叉树时,空间中需要存储空节点,这会导致空间利用率降低

3.性质

(1).小根堆中某个节点的值总是不小于其父节点的值,大根堆中某个节点的值总是不大于其父节点的值。

(2).堆总是一棵完全二叉树。

4.模拟实现堆(以小根堆为例)

(1).堆的调整

【1】.向下调整

1.让parent标记需要调整的节点,child标记parent的左孩子(注意:parent如果有孩子一定先是有左孩子)

2.如果parent的左孩子存在(child     (1).parent右孩子是否存在,存在找到左右孩子中的最小值,让child进行标记
    (2).将parent与较小的孩子child进行比较
        【1】.parent小于较小的孩子child时,结束调整
        【2】.parent大于较小的孩子child时,交换parent与child的值。此时可能并不正确,因此需要继续向下调整,即parent=child;child=parent*2+1;然后重复上述过程

public void shiftDown(int parent,int len){
	int child=2*parent+1;
	while(child

注意:在整个向下调整的过程中时间复杂度为O (log2N).

【2】.向上调整

1.让child标记需要调整的节点,然后找到child的双亲节点parent。

2.如果双亲节点parent存在(chid>0),则进行以下操作直到双亲节点不存在

    (1).将双亲节点与孩子节点进行交换
        【1】.如果双亲节点比孩子节点小,parent满足堆的性质,调整结束
        【2】.双亲节点大于孩子节点时,交换parent与child的值。此时可能并不正确,因此需要继续向上调整,即child=parent; parent=(child-1)/2; 然后重复上述过程。

public void shiftUp(int child){
	int parent =(child-1)/2;
	while(child>0){
		if(elem[parent]>elem[child]){
			break;
		}else{
			int tmp=elem[parent];
			elem[parent]=elem[child];
			elem[child]=tmp;
			child=parent;
			parent=(child-1)/2;
		}
	}
}

 注意:在整个向上调整的过程中时间复杂度为O (N).

(2).堆的创建

public class Heap{
	public int[] elem;
	public int usedSize;
	public Heap(){
		this.elem=new int[10];
	}
	public void initElem(int[] arr){
		for(int i=0;i=0;parent--){
			shiftDown(parent,usedSize);
		}
	}
}

(3).建堆的时间复杂度

数据结构——优先级队列和堆_第4张图片

注意:因为堆是完全二叉树,而满二叉树也是完全二叉树,因此简化为满二叉树来证明。

(4).堆的插入和删除

注意:堆的插入和删除时间复杂度均为O(log2N)

【1】.堆的插入

步骤:
    1.将元素放到最后一个元素后边,空间不够时需要扩容。
    2.将最后新插入的节点向上调整,直到满足要求。

public void push(int val){
	if(isFull()){
		elem=Arrays.copyOf(elem,2*elem.length);
	}
	elem[usedSize++]=val;
	shiftUp(usedSize-1);
}
public boolean isFull(){
    return usedSize==elem.length;
}

【2】.堆的删除

步骤:
    1.将堆顶元素与堆中最后一个元素交换。
    2.将堆中元素有效数据个数减少一个。
    3.对堆顶元素进行向下调整。

public void pop(){
	if(isEmpty()){
		return;
	}
	int tmp=elem[0];
	elem[0]=elem[usedSize-1];
    	elem[usedSize-1]=tmp;
	usedSize--;
	shiftDown(0,usedSize);
}
public boolean isEmpty(){
	return usedSize==0;
}

5.堆的应用

(1).PriorityQueue的实现

用堆作为底层结构封装优先级队列

(2).堆排序

堆排序是利用堆的思想进行排序,首先需要建一个堆,升序排序建大堆,降序排序建小堆;其次利用堆删除思想来进行排序

public void HeapSort(){
	int end=usdSize-1;
	while(end>0){
		swap(elem,0,end);
		shiftDown(0,end);
		end--;
	}
}

(3).Top-k问题

求数据集合中前K个最大的元素或最小的元素,一般情况下数据量比较大,普通排序就不可取,因此我们就需要用到堆来排序。

首先用数据集合中前K个元素建堆(求K个最大元素:建小堆;求K个最小元素:建大堆);然后用剩余的N-K个元素依次与堆顶元素比较,堆顶元素不满足则替换。

二、优先级队列

1.概念

队列是一种先进先出的数据结构,但有时操作的数据会带有优先级,所以在出队列时可能需要优先级较高的的元素先出队列。因此数据结构需要提供:返回最高优先级对象添加新对象这两个基本操作,这种数据结构就是优先级队列。

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列。PriorityQueue是线程不安全的,而PriorityBlockingQueue是线程安全的,本文重点讲解PriorityQueue。

2.PriorityQueue详解

(1).常用接口 

数据结构——优先级队列和堆_第5张图片

(2).PriorityQueue的构造方法

【1】.PriorityQueue():创建一个空的优先级队列,默认容量是11

PriorityQueue priorityqueue = new PriorityQueue<>();

【2】.PriorityQueue(int initialCapacity):创建一个初始容量为initialCapacity的优先级队列,注意:initialCapacity不能小于1,否则会抛出IllegalArgumentException异常

PriorityQueue priorityqueue = new PriorityQueue<>(100);

【3】.PriorityQueue(Collection c) 用一个集合来创建优先级队列

ArrayList arraylist = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
PriorityQueue priorityqueue = new PriorityQueue<>(arraylist);

(3).常用函数

boolean offer(E e)

插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度 ,注意:空间不够时候会进行扩容。

E peek()

获取优先级最高的元素,如果优先级队列为空,返回null

E poll()

移除优先级最高的元素并返回,如果优先级队列为空,返回null

int size()

获取有效元素的个数

void clear()

清空

boolean isEmpty()

检测优先级队列是否为空,空返回true

(4).扩容

如果容量小于64时,是按照oldCapacity的2倍方式扩容的。
如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的。
如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容。

(5).注意

【1】.PriorityQueue中放置的元素必须要能比较大小,不能插入无法比较大小的,否则会抛出ClassCastException异常

【2】.不能插入null对象,否则会抛出NullPointerException

【3】.没有容量限制,可以插入任意多个元素,其内部可以自动扩容

【4】.插入和删除元素的时间复杂度为O(log2N)

【5】.PriorityQueue底层使用了堆数据结构

【6】.PriorityQueue默认是小堆,如果需要大堆需要用户提供比较器

// 自定义比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator{
	@Override
	public int compare(Integer o1, Integer o2) {
		return o2-o1;
	}
}

三、优先队列(PriorityQueue)和堆(Heap)的区别

优先队列:是一种特殊的队列

堆:是一个很大的概念并不一定是完全二叉树,在前文中使用完全二叉树是因为这个很容易被数组储存,所以大部分我们用完全二叉树(数组)来储存堆,但是除了这种二叉堆之外我们还有二项堆、 斐波那契堆等这些堆就不属于二叉树。

优先对列和堆有什么区别呢?这里说的堆默认是我们最常使用的二叉堆,而二叉堆只是优先队列的一种是实现方式而已。而优先队列还有二项堆、平衡树、线段树等来实现。所以说优先队列和堆不是一个概念,如果非要说他们之间的关系就是:二叉堆只是优先队列的一种实现方式,而java的PriorityQueue也是基于这个实现的。
 

数据结构——优先级队列和堆_第6张图片

你可能感兴趣的:(数据结构,数据结构,java)