引入
堆排序写起来是相对比较简单的排序
排序方法有两种
第一种:
每次删除顶部的值存入数组中
缺点:会造成额外的存储空间
第二种:
每次交换顶部与尾部元素,再对顶部进行下滤即可
这里实现的是第二种方法
代码
// 大根堆的堆排序
public class MaxHeapSort {
// 对一个数组建大根堆
public void buildMaxHeap(int arr[], int n){
// 只需要分支结点(非叶子结点)下滤即可
for(int i = n / 2; i >= 1; i--){
Down(arr, i, n);
}
}
// 下滤
/*
目的:把k下标的值放到适当位置,使得每一个k节点的值大于它的左右节点值
1. k节点的值大于左右节点的值,代表k节点处理正确位置,不用动了
2. 否则,对比左右节点值哪个更大
3. 与k节点的值进行交换
4. 使k变为子节点的下标,继续向下对比
*/
public void Down(int arr[], int k, int n){
arr[0] = arr[k];// 先用0位置保存要下滤的值
for(int i = k * 2; i <= n; i *= 2){// 初始i为k的左结点, 每次增长2倍是不断的往子树下面下滤
if(i + 1 <= n && arr[i] < arr[i + 1]){// 先对左右子结点对比,选出更小子结点的下标
i++;
}
if(arr[0] > arr[i]){ // 如果父结点大于左右结点表示满足大根堆就退出,记住需要用保存的arr[0],不能arr[k]因为k的值会变
break;
}
arr[k] = arr[i];// 交换值,把父结点的值变为子结点的值,子结点i位置的值还是本身,但是要么被下次循环的子结点值覆盖,要么在循环外被一开始要发生下滤的值覆盖
k = i;// 父结点的下标变为子结点的下标,即可再次发生下滤
}
arr[k] = arr[0];// 确定了一开始k结点元素值的位置
}
// 堆排序
/*
1. 因为是最大堆,每次让头结点与末尾节点交换,使得最大值已经有序
2. 再让头结点下滤,把未排序的最大值放上顶部
3. 再如此循环即可
*/
public void SwapDownSort(int arr[], int n){
for(int i = n; i > 1; i--){
// 交换第一个和最后一个
swap(arr, 1, i);
// 交换一下代表确定一个排序的值,需要结点数-1,从根结点1开始下滤
Down(arr, 1, i - 1);
}
}
// 辅助方法
public void swap(int arr[], int i, int j){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 遍历
public void disPlay(int arr[]){
for(int i = 1; i < arr.length; i++){
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
int arr[] = {0, 53, 17, 78, 9, 45, 65, 87, 32};// 0位置,空出一个位置,为了从编号从1开始,更方便
MaxHeapSort wh = new MaxHeapSort();
int n = arr.length - 1;// n是实际多少个元素
wh.buildMaxHeap(arr, n);// 建堆
wh.SwapDownSort(arr, n);// 排序
wh.disPlay(arr);// 遍历
}
}
建堆的树结构
堆排序结果