目录
什么是二叉堆
定义:二叉堆,本质上是一种完全二叉树。
分类:二叉堆分为最大堆和最小堆两种类型,最大堆和最小堆分别又可称为大顶堆和小顶堆。最大堆中,任何一个父节点的值都大于或等于它的左、右孩子节点的值;最小堆中,任何一个父节点的值都小于或等于它的左、右孩子节点的值。
二叉堆的根节点叫做堆顶。因此,最大堆的堆顶是整个堆中的最大元素,最小堆的堆顶是整个堆中的最小元素。
二叉堆的基本操作
二叉堆的基本操作也是二叉堆的自我调整,有插入节点、删除节点和构建二叉堆。
1.插入节点
二叉堆要插入一个节点时,总是插在完全二叉树的最后一个位置。插入过程为:
若在最小堆中插入一个节点,则插入节点后与父节点进行比较,若小于父节点则将该节点“上浮”,然后继续与父节点比较,重复操作,直到稳定(即满足最小堆中的所有父节点大于等于其左右节点),最后堆顶节点元素必定是最小的元素。在最大堆中插入节点,则是当大于父节点时“上浮”,最后的堆顶元素必定是最大的元素。
由于每次插入操作,都是单一节点在进行浮动,因此,插入操作的时间复杂度是O(logn)
2.删除节点
二叉堆要删除一个节点时,总是删除堆顶的节点。删除过程为:
若在最小堆中删除一个节点,我们移除堆顶的节点,并将堆中的最后一个节点临时补到原本堆顶的位置,然后将堆顶节点与其左右节点进行比较,如果其中最小的一个比堆顶节点小,则将堆顶节点“下沉”,然后继续与左右节点进行比较,重复操作,直到稳定。在最大堆中删除一个节点时,则是将左右节点中大的那个,并且比父节点还大的那个节点进行移动,将父节点“下沉”,重复操作,直到稳定。
由于删除节点操作,是单一节点的“下沉”,因此,删除操作的时间复杂度为O(logn)
3.构建二叉堆
构建二叉堆,就是把一个无序的完全二叉树调整为二叉堆,本质上就是让所有非叶子节点依次“下沉”(构建最小堆)。
如下图所示,依次将2,8,3,6节点进行下沉操作,顺序如下所示。
节点“3”需要继续下沉:
构建二叉堆的时间复杂度为O(n),这里就不进行数学推理了。
代码实现
由于二叉堆从本质上来说就是一颗完全二叉树,因此采用数组进行存储。
以下的代码中依次展示了下沉节点、上浮节点以及构建二叉堆的过程,其中举的例子为最小堆。
有一点需要注意:上浮节点不一定是一定用在构建最小堆,即“上浮”不一定指的是小值上浮。
import java.util.Arrays;
public class MyHeap {
//下沉调整,将某个节点进行下沉,可以用于构建一个最小堆以及删除一个节点的情况
public static void downAdjust(int[] array, int parentIndex, int length){
int temp = array[parentIndex];
int childIndex = 2 * parentIndex + 1;
while(childIndex < length){
//如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
if(childIndex + 1 < length && array[childIndex + 1] < array[childIndex]){
childIndex++;//childIndex取到的是左右孩子中最小的那个
}
//如果父节点小于任何一个孩子的值,则直接跳出
if(temp <= array[childIndex])
break;
//因为是单一节点的下沉,所以无须真正交换,单向赋值即可
array[parentIndex] = array[childIndex];
parentIndex = childIndex;
childIndex = 2 * childIndex + 1;
}
array[parentIndex] = temp;
}
//上浮调整,将最后一个节点进行上浮,可以用在节点插入中
public static 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;
}
public static void buildHeap(int[] array){
//从最后一个非叶子节点开始,依次下沉
//最后一个非叶子节点为最后一个节点的父节点
for(int i = (array.length - 2) / 2; i >= 0; i--){
downAdjust(array, i, array.length);
}
}
public static int[] insertHeap(int[] array, int data){
//插入一个节点,用新数组表示,然后对新数组进行上浮操作
int newLength = array.length + 1;
int[] newArray = new int[newLength];
System.arraycopy(array,0,newArray,0,array.length);
newArray[newLength - 1] = data;
upAdjust(newArray);
return newArray;
}
public static int[] deleteHeap(int[] array){
int newLength = array.length - 1;
int[] newArray = new int[newLength];
newArray[0] = array[array.length - 1];
System.arraycopy(array,1,newArray,1,array.length-2);
downAdjust(newArray,0,newLength);
return newArray;
}
public static void main(String[] args) {
int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};
upAdjust(array);
System.out.println(Arrays.toString(array));
array = new int[] {7,1,3,10,5,2,8,9,6};
buildHeap(array);
System.out.println(Arrays.toString(array));
//当前的array是一个最小堆,进行插入节点操作
System.out.println(Arrays.toString(insertHeap(array,4)));
//当前的array是一个最小堆,进行删除节点操作
System.out.println(Arrays.toString(deleteHeap(array)));
}
}