目录
1.优先级队列
1.1概念
队列:是一种先进先出(FIFO)的数据结构。
但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如 果有来电,那么系统应该优先处理打进来的电话。这样,我们就引入了优先级队列 这种数据结构。
2.优先级队列的模拟实现
2.1堆的概念
堆的性质:
1.从结构上来说,堆总是一颗完全二叉树,若根节点索引从0开始编号,某个节点编号为k的话
左孩子索引:2k+1
右孩子索引:2k+2
父节点索引:(k-1)/2
2.从节点的值来说,最大堆的根节点一定>=左右子树的所有节点值,最小堆的根节点一定<=左右子树的所有节点值。
2.2堆的存储方式
堆是一颗完全二叉树,因此可以采用顺序方式进行存储。
2.3堆的创建(以大根堆为例)
想要创建一个堆,我们需要先了解堆的两个操作:堆得元素上浮操作和堆的元素下沉操作。
先定义三个方法,用来获取索引为k的节点的父节点,左孩子节点和右孩子节点。
//获取父节点
private int parent(int k){
return (k-1)>>1;
}
//左孩子节点
private int leftChild(int k){
return (k<<1)+1;
}
//右孩子节点
private int rightChild(int k){
return (k<<1)+2;
}
2.3.1堆的元素上浮操作(以大根堆为例)
以数组[8,7,5,4,3,6]为例,在结构上是一个完全二叉树,在数值上,可以看到除了最后一个元素,它是一个大根堆,而加上了这个元素,它就不是一个大根堆了。想要让这个元素到正确的位置,就引入了元素上浮操作。
1.当这个元素的值大于它的父节点的值时,两者交换
2.重复上述操作
代码:
//元素上浮操作
private void siftUp(int k) {
while (k > 0 && data.get(k) > data.get(parent(k))) {
swap(k, parent(k));
k = parent(k);
}
}
//swap方法实现
public void swap(int i,int j){
int tep=data.get(i);
data.set(i,data.get(j));
data.set(j,tep);
}
2.3.2堆的元素下沉操作(以大根堆为例)
以数组[1,7,8,6,5,2,3]为例,仔细观察下图后发现:根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可。即元素的下沉操作。
1.将根节点标记为parent,先比较左右孩子的大小,将较大值标记为child。
若child>parent,则交换两个节点;若child<parent,则不交换。
2.重复上述步骤即可。
代码:
//元素下沉操作
public void siftDown(int k) {
//首先保证有子树
while (leftChild(k) < size) {
//判断是否存在右子树,且右子树的值大于左子树
int j = leftChild(k);
if (j + 1 < size && data.get(j + 1) > data.get(j)) {
j = j + 1;
}
//此时j索引一定是左右子树的最大值
if (data.get(k) >= data.get(j)) {
break;
} else {
swap(k, j);
k = j;
}
}
}
2.3.3堆的创建(以大根堆为例)
给定一个任意整型数组,将其调整为最大堆或最小堆。
这里以数组[1,5,3,8,7,6]为例,将其调整为最大堆。
核心思路:
1.从最后一个非叶子节点开始进行元素下沉操作,不断向根节点倒着向下调整,慢慢的将左右子树调整为最大堆。
2.当最后走到根节点时,只要下沉根节点之后,整棵树就满足了最大堆。
详细实现:
1.先将值为3的节点进行siftDown操作;
2.再将值为5的节点进行siftDown操作;
1.最后将值为1的节点进行siftDown操作,得到一个大根堆。
代码:
//将任意整型数组调整为最大堆
public MaxHeap(int[] arr){
this.data=new ArrayList<>(arr.length);
//先依次将arr中每个元素放入堆中
for(int i:arr){
data.add(i);
size++;
}
//从当前完全二叉树的最后一个非叶子节点开始向下调整,使得每个子树为大根堆
for(int i=parent(size-1);i>=0;i--){
siftDown(i);
}
}
2.3.4建堆的时间复杂度
第一层:2^0个节点,需要向下移动h-1层;
第二层:2^1个节点,需要向下移动h-2层;
第三层:2^2个节点,需要向下移动h-3层;
.......
第h-1层:2^h-2个节点,需要向下移动1层;
则需要移动的总步数为:
T(n)=2^0*(h-1)+2^1*(h-2)+2^2*(h-3)+......2^(h-2)*1 ①
2T(n)=2^1*(h-1)+2^2*(h-2)+2^3*(h-3)+......2^(h-1)*1 ②
②-①错位相减得:
T(n)=1-h+2^1+2^2+2^3+......+2^h+2^(h-1)
T(n)=2^h-1-h
n=2^h-1 h=log(n+1)
T(n)=n-log(n+1)=n
因此,建堆的时间复杂度为n。
2.4堆的插入与删除
2.4.1堆的插入
堆的插入共需要两个步骤:
1.先将元素放入到底层空间中(空间不够时需要扩容)
2.将最后新插入的节点向上调整,直到满足堆的性质
代码:
//向最大堆中添加元素
public void add(int val){
//先将元素尾插进数组,使其结构为完全二叉树
this.data.add(val);
size++;
siftUp(size-1);
}
2.4.2堆的删除
堆的删除一定是删除堆顶元素。
具体如下:
1.将堆顶元素与堆中最后一个元素交换
2.将堆中有效数据个数减少一个
3.对堆顶元素进行向下调整
代码:
//从最大堆中删除最大值
public int extractMax(){
if(data.isEmpty()){
throw new NoSuchElementException("heap is empty!");
}
int val=data.get(0);
data.set(0,data.get(size-1));
data.remove(size-1);
size--;
siftDown(0);
return val;
}
2.5优先级队列的模拟实现
public class PriorityQueue implements MyQueue<Integer> {
// 使用最大堆作为存储元素的集合
private MaxHeap heap = new MaxHeap();
@Override
public void offer(Integer val) {
heap.add(val);
}
@Override
public Integer poll() {
return heap.extractMax();
}
@Override
public Integer peek() {
return heap.peekMax();
}
}