堆特点
- 一个完全二叉树,以数组形式给出,0位置即为根节点
- 假设父节点在数组中的位置为i,则其两个子节点(若存在)的位置分别为2i+1与2i+2
- 一个节点在数组中的位置为k,则其父节点位置为(k-1)/2;
堆排序
- 大顶堆,数组中的最大值位于0位置
- 小顶堆,数组中的最小值位于0位置
- 其余位置不要求有序
流程
- 调整堆结构,使得要排序区间的最大值位于0位置
- 交换,将0位置的值与尾部交换
代码
以自然顺序为例,通过大顶堆排序,虽然该代码能正确排序,但是还需要改进,改进地方是调整堆结构哪里
import java.util.Arrays;
public class MyHeapSort {
public static void main(String[] args) {
int[] arr = {1,4,3,8,1,6,8,4,0,6,8,4,26,13,48,0,48,6};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 堆排
* @param arr
*/
public static void heapSort(int[] arr){
// 先将其调整为大顶堆
adjust(arr,arr.length - 1);
// 堆排,交换,调整
for (int i = arr.length - 1; i > 0; i--) {
swap(arr,0,i);
adjust(arr,i-1);
}
}
/**
* 调整堆结构
* @param arr 要调整的数组
* @param len 要调整的区间,从0开始的
*/
public static void adjust(int[] arr,int len){
// 从最后的非叶子节点调整
for (int i = (len-1)/2; i >= 0; i--) {
// 找到其左子节点位置
int k = 2*i+1;
// 如果该节点存在右子节点或者在调整的区间内
// 找到两个子节点的最大值的位置
if(k+1 <= len && arr[k+1] > arr[k]){
k++;
}
// 如果子节点中存在比该节点的值比较大的就交换
if(arr[k] > arr[i]){
swap(arr,i,k);
}
}
}
public static void swap(int[] arr,int i,int j){
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
结果
改进后的代码,以韩顺平老师的代码为例吧
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int n = 800000;
int[] arr = new int[n];
int[] arr1 = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = (int)(Math.random()*n);
arr1[i] = arr[i];
}
long start = System.currentTimeMillis();
new HeapSort().heapSort(arr);
long mid = System.currentTimeMillis();
System.out.println("myHeapSort cost: " + (mid-start)+"ms");
Arrays.sort(arr1);
long end = System.currentTimeMillis();
System.out.println("ArraysSort cost: " + (end - mid)+"ms");
System.out.println(Arrays.equals(arr,arr1));
}
void heapSort(int arr[]){
//将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
for (int i = arr.length / 2 - 1; i >= 0 ; i--) {
adjustHeap(arr,i,arr.length);
}
/*
2.将堆顶元素于末尾元素交换,将最大元素沉到数组末端
3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序
*/
for (int j = arr.length - 1; j > 0; j--) {
int tmp = arr[j];
arr[j] = arr[0];
arr[0] = tmp;
adjustHeap(arr,0,j);
}
}
/**
* 将一个数组(二叉树),调整成一个大顶堆
* 功能:完成将i对应的非叶子节点的树调整成大顶堆
* 举例:int[] arr = {4,6,8,5,9}; => i=1 => adjustHeap => 得到{4,9,8,5,6}
* 如果再次调用adjustHeap传入的是 i = 0 => 得到{4,9,8,5,6} => {9,6,8,5,4}
* @param arr 待调整的数组
* @param i 表示非叶子节点在数组中的索引
* @param length 表示对多少个元素继续调整,length是在逐渐减少的
*/
void adjustHeap(int[] arr,int i,int length){
int tmp = arr[i];//取出当前元素的值保存在临时变量
//开始调整
//1.k = i * 2 + 1是i节点的左子节点
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] > tmp){//如果子节点大于父节点
arr[i] = arr[k];//把较大的值赋给当前节点
i = k;//i指向k,继续循环比较
}else {
break;
}
}
//当for循环结束后,已经将以i为父节点的树的最大值,放到了最顶(局部)
arr[i] = tmp;//将temp值放到调整后的位置
}
}
执行结果