堆
当前数组中,给定一个节点,如何找到该节点的子节点?根据下标找规律
如果当前下标为i,左子树下标就是2*i+1,右字数下标就是2*i+2
如果当前节点下标为i,父节点下标为(i-1)/2
A下标0,左子树1,右子树2
B下标1,左子树3,右子树4,
C下标2,左子树5,右子树6,
D下标3,左子树7,右子树8.
使用数组表示二叉树的缺点:
1、如果一棵树有很多的空节点
A B C D
0 2 6 14
如果一棵树是完全二叉树,使用数组表示就非常合适,针对完全二叉树使用数组表示,此时空间就没有被浪费
堆
本质上就是一个特殊的二叉树
1、是完全二叉树
2、要求对于树的中的任意节点来说,
当前节点的值必须是大于左右孩子的值=>大堆/大根堆/大顶堆
当前节点的值必须是小于左右孩子的值=>小堆/小根堆/小顶堆
注意:
1、不能是有些节点满足当前节点大于左右孩子节点,有些节点满足当前节点小于孩子节点
2、堆中规则只是父节点和子节点的关系,左右子树之间没有约束
堆通常都是使用数组的方式来表示
堆最主要的用处就是找到一个集合中的最大值/最小值
借助堆,还能高效的找到第二大/第二小的值。
借助堆,还能高效的找到第三大/第三小的值。
…… ====》对对结构进行变换才能看出来
借助堆,还能高效的找到第k大/第k小的值。=>非常经典的 top k问题
堆相关操作:
堆的调整操作:
给定一个数组,当前这个数组不满足堆的特性,经过一定的操作调整,把它变成一个堆
向下调整(下沉式调整):
要求当前这个树中,除了根节点之外,其他节点都符合堆的要求,此时就可以使用向下调整
根据下面这个树来演示:
除了根节点之外,其他节点都已经符合 大堆 的要求了,此时要想让这个结构变成堆,就需要从根节点出发,进行向下调整
以大堆为例。从当前节点出发,先找到左右子树根节点,比较大的值,把当前节点和比较大的这个子节点进行交换
交换一次,不能完成调整,此时数组仍不是一个堆
就从左右子树中找到一个最大的数进行交换
public static void shiftDown(int[]array,int size,int index){
int parent = index;
int child = 2 * parent +1;
//如果child 对应节点存在,就继续调整,
//如果超过size 说明当前parent已经是叶子结点
while(child < size){
//查看一下右子树
if(child +1 < size && array[child +1] > array[child]){
child =child +1;
}
//child经过上面的条件,已经不知道指向的是parent的左子树还是右子树了
//child 肯定是左右子树中较大的那个
if(array[child] > array[parent]){
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
}else {
break;
}
parent = child;
child = 2 * parent + 1;
}
}
向上调整(上浮式调整):
private void shiftUp(int[] array, int size, int index) { //上浮调整
int child = index;
int parent = (child - 1) / 2;
// 若果child 为 0 说明 child 已经是根节点了,根节点就没有父节点
//调整到这里已经就到顶了
while (child > 0){
if(array[parent] < array[child]){
int tmp = array[parent];
array[parent] = array[child];
array[child] = tmp;
}else {
break;
}
child = parent;
parent = (child - 1) / 2;
}
}
建堆
随便给一个数组,都能创建出一个堆来
从后往前遍历,从最后的一个节点的父节点出发(最后一个非叶子节点)
一次从当前位置进行向下调整即可
public static void creatHeap(int[] array,int size){
//从后往前遍历,从最后一个而非叶子节点出发,依次进行向下调整
//size -1 得到的是最后一个元素的下标,
//再次 -1 / 2 视为了根据最后一个节点下标,找到该节点对应的父亲节点下标
for(int i = (size -1 -1) / 2;i >= 0;i--){
shiftDown(array,size,i);
}
}
基于向下调整的方式建堆,必须从后往前遍历,
基于向上调整的方式建堆,必须从前往后遍历。
优先队列
实现优先队列的过程中进一步完成堆的其他操作
普通队列是“先进先出”
优先队列/优先级队列:每次出队列的元素都是优先级最高的元素
进行入队列的时候,就把元素插到数组末尾,然后进行向上调整
进行出队列的时候,删除堆顶元素,然后向下调整
删除操作:
1、去最后一个元素,复制到[0] 的元素上
2、尾删最后的元素
3、从 [0] 出发,进行向下调整
public Integer poll(){
if(size <= 0){
return null;
}
int ret = array[0];
array[0] =array[size -1];
size--;
shiftDown(array,size,0);
return ret;
}
private void shiftDown(int[] array,int size,int index){
int parent = index;
int child = 2 * parent +1;
while (child < size){
if(child +1 < size && array[child + 1 ]> array[child]){
child = child +1;
}
if(array[child] > array[parent]){
int tmp = array[child];
array[child] = array[parent];
array[parent] =tmp;
}else{
break;
}
parent =child;
child = parent * 2 +1;
}
}
时间复杂度
入队列 ==> 向上调整 ===》O(logN)
出队列 ==> 向下调整 ===》O(logN)
取堆首元素 ===》O(1)
建堆操作:看起来是O(NlogN) 实际上是===》O(N)
java 标准库中已经提供了一个优先队列
标准库中的优先队列默认是一个小堆,每次出队里的元素都是最小值
相当于标准库中,定义了“值越小,优先级越高”这样的规则
也可以有办法修改这个规则
手动定义一个比较器对象,借助比较器对象来描述比较规则(描述何种数据优先级高)
下面这个代码中创建了一个匿名内部类(定义在方法中的类),下面的代码可以直接替代上方的代码,不知道类名但知道这个类实现了 Compartor 接口
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
下面更简洁的方法:
点击后就可以得到下面的代码:
PriorityQueue<Integer> queue = new PriorityQueue<>((Integer o1,Integer o2) -> o2 - o1);
lambda表达式 ,Java8后添加的,相当于一个匿名的方法,由于省略的东西太多了代码看起来不好理解,我们可以展开成下面的代码:
public static void main(String[] args) {
PriorityQueue<Integer> queue = new PriorityQueue<>((Integer o1,Integer o2) -> {
return o2 - o1;
});