8.堆排序
(1)介绍
① 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最好、最坏、平均时间复杂度均为O(nlogn),不是稳定排序;
② 堆排序是利用了顺序存储二叉树的思想;
③ 堆是具有以下性质的完全二叉树:每个结点的值都大于或者等于它的左子结点和右子结点,称之为大顶堆;每个结点的值都小于或者等于它的左子结点和右子结点,称之为小顶堆;
④ 升序使用大顶堆,降序使用小顶堆。
(2)基本思想
① 首先要根据题上的要求,将待排的无序序列调整成为大顶堆或者小顶堆,以下思想和代码以大顶堆为例,小顶堆与其相似。
② 将调整后的大顶堆的堆顶元素与末尾元素进行交换,将最大的元素“沉”到数组末尾。交换之后,再次将除了最大元素(即末尾元素)之外的其他元素再调整为一个大顶堆,循环执行(调整+交换),直到整个数组有序。
(3)代码实现
以 arr = {4,6,8,5,9} 的无序序列为例,完成对该序列的升序排序:
import org.junit.Test;
import java.util.Arrays;
/**
* 堆排序:利用顺序存储二叉树的思想
* 时间复杂度:O(nlogn),速度非常快
*
*/
public class HeapSort {
@Test
public void test() {
//要求:以升序的方法进行排序(大顶堆)
int[] arr = {4,6,8,5,9};
heapSort(arr);
}
//编写一个堆排序的方法
public void heapSort(int[] arr) {
int temp = 0;
//1.首先要根据题上的要求,将数组调整为一个大顶堆(升序)或者小顶堆(降序),该代码以大顶堆为例
for(int i = arr.length / 2 -1;i >= 0;i--) {
adjustHeap(arr,i, arr.length);
}
//2.将调整后的大顶堆的堆顶元素与末尾元素进行交换,将最大的元素“沉”到数组末尾,
// 交换之后,再次将除了最大元素(即末尾元素)之外的其他元素再调整为一个大顶堆,循环执行(调整+交换),直到整个数组有序。
for(int j = arr.length-1;j > 0;j--) {
//前面已经对于整个数组进行一次完整的调整,则此时直接进行交换
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
//进行调整
adjustHeap(arr,0,j);
}
System.out.println("堆排序后:" + Arrays.toString(arr));
}
/**
*将一个数组(二叉树)调整成为一个大顶堆:
* @param arr 需要调整的数组
* @param i 表示非叶子节点在数组中的索引位置
* @param len 表示对多少个元素继续调整,len是在逐渐减少的
*/
public void adjustHeap(int arr[],int i,int len) {
//首先将当前元素的值保存在临时变量temp中
int temp = arr[i];
//开始调整
//说明:k = i * 2 + 1 :是让k指向当前非叶子节点的左子节点;
// k = k * 2 + 1 :是因为当前非叶子节点如果与它的左子结点或者右子结点交换值后,可能会引起
// 它的左或者右子结点所对应的子树就不能成为一个大顶堆,因此要再当前的结点开始从上往下再进行调整,
// 那么在下一轮循环中就让K指向左或者右子结点的左子结点。
for(int k = i * 2 + 1;k < len;k = k * 2 + 1) {
if(k+1 < len && arr[k] < arr[k+1]) {
k++;//让k指向右子结点
}
//如果左或右子结点的值是大于当前非叶子结点,则进行交换
if(arr[k] > temp) {
arr[i] = arr[k];
i = k; //非常重要!!!类似于于k = k * 2 + 1的理由
}else{
break;
}
}
//当for循环结束后,我们已经将以堆中索引位置为i的父结点放在了整个堆的堆顶
arr[i] = temp; //将temp放在调整后的位置
}
}