堆排序的JAVA實現及時間複雜度分析
阿新 • • 發佈:2018-12-13
堆排序是一個比較常用的排序方式,下面是其JAVA的實現:
1. 建堆
// 對輸入的陣列進行建堆的操作 private static void buildMaxHeap(int[] array, int length) { // 遍歷所有的內部節點,每一個都進行siftdown操作 for (int i = (int)Math.floor(length / 2 - 1); i >= 0; i--) { siftdown(array, length, i); } } private static void siftdown(int[] array, int length, int index) { // 1. 判斷是否為葉子節點 if (isLeaf(array, length, index)) { return; } // 2. 計算左右兩個值的大小 int max = Integer.MIN_VALUE; int maxIndex = index; // 判斷該內部節點是否有兩個子樹 if (2 * (index + 1) > length - 1) { max = array[2 * index + 1]; maxIndex = 2 * index + 1; } else { // 對兩個子樹的值進行比較 if (array[2 * index + 1] > array[2 * (index + 1)]) { max = array[2 * index + 1]; maxIndex = 2 * index + 1; } else { max = array[2 * (index + 1)]; maxIndex = 2 * (index + 1); } } // 3. 如需交換,交換後繼續向下遞迴 if (array[index] < array[maxIndex]) { array[maxIndex] = array[index]; array[index] = max; siftdown(array, length, maxIndex); } } // 判斷是否為葉子節點 private static boolean isLeaf(int[] array, int length, int index) { return index > (int)Math.floor(length / 2 - 1); }
2. 堆排序
private static void heapSort(int[] array) { buildMaxHeap(array, array.length); // 將堆頂的最大值每次都交換到最後,對剩餘的數字進行新的建堆操作 for (int i = 0; i < array.length; i++) { int temp = array[array.length - i - 1]; array[array.length - i - 1] = array[0]; array[0] = temp; buildMaxHeap(array, array.length - i - 1); } }
3. 時間複雜度
堆排序的時間複雜度主要有兩個部分:
- 初始化建堆
- 每次取堆頂元素後,剩餘部分的排序
因此,我們來分別看一下這兩個部分的時間複雜度
- 對於初始化建堆:我們需要對除了葉子節點這一層的其他節點都進行遍歷,每一個點假設都遞迴到了葉子節點,需要 k - i 次siftdown操作,而每一層有 2^(i - 1) 個節點,(i代表層數,設根節點為第一層)即,我們需要計算 2^(i - 1) * (k - i) ,當 i 取值為 1 到 n - 1時的值的和。 即求: 計算過程如下: 對於前半部分: 對於後半部分: 即,兩部分相加,有: 由於,n代表的是層數,它可以由 n = log N 求得,代入,有:N - 1 - log N,故時間複雜度 O(N) = N
- 第二部分,是關於排序的,我們交換堆頂元素不斷放置到堆的末尾。即堆的元素是越來越少,我們比較的次數也減少,即求: 可以證明log(n!)和nlog(n)是同階函式: 即可以知道第二部分的時間複雜度為O(nlogn)
將兩部分結合起來,我們可以獲得時間複雜度為O(n) = O(n + nlogn) = O(nlogn)