博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收**藏 ^ _ ^ !
堆是一颗完全二叉树,是一种经过排序的树形数据结构,它满足如下性质:
二叉树又是啥?请查看我的另一篇博文《心中有树——基础》
小根堆与大根堆
小根堆:结点值小于后代结点值的堆,也叫最小堆,图左
大根堆:结点值大于后代结点值的堆,也叫最大堆,图右
二叉堆
二叉堆是一种特殊的堆,即每个结点的子结点不超过2个。堆排序就是使用的二叉堆。
1)根据子结点推父结点:(n-1)/2
2)根据父结点推子结点:左子结点(2n+1),右子结点(2n+2)
索引由0开始计数
我们以9、12、5、24、0、1、99、3、10、7 这10个数来构建大根堆如下,
/***
* 构建大根堆
* @param array 数据源
*/
private void buildHeap(int[] array) {
//从右向左,从下到上依次遍历父结点,建立大根堆,时间复杂度:O(n*log2n)
for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
adjust(array, i, array.length - 1);
}
}
/**
* 将指定堆构建成大堆根函数
* 逻辑
* 1. 如果起始索引无子结点,则跳出该方法
* 2. 如果只有一个左子结点,进行大小比较并置换值
* 3. 如果有两个子结点,选择最大值与父结点比较,然后置换其位置。
* 如果子结点大于父结点,置换完成后,递归进行同样操作,其子结点索引即是函数的start值
*
* @param array 源数组
* @param start 起始索引
* @param end 结尾索引
*/
public void adjust(int[] array, int start, int end) {
// 左子结点的位置
int leftIndex = 2 * start + 1;
if (leftIndex == end) {
//只有一个左结点,进行比较并置换值
if (array[leftIndex] > array[start]) {
int temp = array[leftIndex];
array[leftIndex] = array[start];
array[start] = temp;
}
} else if (leftIndex < end) {
//有两个子结点
int temp = array[leftIndex];
int tempIndex = leftIndex;
if (array[leftIndex + 1] > array[leftIndex]) {
temp = array[leftIndex + 1];
tempIndex = leftIndex + 1;
}
if (temp > array[start]) {
array[tempIndex] = array[start];
array[start] = temp;
}
adjust(array, tempIndex, end);
}
}
上面我们将大根堆构建好了,现在我们对堆进行排序。其构建思想是:
排序代码
/**
* 堆排序
*
* @param array 源数组
*/
public void heapSort(int[] array) {
buildHeap(array);
int tmp;
//要与root结点置换位置元素的索引
int end = array.length - 1;
//n个结点只用构建排序n-1次,最后只有1个元素不用在排序
for (int i = array.length - 1; i > 0; i--) {
tmp = array[0];
array[0] = array[end];
array[end] = tmp;
end--;
//头尾置换后,将堆重新构建为大堆根,置换尾部大元素不参加构建
//因为除了root结点,其他都是由大到小有序的,所以再次构建大根堆时,不用在进行adjust()前的那个循环
adjust(array, 0, end);
}
}
添加元素时,新元素被添加到数组末尾,但是添加元素后,堆的性质可能会被破坏,需要向上调整堆结果。如给大堆根堆添加元素100。
此时此时堆的结构被破坏,需要从下往上进行调整。因100大于0,5,99,则新的大堆根为
元素添加代码
/**
* 在 array 是大堆根的前提下添加元素然后重构大堆根
*
* @param array 大堆根数组
* @param value 添加的元素值
*/
private void addHeap(int[] array, int value) {
int[] arr = new int[array.length + 1];
System.arraycopy(array, 0, arr, 0, array.length);
arr[arr.length - 1] = value;
int currentIndex = arr.length - 1;
int parentIndex = (arr.length - 1) / 2;
while (parentIndex >= 0) {
if (value > arr[parentIndex]) {
int temp = arr[parentIndex];
arr[parentIndex] = value;
arr[currentIndex] = temp;
//如果最后一个元素的父结点还有父结点需要继续进行对比
currentIndex = parentIndex;
parentIndex = (currentIndex - 1) / 2;
} else {
break;
}
}
}
堆删除元素都是从结点删除,然后以这个结点为root结点的数组的最后一个元素移动到根结点的位置,并向下调整堆结构,直至重新符合堆序性。如我们删除结点35,将7移到35的位置
将7与其子结点逐个比较,直至符合大根堆规则
删除元素代码
/**
* 在 array 是大堆根的前提下删除元素然后重构大堆根
*
* @param array 大堆根数组
* @param deleteIndex 删除元素的索引
*/
private int[] deleteHeap(int[] array, int deleteIndex) {
array[deleteIndex] = array[array.length - 1];
int[] arr = new int[array.length - 1];
System.arraycopy(array, 0, arr, 0, array.length - 1);
int lefeIndex = 2 * deleteIndex + 1;
while (lefeIndex >= arr.length - 1) {
int maxIndex = lefeIndex;
if (arr.length - 1 > lefeIndex) {
if (arr[lefeIndex + 1] > arr[lefeIndex]) {
maxIndex = lefeIndex + 1;
}
}
if (arr[maxIndex] > arr[deleteIndex]) {
int temp = arr[maxIndex];
arr[maxIndex] = arr[deleteIndex];
arr[deleteIndex] = temp;
lefeIndex = 2 * maxIndex + 1;
} else {
break;
}
}
return arr;
}
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收**藏 ^ _ ^ !
相关链接