谈谈堆排序
堆排序是利用堆这一数据结构设计的排序算法,是一种选择排序。它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
什么是堆?
堆是由以下两种不同性质的完全二叉树组成:
(1): 每个节点的值大于等于其左右孩子节点值称为大顶堆 (arr[i]>=arr[2*i+1] && arr[i]>=arr[a*i+2])
(2): 每个节点小于等于其左右孩子节点的值称为小顶堆 (arr[i]<=arr[2*i+1] && arr[i]<=arr[a*i+2])
图示:
注: 一般来说,大顶堆用于升序排列,小顶堆用于降序排列
堆排序的思路:(在这里以升序为例)
tip1.将待排序的序列构建为一个大顶堆(此时整个序列中最大的元素就是堆顶的根节点)【代码演示的时候会详细说这一段】
tip2.将数组的头元素和末尾元素进行交换,此时末尾为最大值(将最大的值沉到了末端)
tip3.将剩余的元素重新构建一个堆,反复执行上述1,2操作,直到得到了整个有序序列
康康代码:
1.准备待操作的数组: 4,6,8,5,9
2.开始进行将序列构建为大顶堆的过程
/**
* 功能: 完成将以i为非叶子节点的子树调整为大顶堆
* 4 6 8 5 9 => i=1 => adjustHeap => 4 9 8 5 6
* 如果再次调用 i=0 => adjustHeap => 9 6 8 5 4
*
* <p>
* arr: 待调整的数组实例(从左到右 从下到上进行调整)
* i: 表示非叶子节点的索引
* length: 对多少个元素进行调整,length在逐渐减少
*/
public static void adjustHeap(int[] arr, int i, int length) {
count++;
//先取出当前元素的值,保存在临时变量中
int temp = arr[i];
//开始调整
//1.k=i*2+1 说明k是i节点的左子节点;k=k*2+1 说明了k的下一轮是k节点的当前子节点
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
//说明当前左子节点小于右子节点的值
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++;//k指向右子节点
}
if(arr[k]>temp){//如果子节点大于父节点
arr[i]=arr[k];//把较大的值赋给当前节点
i=k;//!! 让i指向k(找到下一组子树进行比较)
}else{
break;//! 从小到大 从下到上进行调整
}
}
//循环结束后,将以i为子树的最大值已经放在了最顶部,此时的i不再是原来的i了
arr[i]=temp;//将temp放置到调整后的位置
}
注: 由于这里是对子树进行操作,比较难以理解,所以画了一个图示帮助理解(在这里是第一轮代码结束后的过程,即是对于6 5 9元素的大顶堆转换操作,但是其余的操作和上述类似,大家可以获取完整代码后debug一下)
3.分步操作【针对本次需求而言,可以这么弄,但是还是需要弄出统一的方式进行分析操作】
public static void heapSort(int[] arr) {
int temp=0;
System.out.println("堆排序");
adjustHeap(arr,1,arr.length);
//第一次处理子树
System.out.println("第一次调整过后:"+ Arrays.toString(arr));// 4 9 8 5 6 ??
//在原来的基础上进行调整
adjustHeap(arr,0,arr.length);
//第二次处理子树
System.out.println("第二次调整过后:"+ Arrays.toString(arr));// 9,6,8,5,4
}
4.数组转化为大顶堆统一方案
public static void heapSort(int[] arr) {
int temp=0;
System.out.println("堆排序");
//调整子树
for(int i=arr.length/2-1;i>=0;i--){
adjustHeap(arr,i,arr.length);
}
System.out.println("调整过后:"+Arrays.toString(arr));
/**
* 将堆底的元素和堆顶元素进行交换
* */
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));
}
注:此时tip1已经完成
5.堆顶元素和堆底元素进行交换(日常交换)
for(int j=arr.length-1;j>0;j--){
//交换
temp=arr[j];
arr[j]=arr[0];
arr[0]=temp;
//将一棵树调整为大顶堆
adjustHeap(arr,0,j);
}
此时在循环中反复调用adjustHeap,完成tip2和tip3
6.处理结果
完整代码:
package tree_op.heap_sort;
import java.util.Arrays;
/**
* 800万个数据 大概3s左右排完 O(nlogn)
* */
public class HeapSort {
public static void main(String[] args) {
//操作数组,将数组进行升序排序
int arr[] = {4, 6, 8, 5, 9};
heapSort(arr);
}
//编写一个堆排序的方法
public static void heapSort(int[] arr) {
int temp=0;
System.out.println("堆排序");
// adjustHeap(arr,1,arr.length);
// //第一次处理子树
// System.out.println("第一次调整过后:"+ Arrays.toString(arr));// 4 9 8 5 6 ??
// //在原来的基础上进行调整
// adjustHeap(arr,0,arr.length);
// //第二次处理子树
// System.out.println("第二次调整过后:"+ Arrays.toString(arr));// 9,6,8,5,4
//第1棵子树调整过后可得
for(int i=arr.length/2-1;i>=0;i--){
adjustHeap(arr,i,arr.length);
}
System.out.println("调整过后:"+Arrays.toString(arr));
/**
* 将堆底的元素和堆顶元素进行交换
* */
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));
}
/**
* 功能: 完成将以i为非叶子节点的子树调整为大顶堆
* 4 6 8 5 9 => i=1 => adjustHeap => 4 9 8 5 6
* 如果再次调用 i=0 => adjustHeap => 9 6 8 5 4
*
* <p>
* arr: 待调整的数组实例(从左到右 从下到上进行调整)
* i: 表示非叶子节点的索引
* length: 对多少个元素进行调整,length在逐渐减少
*/
public static void adjustHeap(int[] arr, int i, int length) {
//先取出当前元素的值,保存在临时变量中
int temp = arr[i];
//开始调整
//1.k=i*2+1 说明k是i节点的左子节点;k=k*2+1 说明了k的下一轮是k节点的当前子节点
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
//说明当前左子节点小于右子节点的值
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++;//k指向右子节点
}
if(arr[k]>temp){//如果子节点大于父节点
arr[i]=arr[k];//把较大的值赋给当前节点
i=k;//!! 让i指向k(找到下一组子树进行比较)
}else{
break;//! 从小到大 从下到上进行调整
}
}
//循环结束后,将以i为子树的最大值已经放在了最顶部,此时的i不再是原来的i了
arr[i]=temp;//将temp放置到调整后的位置
}
}