目录
2、堆顶元素和末尾元素进行交换,得到最大值下沉到数组末端,不断调整重建大根堆,再进行交换调整...
一、堆的相关概念
堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。堆排序是利用堆这种数据结构而设计的一种排序算法,下面着重介绍一下堆的相关概念。直接借鉴百度百科。
所以堆首先是一颗完全二叉树。
每个结点的值都大于或等于其左右孩子结点的值,称为大根堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。
每个结点的值都小于或等于其左右孩子结点的值,称为小根堆。

二、堆排序动图

三、堆排序思路
将无序序列构建成一个堆,根据升序降序需求选择大根堆或小根堆。
将堆顶元素和末尾元素交换,将最大元素“沉”到数组末端。
重新调整结构,使其满足堆定义,然后继续交换堆顶元素和当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
说实话,找了好几篇帖子才看懂一点,另外我觉得堆排序似乎是“选泡插,快归堆希桶计基; 恩方恩老恩一三,对恩加K恩乘K。 选快堆希都不稳(ps:时常背口诀haha)中最复杂的,下面的思路都是建立的大根堆。
以数组[3,7,8,20,16,17]为例子,众所周知,数组和二叉树是可以相互转化的,堆是作为二叉树的一种典型数据结构。
首先明确很重要的一点就是整个排序过程中,并没有真正创建过树,整个过程中,其实都是在对数组进行操作。在对数组操作的时候,是以大根堆其中的规则来进行操作的。
一般升序采用大根堆,降序采用小根堆
1、 建立逻辑上的大根堆
将数组调整成大根堆的时候是从最后一个非叶子结点开始(从下往上,从右往左)到最后一个非叶子结点结束为止。
2、堆顶元素和末尾元素进行交换,得到最大值下沉到数组末端,不断调整重建大根堆,再进行交换调整...
因为堆顶元素和末尾元素交换动作,往小了说,只动了两个元素的值,堆顶和末尾,而末尾元素我们不用去理会,就相当于在堆中只变动了堆顶元素,所以会造成每次调整重建大根堆,是从堆顶开始。(从上往下)
3、代码实现(ps:比较复杂)
代码的核心部分还是 :调整索引为i的非叶子结点下所有的子树,最后构成一个堆的结构
package com.zengwen.sort;
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int arr[] = {3, 7, 8, 20, 16, 17};
heapSort(arr);
}
public static void heapSort(int arr[]) {
//A步骤一、建立逻辑上的大根堆(从最后一个非叶子结点(从下往上,从右往左))
//i表示每个非叶子结点的索引值
//在一次循环中,如果非叶子结点所在的子树,不满足大根堆的要求,则非叶子结点会和其左或右孩子结点进行交换
for (int i = arr.length / 2 - 1; i >= 0 ; i--) {
buildMaxHeap(arr,i,arr.length);
}
//B步骤二、堆顶元素和末尾元素进行交换,得到最大值下沉到数组末端,不断调整重建大根堆,再进行交换调整...
for (int i = arr.length - 1; i > 0 ; i--) {
//将堆顶元素和末尾元素交换
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//调整重建大根堆,从堆顶(从上往下))
buildMaxHeap(arr,0,i);
}
System.out.println(Arrays.toString(arr));
}
//调整索引为i的非叶子结点下所有的子树 构成一个堆的结构
/**
* @param arr 待调整的数组
* @param i 表示非叶子结点在数组中的索引
* @param length 表示对多少个元素进行调整
*/
public static void buildMaxHeap(int[] arr, int i, int length) {
//i指向非叶子结点
int temp = arr[i];
//k指向非叶子结点的左孩子
//因为length为要调整的元素个数,所以k<length
//k = 2 * k + 1 过来要么是左要么是右下一趟要么调左要么调右,k如果为叶子结点(跳出循环)或者k没有破坏不用调整(进循环if全部跳过)
//在调整三角(非叶子结点,非叶子结点左孩子,非叶子结点右孩子)后
//假设max(非叶子结点,非叶子结点左孩子,非叶子结点右孩子) = maxValue不为非叶子结点
//则下一趟还要调整三角(maxValue,maxValue左孩子,maxValue右孩子)
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
//如果k+1<length则表示左右结点都存在,否则,则表示右结点不存在
if (k + 1< length && arr[k] < arr[k + 1]) {
//k右移,指向右子结点
k++;
}
//此时k指向的是max(左子结点,右子结点)
//如果左(右)大于父结点
if (arr[k] > temp) {
//将max(左子结点,右子结点)赋值到父结点
arr[i] = arr[k];
//i指向与非叶子结点交换的左(右)孩子
i = k;
}else {
break;
}
}
//for循环结束,已经将以i为父结点的子树的最大值,放在了局部子树顶部
//将非叶子结点赋值给它的左或右孩子结点
arr[i] = temp;
}
}