1、堆的简介
Heap是一种数据结构:
1)完全二叉树;
2)heap中存储的值是偏序;
最大堆:最大堆的任何一个父节点的值,都大于或等于它左、右孩子节点的值。
图片说明
最小堆:最小堆的任何一个父节点的值,都小于或等于它左、右孩子节点的值。
图片说明
堆的根节点叫堆顶。
最大堆的堆顶是整个堆中最大元素;最小堆的堆顶是整个堆中最小元素。
2、堆的操作
2.1 堆的数据结构
存储结构:一般是由数组来表示堆,i节点的父节点下标就为(i-1)/2。它的左右节点下标分别为 2i+1 和 2i+2。该性质有对的逻辑结构为完全二叉树得来。
图片说明
2.2 插入节点(Insert)
当二叉树插入节点时,插入的位置是完全二叉树的最后一个位置。例如插入一个新的节点2。
图片说明
这时,新的节点的父节点10比5大,显然不符合最小堆的性质。于是让新节点"上浮",和父节点交换位置。
图片说明
继续用节点2和父节点7比较,因为2小于7,则让新节点继续"上浮"。
图片说明
继续比较,最终新节点2"上浮"到堆顶位置。
图片说明
插入操作在数据存储结构形式变化:
图片说明
"上浮(swim)"操作的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
- “上浮”调整
- 时间复杂度 O(logn)
- @param array 待调整的堆
*/
public void upAdjust(int[] array){
int childIndex=array.length-1;
int parentIndex=(childIndex-1)/2;
//temp保存插入的叶子节点值,用于最后的赋值
int temp=array[childIndex];
while(childIndex>0&&temp<array[parentIndex]){
//无须真正交换,单向赋值即可
array[childIndex]=array[parentIndex];
childIndex=parentIndex;
parentIndex=(parentIndex-1)/2;
}
array[childIndex]=temp;
}
2.3 删除节点(Delete)
二叉树删除节点的过程和插入节点的过程正好相反,所删除的是处于堆顶的节点。例如删除最小堆的堆顶节点2。
图片说明
这时,为了继续维持完全二叉树的结构,我们把堆的最后一个节点10临时补到堆顶的位置。
图片说明
接下来,让暂处堆顶位置的节点10和它的左、右孩子进行比较,如果左、右孩子中最小的一个(显然是节点5)比节点10小,那么让节点10"下沉"。
图片说明
接下来继续让节点10和它的左、右孩子做比较,左、右孩子中最小的是节点7,由于10大于7,让节点10继续"下沉"。
图片说明
这样一来,二叉树重新得到了调整。
插入操作在数据存储结构形式变化:
图片说明
"下沉(link)"操作的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
- “下沉”调整
- 时间复杂度 O(logn)
- @param array 待调整的堆
- @param parentIndex 要“下沉”的父节点
- @param length 堆的有效大小
/
public void downAdjust(int[] array,int parentIndex,int length){
//temp保存父节点值,用于最后的赋值
int temp=array[parentIndex];
int childIndex=2parentIndex+1;
while(childIndex<length){
//如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
if(childIndex+1<length&&array[childIndex+1]<array[childIndex]){
childIndex++;
}
//如果父节点小于任何一个孩子的值,则直接跳出
if(temp<=array[childIndex]){
break;
}
array[parentIndex]=array[childIndex];
parentIndex=childIndex;
childIndex=2*childIndex+1;
}
array[parentIndex]=temp;
}
2.4 构建二叉堆(Build)
构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆,本质上就是让所有非叶子节点依次"下沉"。
下面举一个无序完全二叉树的例子,如下图所示。
图片说明
首先,从最后一个非叶子节点开始,也就是从节点21开始。如果节点21大于它左、右孩子中最小的一个,则节点21"下沉"。
图片说明
接下来,轮到节点9,如果节点9大于它左、右孩子节点中最小的一个,则节点9"下沉"。
图片说明
然后轮到节点5,如果节点5大于它左、右孩子节点中最小的一个,则节点5"下沉"。事实上节点5小于它的左、右孩子,所以不用改变。
接下来轮到节点15,如果节点15大于它的左、右孩子节点中最小的一个,则节点"下沉"。
图片说明
节点15继续比较,继续"下沉"。
图片说明
经过上述几轮比较和"下沉"操作,最终每一节点都小于它的左、右孩子节点,一个无序的完全二叉树就被构建成了一个最小堆。
"构建堆"的操作实现:
1
2
3
4
5
6
7
8
9
10
/**
- 构建堆
- 时间复杂度 O(n)
- @param array 待调整的堆
*/
public void buildHeap(int[] array){
for(int i=(array.length-2)/2;i>=0;i–){
downAdjust(array,i,array.length);
}
}