堆
一、堆的定义
数据结构中的堆,不要和 jvm 中的堆空间搞混了
数据结构中的堆,分为大根堆和小根堆,其表现形式是一棵完全二叉树,区别在于,对于大根堆,其每个节点的值都大于等于其所有子节点的值,小根堆反之
那什么是完全二叉树呢?,即除了叶子层外,其他部分必须是满二叉树,且所有叶子必须从左到右连续排列
那什么是满二叉树呢?,即除了叶子节点外,每个节点都有左右孩子的二叉树
在接下来的讲解中,如无特殊说明,我都是用大根堆来进行讲解的,相信聪明的你在看懂大根堆后,很快就写出小根堆的代码的
大根堆:
小根堆:
二、堆的构建
1、数据结构选型
构建,无非是增删两种情况
在构建之前,我们先考虑用什么数据结构来表示堆
很多初学者第一反应可能是用树来表示,毕竟上面的图展现的就是一棵树的形式
但是,我们来考虑一下堆的应用场景,经常是要取出最大的根(把树的根节点提出来),然后让剩下的部分再次形成一个大根堆
这种场景下,用树这种数据结构显然不合理,因为树的非叶子节点的删除和转移十分麻烦
那该怎么办?
此时,我们来看一看一棵树广度遍历下的性质:
可以看到,如果将根节点标号为1,那么每个节点 k 的左右节点的编号分别为 2k 和 2k+1
并且因为堆是一棵完全二叉树,所以其广度遍历的顺序下,所有节点的标号是连续的,即对于有 n 个节点的堆,其节点的广度遍历顺序标号为 1-n
这样连贯的顺序,自然而然让我们想到数组
所以,我们一般都用数组来表示堆
2、初始化
public class Heap {
// 存放堆数据
private int[] heap;
// 末尾元素下标(从1开始)
private int index;
// 记录最大元素个数
private int capacity;
public Heap(int capacity) {
this.capacity = capacity;
this.index=0;
this.heap=new int[capacity+1];
}
}
int[] heap
:表示堆的数组**(因为我们的根节点标号为 1 ,所以表示堆的数组,我们要多申请一个空间)**int capacity
:表示堆的最大容量int index
:表示堆的最后一个节点(堆为空的时候,index==0)
这里要多插一句,为什么我们的下标要从 1 开始,理由有 :
**1、**这样左右孩子的下标,就是 2k 和 2k+1(如果从0开始的话,左右孩子下标是 2k+1 和 2k+2)
**2、**末尾节点的下标,同时也是节点的个数,在编写的时候不容易出错
因为不是堆排序,我们不是要进行原地排序,所以数组的排列可以按照我们的意愿来,怎么方便、怎么不容易出错,就怎么来
3、添加元素
对于添加元素,我们的做法是:
1、将新元素插入到堆的最后一个位置(要满足完全二叉树的性质)
2、将这个新的节点,与其父节点进行比较,如果大于父节点,就与父节点进行交换,直到为根或者父节点大于它为止
// 大根堆添加元素
public boolean add(int num) {
// 堆已经满了
if (index>=capacity) return false;
// 添加了一个元素,最后一个元素的下标要加1了
index++;
heap[index]=num;
int curr = this.index;
// 将新插入的元素,不断与其父节点进行比较,直到符合堆的定义为止
while (curr/2>=1 && heap[curr]>heap[curr/2]) {
// swap 的代码,会在最后的完整代码中给出
swap(curr,curr/2);
curr/=2;
}
return true;
}
完整代码如下:
public class Heap {
// 存放堆数据
private int[] heap;
// 末尾元素下标(从1开始)
private int index;
// 记录元素个数
private int capacity;
public Heap(int capacity) {
this.capacity = capacity;
this.index=0;
this.heap=new int[capacity+1];
}
// 大根堆添加元素
public boolean add(int num) {
// 堆已经满了
if (index>=capacity) return false;
index++;
heap[index]=num;
int curr = this.index;
while (curr/2>=1 && heap[curr]>heap[curr/2]) {
swap(curr,curr/2);
curr/=2;
}
return true;
}
// 交换堆内元素位置
private void swap(int x,int y) {
checkInBound(x,y);
heap[x]=heap[y]-heap[x];
heap[y]=heap[y]-heap[x];
heap[x]=heap[y]+heap[x];
}
private void checkInBound(int... indexes) {
for (int i : indexes) {
checkInBound(i);
}
}
// 不在范围内,抛出异常
private void checkInBound(