0 - 前言
本文是对下面两篇博文做的笔记
参考:
1 - 二叉堆简介
二叉堆是一种完全二叉树,分为最大堆和最小堆
- 最大堆:任意父节点的值 ≥ 左右子节点的值;堆顶(即二叉树的根节点)是堆中最大元素
- 最小堆:任意父节点的值 ≤ 左右子节点的值;堆顶是堆中最小元素
完全二叉树:叶结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。
参考:百度百科 - 完全二叉树
注意,二叉树以链表形式实现(每个节点都有指向子节点的指针),二叉堆以数组形式实现
1-1 向二叉堆中插入节点(最小堆)
- 将插入节点添加到二叉堆最后一个位置,相当于二叉堆节点数组的尾部
- 从插入位置开始,将插入节点与父节点比较,如果<父节点(如果是最大堆就要>父节点),那么将插入节点上浮(与父节点交换位置)。上浮直到上浮条件被打破或者到达根节点之时。
1-2 删除堆顶节点(最小堆)
- 将堆顶节点删除后,将二叉堆最后位置上的节点提到堆顶(即将二叉堆节点数组的最后一个元素移到数组开头)
- 从堆顶开始,如果左右子节点<新父节点(如果是最大堆就要>父节点),那么将新父节点下沉 (与满足下沉条件的子节点交换位置,如果左右子节点都满足下沉条件,将父节点与最小的子节点交换位置)。下沉直到下沉条件被打破或者下沉到叶节点)
1-3 将完全二叉树转化为二叉堆
将一个无序的完全二叉树转化为二叉堆,可以让所有的非叶节点依次下沉(最大堆或最小堆都适用)
注意下沉不是从上到下(堆顶到叶节点),而是从下到上(从最下层的非叶子节点开始下沉)
1-4 代码实现
因为二叉堆使用数组实现,所以要知道父节点与子节点的下标关系:
一个节点的下标为n,它的左子节点下标为2n+1,右子节点下标为2n+2,父节点坐标为(n-1)/2,注意是整除
public class BinaryHeap {
/**上浮操作,对插入的节点进行上浮
*
* @param arr
* @param length :表示二叉堆的长度
*/
public static int[] upAdjust(int arr[], int length){
//标记插入的节点
int child = length - 1;
//其父亲节点
int parent = (child - 1)/2;
//把插入的节点临时保存起来
int temp = arr[child];
//进行上浮
while (child > 0 && temp < arr[parent]) {
//其实不用进行每次都进行交换,单向赋值就可以了
//当temp找到正确的位置之后,我们再把temp的值赋给这个节点
arr[child] = arr[parent];
child = parent;
parent = (child - 1) / 2;
}
//退出循环代表找到正确的位置
arr[child] = temp;
return arr;
}
/**
*/
/**
* 下沉操作,执行删除操作相当于把最后
* * 一个元素赋给根元素之后,然后对根元素执行下沉操作
* @param arr
* @param parent 要下沉元素的下标
* @param length 数组长度
*/
public static int[] downAdjust(int[] arr, int parent, int length) {
//临时保证要下沉的元素
int temp = arr[parent];
//定位左孩子节点位置
int child = 2 * parent + 1;
//开始下沉
while (child < length) {
//如果右孩子节点比左孩子小,则定位到右孩子
if (child + 1 < length && arr[child] > arr[child + 1]) {
child++;
}
//如果父节点比孩子节点小或等于,则下沉结束
if (temp <= arr[child])
break;
//单向赋值
arr[parent] = arr[child];
parent = child;
child = 2 * parent + 1;
}
arr[parent] = temp;
return arr;
}
/**
* 构建操作
*
* @param arr
*/
public static int[] buildHead(int[] arr,int length) {
//从最后一个非叶子节点开始下沉
for (int i = (length - 2) / 2; i >= 0; i--) {
arr = downAdjust(arr, i, length);
}
return arr;
}
}
代码注释很清楚了,解释一下为什么构建二叉堆的时候从i = (length - 2) / 2开始,这是因为最后一个节点下表为length - 1,该节点对应的父节点是最后一个非叶节点,根据父节点坐标为(n-1)/2得到最后一个非叶节点的下标就是(length - 1 - 1) / 2
2 - 堆排序
给定一个无序数组,首先将该数组构建为二叉堆,然后采用逐步删除堆顶节点、将删除节点存放于二叉堆最后位置上的方式实现堆排序(因为删除堆顶节点会将二叉堆最后位置上的节点提到堆顶并下沉)。
简言之,就是交换堆顶与堆最后位置的节点,并对交换以后的堆下沉,保证下一次交换还是一个二叉堆。等到原本堆中最后一个节点交换到堆顶时,二叉堆已经排序完毕(即二叉堆数组满足降序或者升序)
public class HeapSort {
/**
* 下沉操作,执行删除操作相当于把最后
* * 一个元素赋给根元素之后,然后对根元素执行下沉操作
* @param arr
* @param parent 要下沉元素的下标
* @param length 数组长度
*/
public static int[] downAdjust(int[] arr, int parent, int length) {
//临时保证要下沉的元素
int temp = arr[parent];
//定位左孩子节点位置
int child = 2 * parent + 1;
//开始下沉
while (child < length) {
//如果右孩子节点比左孩子小,则定位到右孩子
if (child + 1 < length && arr[child] > arr[child + 1]) {
child++;
}
//如果父节点比孩子节点小或等于,则下沉结束
if (temp <= arr[child])
break;
//单向赋值
arr[parent] = arr[child];
parent = child;
child = 2 * parent + 1;
}
arr[parent] = temp;
return arr;
}
//堆排序
public static int[] heapSort(int[] arr, int length) {
//构建二叉堆
for (int i = (length - 2) / 2; i >= 0; i--) {
arr = downAdjust(arr, i, length);
}
//进行堆排序
for (int i = length - 1; i >= 1; i--) {
//把堆顶的元素与最后一个元素交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//下沉调整
arr = downAdjust(arr, 0, i);
}
return arr;
}
//测试
public static void main(String[] args) {
int[] arr = new int[]{
1, 3, 5,2, 0,10,6};
System.out.println(Arrays.toString(arr));
arr = heapSort(arr, arr.length);
System.out.println(Arrays.toString(arr));
}
}