本篇介绍另一种排序算法——堆排序
堆的定义与分类
在了解堆排序之前必须了解堆排序的定义,如下:
堆是具有以下性质的完全二叉树
:
1.
每个节点的值都大于或者等于其左右孩子节点的值称为大顶堆
2.
每个节点的值都小于或者等于其左右孩子节点的值称为小顶堆
大顶堆
示意图:
大顶堆
特点:
我们将这个二叉树映射到一个数组即
int[] arr = {34,17,24,5,3}
可以得出
//父节点大于左右孩子节点
arr[i] >=arr[2*i + 1] && arr[i] >= arr[2*i + 2]
小顶堆
示意图:
小顶堆
特点:
同样将这个二叉树映射到一维数组即
int[] arr = {6,7,12,9,8};
//父节点小于左右孩子节点
arr[i] <= arr[i*2 + 1] && arr[i] <= arr[i*2 + 2]
堆排序基本思路
堆排序的基本思路大致可以分为三步:
1.
将待排无序数组建立起大顶堆或者小顶堆(升序建立大顶堆,降序建立小顶堆)
2.
建立堆后,将最顶上的元素跟最后一个元素(右边最下的元素)交换。
3.
交换后最后一个元素就是最大或者最小的值,在原堆的基础上剪掉这个元素再重新建立起堆
重复2,3
步操作,直至全部元素排列完毕
文字描述还是挺抽象的,没关系,接下来逐步分析。
图解堆排序过程
如图,需要排序的树为:
这里要说明,建立堆的方式这里采用从右往左,从下往上的策略。这里的理解非常重要,所以我画了个图帮助读者理解。
第一步、将二叉树映射数组。开始建立堆,先找到倒数第一个非叶子节点开始建立堆。也就是在1号节点所在的子树建立堆。结果发现,右孩子节点比父节点的值大,所以则需要调整,调整的结果为:
接着再向前找到非叶子节点,也就是0号节点,此时为最顶端的节点。发现左孩子节点最大,调整为:
此时发现经过调整,1号节点也被改变了,所以要继续向下判断并调整(这就是代码片段中,为什么要循环判断),结果发现其右孩子节点的值最大所以需要调整,结果为:
到此为止,一个无序的二叉树已经建成了一个大顶堆
。大致步骤的第一步宣告完成。此时映射的数组为
arr = {9,6,8,5,4}。接下来就是进行第二步将最顶上的元素跟最后一个元素交换也就是数组的第一个元素跟最后一个元素交换,然后截掉最后一个节点。结果如图:
可以看到,交换过后堆顶的节点发生了改变,所以还需要再次建立起堆及找到堆顶元素的放置位置。再次建立的堆为:
然后再进行交换、截取、建堆操作直至整个二叉树排列完毕。后面就不再赘述了,相信读者到此已经明白了整个过程。整个个过程简单来说就是通过建大顶堆找到最大值的节点,找到的这个节点后跟最后一个节点交换,保证后面的元素比前面的大,实现升序排列。
上述的一系列操作是从右往左,从下往上建立堆,那么倒数第一个叶子节点是怎么找的呢?答案就是通过以下算法可以求出:
arr.length / 2 - 1
剩下的具体细节就在代码中慢慢体会吧
完整代码片段
public static void main(String[] args) {
int temp = 0;
int arr[] = {4,6,8,5,9};
//将无序的数组建立起大顶堆
//从低向上建立,先找到左,最下的非叶子节点
for (int i = arr.length / 2 - 1;i >= 0;i--){
adjustHeap(arr,i,arr.length);
}
//再进行交换操作
for (int j = arr.length - 1;j > 0;j --){
//第一个元素跟最后一个元素交换
temp = arr[0];
arr[0] = arr[j];
arr[j] = temp;
//交换后再调整成大顶堆,并截掉交换后的到最大值的元素,也就是末尾元素
adjustHeap(arr,0,j);
}
System.out.println(Arrays.toString(arr));
}
/**
* 建立大顶堆
* @param arr 待建立数组
* @param index 非叶子节点的下标
* @param length 数组长度
*/
public static void adjustHeap(int[] arr,int index,int length){
//从左边最下面的非叶子节点的构成的子树开始建立大顶堆
int temp = arr[index];//存储当前非叶子节点的值
//这个循环的作用就是:
// 保证当前非叶子节点和当前非叶子节点下的所有的非叶子节点都是最大值
for (int i = index * 2 + 1;i < length;i = i * 2 + 1){
if (i+1 < length && arr[i] < arr[i+1]){//找出左孩子、右孩子节点中最大的那个
i++;
}
if (arr[i] > temp){//如果孩子节点比父节点大则需要交换,(这里先不做赋值操作,因为还需要建立子节点的堆)
arr[index] = arr[i];
index = i;//这句执行后,当再次执行循环,作用就是建立当前节点下子节点的堆
}else {
break;//由于采用的是从底向上建立堆,所以子节点的不用再建立堆
}
}
//这个循环结束后,则表示index为要替换的位置
arr[index] = temp;
}