普通的数据结构,如果我们想要寻找所存元素中的最大值或者最小值,需要挨个查找。而本章所学的优先队列和堆会按照优先级给元素排序,帮助我们以最快的速度O(1)获取优先级最高的元素。
优先队列
优先队列把优先级最高的元素设为链表的头节点,这样我们获取或删除优先级最高的元素只需要O(1)的时间复杂度,这么设计的代价就是牺牲插入的效率,每次插入一个新的元素时,我们都需要迭代链表,并找到合适的地方插入,这个时间复杂度往往是O(n)。
以下是优先队列支持的操作:
- push:插入一个新的元素
- pop:将优先级最高的元素弹出(删除)
- peek:查看优先级最高的值
使用java实现优先队列
优先队列的定义和链表很相似,首先我们需要定义节点和PriorityQueue
:
public class PriorityQueue {
class Node {
int value;
// priority 越大,优先级越高
int priority;
Node next;
public Node(int value, int priority) {
this.value = value;
this.priority = priority;
}
}
// 头结点
Node header = null;
}
其中Node就是队列中的节点,包含value数组和优先级priority,如果希望数值大的节点优先级高,那么可以将priority的数值设为和value一样。反之,我们可以将priority设为数值的相反数,那么在此队列中,一个数值越小,优先级就越高。在PriorityQueue
中,我们只需要记录一个头节点head即可。
push方法定义
public void push(int value, int priority) {
// 队列为空,将当前节点设置为头结点
if (header == null) {
header = new Node(value, priority);
return;
}
// 队列不为空,需要找到一个正确的位置将节点插入
Node node = new Node(value, priority);
// 将元素插入到头结点
if (header.priority < priority) {
node.next = header;
header = node;
return;
}
Node current = header;
while (current.next != null && current.next.priority > priority) {
current = current.next;
}
node.next = current.next;
current.next = node;
}
在Push中,我们需要检查是否头节点为空,如果是,就将新的节点设置为头节点。否则我们迭代循环头节点,将新的节点插入一个特定位置,插入后之前节点的优先级都比新节点高,之后节点的优先级都比新节点小。
peek和pop方法定义
/**
* 弹出头结点
*
* @return
*/
public Node pop() {
if (header == null) {
return null;
}
Node tmp = header;
header = header.next;
return tmp;
}
/**
* 返回头结点
*
* @return
*/
public Node peek() {
return header;
}
peek只需要返回头节点即可。pop只需要弹出head的值,并让head指向自己的下一个节点即可。
isEmpty方法定义
isEmpty只需要查看head是否为空:
/**
* 判断队列是否为空
*
* @return
*/
public boolean isEmpty() {
return header == null;
}
方法复杂度分析
- push: O(n)
- pop: O(1)
- peek: O(1)
堆(Heap)
堆(Heap)是一种可以迅速找到一堆数中的最大或者最小值的数据结构,二叉堆(binary heap)是堆的一种实现,计算机中一般使用数组存储二叉堆。
堆是一颗完全二叉树,堆中的节点满足以下的条件:一个节点的父节点优先级比自己高,而自己的子节点优先级比自己底。优先级可以根据数值的大小来决定。最常见的堆有以下两种类型:
-
最大堆(Max Heap):根节点数值最大,所有父节点的数值比各自的子节点数值大或等于子节点,很多地方也叫大顶堆。
-
最小堆(Min Heap):根节点数值最小, 父节点数值比其子节点数值小或等于子节点,很多地方也叫小顶堆。
最大堆(Max Heap)
最大堆的基本操作:
- add: 将新元素插入堆
- poll: 将根节点(数值最大的元素)删除
- peek: 获取根节点的数值
在任何的时间点,最大堆都应该保持其特性:父节点的数值比所有子节点大。在插入新元素的时候,我们要遵循以下步骤:
- 在堆的最后新建一个节点,将数值赋予新节点。
- 将其节点和父节点比较。
- 如果新节点的数值比父节点大,调换父子节点的位置。
- 重复步骤2和3直到最大堆的特性被满足。
以下是删除根节点的步骤:
- 移除根节点
- 将最后一个节点移到根节点处
- 将子节点和父节点比较
- 如果父节点的数值比子节点小,替换父子节点
- 重复步骤3和4直到最大堆的特性被满足。
使用数组实现最大堆
因为堆是一颗完全二叉树,因此我们可以直接使用数组而不是链表来实现堆。