1. 定义与基本特性
二叉堆是用数组描述的完全二叉树。下面对定义中的名词作简单解释。
1.1 完全二叉树
即树上全部的结点都按照从上到下、从左到右的顺序依次填满树。如下图就是一颗完全二叉树。如果将第6个元素(也就是圆圈9)从树中删去,则树未能填满,不是完全二叉树。
1.2 本质是数组
当我们在写代码时,二叉堆其实就是一个数组a[]。只不过我们给这个数组赋予了特殊的意义,即:
- a[2*i]是a[i]的左子节点
- a[2*i+1]是a[i]的右子节点
- a[i/2]是a[i]的父节点(注意:i/2是整除运算)
所以,就不要问“欢哥,子节点的指针在哪呀?”之类的问题,数组怎么会有指针呢?
1.3 二叉堆的成员变量
class MinHeap{
// data 是二叉堆的数组
private double[] data;
// size 是二叉堆的大小。显然有 size <= data.length
private int size;
public double[] getData(){
return data;
}
public MinHeap(double[] data) {
this.data = data;
size = data.length;
}
}
2. 二叉堆的基本操作及其实现
2.0 核心思想(十分重要)
- 最小堆的性质:即父节点的元素比子节点小。最大堆与之相反。
- 最小堆操作的核心思想:最小堆的所有操作,都是为了维护和保证上述最小堆的性质。理解了核心思想,就容易理解最小堆的操作了。
2.1 最小堆化 Min_Heapify(int index)
- 输入参数index
public void minHeapify(int index) {
int l = 2*index+1;//左子节点
int r = 2*index+2;//右子节点
int minIndex = index;//这个变量用于记录比index小的子节点
// 下面三个连续的if代码,用于判断左右子节点是否比index节点小。
if (l >= size){
return;
}
if (data[l] < data[index]){
minIndex = l;
}
if (r < size){
if (data[r] < data[minIndex]){
minIndex = r;
}
}
// 如果存在左右子节点比index节点小,则交换该节点与index节点,并对该节点递归minHeapify
// 这样做维护了最小堆的性质
if(minIndex != index) {
double t = data[index];
data[index] = data[minIndex];
data[minIndex] = t;
minHeapify(minIndex);
}
}
2.2 构建最小堆 Build_MinHeap()
public void buildMinHeap(){
//自底向上构建最小堆
for (int i = size/2-1; i >= 0; i--){
minHeapify(i);
}
}
自底向上构建最小堆,即从叶子节点 --> 根节点。
循环过程中,每次执行完minHeapfiy(i) 后,以i为根节点的堆都是最小堆。因此当i=0时,整个堆就是最小堆。这个思想称为循环不变式( Loop invariant)。(这句话肯定不考)
2.3 堆排序 Heap_Sort()
public void sort() {
//首先,构建最小堆
buildMinHeap();
while (size > 0){
/*
* 每次将堆中的首个元素(也就是最小元素)与堆的结尾元素交换,
* 交换后缩小堆的size,并完成最小堆化(为了维护最小堆的性质)
*/
double t = data[size - 1];
data[size - 1] = data[0];
data[0] = t;
size--;
minHeapify(0);
}
}
- 上述操作可以用一句话概括:依次找到堆中的最小元素、第二小元素、第三小元素…。
- 最小堆完成上述操作后,堆的数组就是一个降序排列的数组。使用最大堆可以获得升序排列的数组。
- 时间复杂度:O(n·logn)
- 空间复杂度:O(1) 原地算法
2.4 抽取最大值 Heap_Extract_Max()
- 抽取:先获得最小值,再将该值从堆中删除。
public double heapExtractMax(){
// 第0个元素就是最大值
double res = data[0];
// 删除第0个元素,并维护最小堆
data[0] = data[--size];
minHeapify(0);
return res;
}
2.5 获取最大值 Heap_Max()
- 获取:只是获取最大值,不需要删除。
public double heapMax(){
return data[0];
}
2.6 最小堆降低键值 MinHeap_Decrease_Key(int index, double key)
- int index: 被降低键值的元素的下标
- double key: 键值降低为 key
void minHeapDecreaseKey(int index, double key){
// 若key值大于index元素的值,则是提高键值,滚粗,不许调用
if (key > data[index]){
System.out.println("method call error");
return;
}
// 将index中的元素,键值设置为key
data[index] = key;
// 自底向上进行,将节点元素与父节点进行比较,判断它是否满足最小堆的性质
while (index > 0 && data[index/2] > data[index]){
// 若不满足,则维护最小堆的性质,交换父节点与子节点的值
double t = data[index/2];
data[index/2] = data[index];
data[index] = t;
index /= 2;
}
}
2.7 最小堆尾插入元素 MinHeap_Insert(double key)
- double key: 插入元素的键值
void minHeapInsert(double key){
/*
* 扩大堆的容量(size),并令新扩增位置的元素为正无穷
*/
size ++;
data[size - 1] = Double.POSITIVE_INFINITY;//POSITIVE:正数,INFINITY:无穷大
/*
* 将正无穷的键值降至key
* 借助该方法,维护最小堆的性质
*/
minHeapDecreaseKey(size, key);
}