算法 - 堆排序
要点:将数组作为堆结构,利用大根堆根最大的性质,构建完就将根与未排序部分的末尾交换,逐步实现有序。
1 import java.util.Random; 2 3 public class HeapSort<T extends Comparable> { 4 5 public void sort(T[] arr) { 6 printArr(arr, " => 初始数组"); 7 for (int i = arr.length - 1; i > 0; i--) { 8 // 构建大跟堆 9 buildHeap(arr, 0, i); 10 printArr(arr, " => 构建大根堆"); 11 // 最大值依次往后放,进行排序 12 swap(arr, 0, i); 13 printSign(0, i); 14 } 15 } 16 17 /** 18 * 从上向下构建大根堆 19 * 很多大跟堆是从下向上构建的 20 * 21 * @param arr 原数组 22 * @param rootIndex 根坐标 23 * @param end 堆大小 24 */ 25 private void buildHeap(T[] arr, int rootIndex, int end) { 26 // 左子节点 27 int leftChildIndex = rootIndex * 2 + 1; 28 // 如果左子节点超出堆的大小就退出 29 if (leftChildIndex > end) { 30 return; 31 } 32 // 如果根比左子节点小就交换 33 if (arr[rootIndex].compareTo(arr[leftChildIndex]) < 0) { 34 swap(arr, rootIndex, leftChildIndex); 35 // 交换可能会出现规则破坏,回溯一下 36 backCheck(arr, rootIndex); 37 } 38 // 继续构建堆 39 buildHeap(arr, leftChildIndex, end); 40 // 右子节点 41 int rightChildIndex = rootIndex * 2 + 2; 42 // 如果右子节点超出堆的大小就退出 43 if (rightChildIndex > end) { 44 return; 45 } 46 // 如果根比右子节点大就交换 47 if (arr[rootIndex].compareTo(arr[rightChildIndex]) < 0) { 48 swap(arr, rootIndex, rightChildIndex); 49 backCheck(arr, rootIndex); 50 } 51 // 继续构建堆 52 buildHeap(arr, rightChildIndex, end); 53 } 54 55 /** 56 * 向上追溯,查看是否乱序 57 * 例: 树的坐标规律 58 * 0,1,2,3,4 => 坐标 0 59 * 3,6,7,9,8 => 开始构建大跟堆 1 2 60 * 3,6 => 发现6比3大交换 34 56 61 * 6,3,9 => 发现9比3大交换 62 * 6,9,3 => 如果在这里不追溯的话,就会出现不符合大跟堆规律的情况 63 * 9,6,3 => 追溯发现根节点的父节点比根小,就交换,继续追溯,发现坐标已经为0了,就停下 64 * 65 * @param arr 数组 66 * @param rootIndex 根坐标 67 */ 68 private void backCheck(T[] arr, int rootIndex) { 69 if (rootIndex == 0) { 70 return; 71 } 72 int fatherIndex = (rootIndex - 1) / 2; 73 if (arr[fatherIndex].compareTo(arr[rootIndex]) < 0) { 74 swap(arr, fatherIndex, rootIndex); 75 } 76 backCheck(arr, fatherIndex); 77 } 78 79 private void swap(T[] arr, int a, int b) { 80 T temp = arr[a]; 81 arr[a] = arr[b]; 82 arr[b] = temp; 83 } 84 85 private void printSign(int a, int b) { 86 String[] arr = new String[]{" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "}; 87 arr[a] = "^"; 88 arr[b] = "^"; 89 for (String n : arr) { 90 System.out.print(n); 91 } 92 System.out.println(" => 交换"); 93 } 94 95 public void printArr(T[] arr, String message) { 96 for (T n : arr) { 97 System.out.print(n); 98 } 99 System.out.print(message); 100 System.out.println(); 101 } 102 103 public static void main(String[] args) { 104 int n = 11; 105 Integer[] arr = new Integer[n]; 106 for (int i = 0; i < n; i++) { 107 arr[i] = new Random().nextInt(10); 108 } 109 HeapSort hs = new HeapSort(); 110 hs.sort(arr); 111 } 112 113 /** 114 * 92134594608 => 初始数组 115 * 98946152304 => 构建大根堆 116 * ^ ^ => 交换 117 * 96844152309 => 构建大根堆 118 * ^ ^ => 交换 119 * 84634150299 => 构建大根堆 120 * ^ ^ => 交换 121 * 64523140899 => 构建大根堆 122 * ^ ^ => 交换 123 * 53402146899 => 构建大根堆 124 * ^ ^ => 交换 125 * 43402156899 => 构建大根堆 126 * ^ ^ => 交换 127 * 42301456899 => 构建大根堆 128 * ^ ^ => 交换 129 * 31204456899 => 构建大根堆 130 * ^ ^ => 交换 131 * 20134456899 => 构建大根堆 132 * ^ ^ => 交换 133 * 10234456899 => 构建大根堆 134 * ^^ => 交换 135 * 136 * => 遍历次数:log(n) + log(n-1) + log(n-2) + ... + log(1) 137 * => 时间复杂度:O(nlogn) 138 * => 稳定性:不稳定 139 * 140 * 例: 141 * 0,1,2,3,5 => 坐标 142 * 7,1,3,3,2 => 原数组 143 * 7,1,3 => 3比1大交换 144 * 7,3,3,1,2 => 交换后的数组 => 3的顺序发生改变,因此不稳定 145 */ 146 147 }
知止而后有定;定而后能静;静而后能安;安而后能虑;虑而后能得。