接上文《數據結構和算法 | 快速排序算法原理及實現和優化(一)》,我們來講講快速排序的五種優化方案。
1、優化選取基準點
三數取中法:先找出三個關鍵字,然後排序,取中間的關鍵字(至少不會是兩個極端)。在代碼示例中我們選取相應區間的開始元素、中間元素和末尾元素。
按照所選取的基準點的順序可以構造一顆順序二叉樹。二叉樹的深度就是遞歸的深度。二叉樹的平衡性越好,算法的性能越好。
2、優化不必要的交換
將交換改爲賦值。
3、優化小數組時的排序方案
大數據排序時對快速排序算法性能的影響很小。但是,只有幾個數據要排序時有效率更高的排序算法。例如,直接插入排序。直接插入排序是簡單排序中性能最好的。
4、優化遞歸操作
遞歸對於算法的效率有着很大的影響。快速排序算法有兩次遞歸操作,對我們來說太奢侈了,我們應該減少遞歸的調用,就可以最大程度的提高性能。我們可以利用尾遞歸
。
什麼是尾遞歸呢?如果一個函數中遞歸形式的調用出現在函數的末尾,我們稱這個遞歸函數是尾遞歸的。
當編譯器檢測到一個函數調用是尾遞歸的時候,它就覆蓋當前的活躍記錄而不是在棧中去創建一個新的。編譯器可以做到這點,因爲遞歸調用是當前活躍期內最後一條待執行的語句,於是當這個調用返回時棧幀中並沒有其他事情可做,因此也就沒有保存棧幀的必要了。通過覆蓋當前的棧幀而不是在其上重新添加一個,這樣所使用的棧空間就大大縮減了,這使得實際的運行玫率會變得更高。
因此,只要有可能我就需要將遞歸函數寫成尾遞歸的形式。
5、發揮尾遞歸的優勢:處理小部分的的遞歸
對數據規模較小的部分遞歸,對數據規模較大的部分通過尾遞歸的調用分小。儘量把棧的空間利用起來。
具體優化代碼
public class QuickSort01 {
public static int MAX_LENGTH_INSERT_SORT = 3;
public static void main(String[] args) {
int[] array = {5, 9, 1, 9, 5, 3, 7, 6, 1}; // 待排序數組
sort(array, 0, array.length - 1);
print(array);
}
/**
* 快速排序
*
* @param array 待排序的數組
* @param low 數組的起始地址
* @param high 數組的結束地址
*/
public static void sort(int array[], int low, int high) {
// 直到兩個下標相遇,程序結束
// ------------優化點3:小規模排序使用直接插入排序------------
if (high - low > MAX_LENGTH_INSERT_SORT) {
// ------------優化點4:改爲尾遞歸------------
while (low < high) {
// 查找 k 的位置
int k = partition(array, low, high);
// ------------優化點5:處理小部分的的遞歸------------
if (k - low < high - k) {
sort(array, low, k - 1);
low = k + 1;
} else {
sort(array, k + 1, high);
high = k - 1;
}
}
} else {
insertSort(array, low, high);
}
}
/**
* 快速排序,分割的過程
*
* @param array 待排序的數組
* @param low 數組的起始地址
* @param high 數組的結束地址
* @return k 值
*/
public static int partition(int array[], int low, int high) {
// ------------優化點1:優化選取基準點------------
selectPoint(array, low, high);
// 基準點
int point = array[low];
// 直到兩個下標相遇,程序結束
while (low < high) {
// high 向左移動,直至遇到比point值小的記錄,停止移動
while (low < high && array[high] >= point) {
high--;
}
// ------------優化點2:將交換改爲賦值------------
array[low] = array[high];
//low 向右移動,直至遇到比point值大的記錄,停止移動
while (low < high && array[low] <= point) {
low++;
}
// ------------優化點2:將交換改爲賦值------------
array[high] = array[low];
}
// ------------優化點2:別忘記將 point 填回去------------
array[low] = point;
return low;
}
/**
* 優化選取基準點:選取相應區間的開始元素、中間元素和末尾元素。將值大小處在中間的元素放到low位置。
* @param array 待排序的數組
* @param low 數組區間的開始位置
* @param high 數組區間的結束位置
*/
public static void selectPoint(int[] array, int low, int high) {
int mid = low + (high - low)/2;
if (array[low] > array[high]) {
swap(array, low, high);
}
if (array[mid] > array[high]) {
swap(array, mid, high);
}
// 經過前面兩步,最大值已經在 high 位置
// 下一步將值大小處在中間的元素放到low位置
if (array[mid] > array[low]) {
swap(array, mid, low);
}
}
public static void insertSort(int array[], int low, int high) {
// 注意:low是數組區間的開始,high是數組區間的結束
// 循環待排序的部分的數列
// 第一個數據(下標爲low的數據)由於插入排序剛開始,有序表中沒有任何記錄,可以直接添加到有序表中
for (int i = low + 1; i < high + 1; i++) {
int temp = array[i];
int j = i;
// 如果前面的元素小於temp,則向後移
for (; j > low && array[j - 1] > temp; j--) {
array[j] = array[j - 1];
}
// 前一個元素(array[j - 1])和後一個元素(array[j])是相同的
// 在下一輪時,當array[j - 1]小於或等於temp時,將temp插入array[j](即上一輪的array[j - 1])
array[j] = temp;
}
}
/**
* 交換數組中兩個元素的位置
*/
public static void swap(int array[], int low, int high) {
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
/**
* 打印數組
*/
public static void print(int array[]) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
}
5 1 3 5 9 6