目录
🐳今日良言:未经审视的人生不值得一过
🐇一、堆
🐍1.介绍
堆是一种数据结构,是一棵完全二叉树。
小根堆
根节点值总小于孩子节点的值,左右孩子节点的值无任何关系
大根堆
根节点的值总大于孩子节点,左右孩子节点的值无任何关系
🐭2.步骤分析
我们以:集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据为例
该如何将上述二叉树调整成堆呢?
观察上面二叉树可以发现,根节点27的左右子树都满足小根堆的性质,因此,只需要将27向下调整到合适位置,这棵二叉树就可以调整成小根堆。
根据上述分析,我们就可以写出向下调整的代码:
len是数组长度。
public void shiftDown(int parent,int len) {
int child = 2 * parent +1;// 左孩子节点
// 不越界才调整
while (child < len) {
// 拿到左右孩子节点中数据较大的那个下标
if (child + 1 < len && elem[child] > elem[child + 1]) {
child++;
}
// 如果孩子节点的值大于根节点的值就交换
if (elem[child] < elem[parent]) {
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
parent = child;
child = 2 * parent + 1;// 继续向下调整
} else {
// 如果不大于说明该树已经调整好了
break;
}
}
}
上述代码时调整小根堆的代码,调整大根堆只需要修改两个地方即可。
elem[child] > elem[child + 1] // > 修改成 < 即可
if (elem[child] < elem[parent]) // 修改成 > 即可
如果左右子树都不是堆,该如何将这课二叉树调整成大根堆呢?
以普通的序列{ 1,5,3,8,7,6 }为例
数组长度为6,最后一位6下标为5,调整规则是,从最后一个叶子节点的父节点开始调整,所以从3开始调整:
代码:
// 创建大顶堆
public void createHeap() {
// 代表每次从哪个根节点开始
for (int parent = (this.usedSize-1-1)/2;parent >= 0;parent--) {
shiftDown(parent,this.usedSize);
}
}
接下来,分析一下,上述建堆的时间复杂度:
如何在已构建好的堆中插入数据呢?例:再已构建好的小根堆中插入元素10
插入规则:如果是数组的话先要判断数组是否已经满了,满了的话就需要扩容,没满的话就先将待插入数据放到最后,然后进行下面操作:
向上调整代码:
// 向上调整
public void siftUp(int child) {
int parent = (child - 1) /2;
while (child > 0) {
if (this.elem[child] > this.elem[parent]) {
int temp = this.elem[child];
this.elem[child] = this.elem[parent];
this.elem[parent] = temp;
child = parent;
parent = (child - 1) /2;
} else {
break;
}
}
}
插入操作代码:
// 添加数据
public void offer(int val) {
// 首先需要判满
if (isFull()) {
this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
}
// 然后放在最后usedSize位置
this.elem[usedSize] = val;
this.usedSize++;
// 然后开始向上调整
siftUp(usedSize - 1);
}
删除操作:堆的删除一定删除的是堆顶元素。
假如要删除10
第一步:将10与最后的数据28交换位置,
第二步:将堆中有效数据个数减少一个
第三步:从根节点处开始向下调整即可。
删除操作代码:
// 删除堆顶数据
public int pop() {
// 判空
if (isEmpty()) {
return -1;
}
// 然后交换堆顶和最后一个数据
int temp = this.elem[0];
this.elem[0] = this.elem[usedSize - 1];
this.elem[usedSize - 1] = temp;
usedSize--;
// 从堆顶开始向下调整
shiftDown(0,usedSize);
return temp;
}
🐝3.完整代码
import java.util.Arrays;
class Heap {
public int[] elem;// 数组
public int usedSize;// 有效数据的个数
public static final int DEFAULT_SIZE = 10;
public Heap(int[] array) {
this.elem = new int[DEFAULT_SIZE];
for (int i = 0; i < array.length;i++) {
this.elem[i] = array[i];
usedSize++;
}
}
// 创建大顶堆
public void createHeap() {
// 代表每次从哪个根节点开始
for (int parent = (this.usedSize-1-1)/2;parent >= 0;parent--) {
shiftDown(parent,this.usedSize);
}
}
/**
* 真正的向下调整方法
* @param parent 每次调整的根节点
* @param len 每棵子树调整的位置 < len
*/
public void shiftDown(int parent,int len) {
int child = 2 * parent +1;// 左孩子节点
// 不越界才调整
while (child < len) {
// 拿到左右孩子节点中数据较大的那个下标
if (child + 1 < len && elem[child] > elem[child + 1]) {
child++;
}
// 如果孩子节点的值大于根节点的值就交换
if (elem[child] < elem[parent]) {
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
parent = child;
child = 2 * parent + 1;// 继续向下调整
} else {
// 如果不大于说明该树已经调整好了
break;
}
}
}
// 添加数据
public void offer(int val) {
// 首先需要判满
if (isFull()) {
this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
}
// 然后放在最后usedSize位置
this.elem[usedSize] = val;
this.usedSize++;
// 然后开始向上调整
siftUp(usedSize - 1);
}
// 向上调整
public void siftUp(int child) {
int parent = (child - 1) /2;
while (child > 0) {
if (this.elem[child] > this.elem[parent]) {
int temp = this.elem[child];
this.elem[child] = this.elem[parent];
this.elem[parent] = temp;
child = parent;
parent = (child - 1) /2;
} else {
break;
}
}
}
// 判满
public boolean isFull() {
return this.usedSize == this.elem.length;
}
// 删除堆顶数据
public int pop() {
// 判空
if (isEmpty()) {
return -1;
}
// 然后交换堆顶和最后一个数据
int temp = this.elem[0];
this.elem[0] = this.elem[usedSize - 1];
this.elem[usedSize - 1] = temp;
usedSize--;
// 从堆头开始向下调整
shiftDown(0,usedSize);
return temp;
}
public boolean isEmpty() {
return this.usedSize == 0;
}
}
public class TextHeap {
public static void main(String[] args) {
int[] array = {27,15,19,18,28,34,65,49,25,37};
Heap heap = new Heap(array);
heap.createHeap();
//heap.offer(80);
System.out.println(heap.pop());
System.out.println(Arrays.toString(heap.elem));
}
}
🐯二、优先级队列
🐕1.介绍
🐂2.源码等相关分析
使用PriorityQueue的几点注意事项:
1.使用之前必须导入包:import java.util.PriorityQueue;
2.放置的元素必须是能够比较大小的,否则会报异常,如图:
3.不能插入空对象,会报异常。
4.默认是建小堆。
创建一个空的优先级队列:
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
观察一下它的底层容量等。
当不传入比较器时,会创建一个默认容量为11的数组。
再来看一下扩容规则:
然后插入一些数据:
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(10); priorityQueue.offer(5);
通过上述了解,提出一个问题:如何可以创建大根堆呢?
如果是自定义类型其实只需要修改compareTo里面相减的返回值即可
因为Integer内部实现了compareTo方法,我们想要让其可以创建出大根堆,可以传入一个比较器。
这里先补充一下compareable和comparetor的区别:
面试官:元素排序Comparable和Comparator有什么区别? - 腾讯云开发者社区-腾讯云 (tencent.com)
上述链接博主写的非常详细。
我们这里创建一个比较器
class Intcmp implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }
在创建PriorityQueue 时传入比较器
此时再来分析一下插入数据:
除了可以通过创建自定义比较器外,还可以通过匿名类的方式,更快速、便捷的完成自定义比较器的功能。