快速排序:通過一趟排序,將數據分為兩部分,其中一部分中的所有數據比另外一部分的所有數據要小,然后按照此方法,分別對這兩部分進行排序,達到最終的排序結果。
每趟排序選取基准元素,比該基准元素大的數據放在一邊,比該基准元素小的數據放在另一邊,這種處理方式稱為分治法。
數據的移動是基准元素中比較重要的點,有兩種方式實現,挖坑填數法和指針交換法。
挖坑填數法
(下圖中單詞有兩處拼寫錯誤,pviot和pvoit應該為pivot)
如下為代碼實現
import java.util.Arrays; public class QuickSort { public static void main(String[] args) { int[] arr = { 12, 45, 23, 67, 7, 1, 5, 21 }; quickSort(arr, 0, arr.length - 1); System.out.println(Arrays.toString(arr)); } public static void quickSort(int[] arr, int startIndex, int endIndex) { if (startIndex >= endIndex) { return; } int partitionIndex = getPartitionIndex(arr, startIndex, endIndex); quickSort(arr, startIndex, partitionIndex - 1); quickSort(arr, partitionIndex + 1, endIndex); } public static int getPartitionIndex(int[] arr, int startIndex, int endIndex) { int index = startIndex; int left = startIndex; int right = endIndex; int pivot = arr[startIndex]; while (left < right) { while (left < right) { if (arr[right] < pivot) { arr[index] = arr[right]; // 找到所需值,將值填充到坑位 index = right; // 此時right位置為index新坑位 left++; // 右側找到了數據,則left右移一位,准備進行比對 break; // 找到數據之后,跳出內循環,准備從左側開始對比 } // 沒有匹配的數據,則right左移一位,繼續對比 right--; } while (left < right) { if (arr[left] > pivot) { arr[index] = arr[left]; // 找到所需的值,將值填充到坑位 index = left; // 此時left位置為index新坑位 right--; // 左側找到了數據之后,right向左移動一位,准備進行比較 break; // 找到數據之后,跳出內循環,准備從左側開始對比 } // 沒有匹配的數據,則left右移一位,繼續對比 left++; } } // 最后將pivot放入index位置 arr[index] = pivot; return index; } }
指針交換法
代碼實現如下
import java.util.Arrays; public class QuickSort { public static void main(String[] args) { int[] arr = { 12, 45, 23, 67, 7, 1, 5, 21 }; quickSort(arr, 0, arr.length - 1); System.out.println(Arrays.toString(arr)); } public static void quickSort(int[] arr, int startIndex, int endIndex) { if (startIndex >= endIndex) { return; } int partitionIndex = getPartitionIndex(arr, startIndex, endIndex); quickSort(arr, startIndex, partitionIndex - 1); quickSort(arr, partitionIndex + 1, endIndex); } public static int getPartitionIndex(int[] arr, int startIndex, int endIndex) { int left = startIndex; int right = endIndex; int pivot = arr[startIndex]; while (left != right) { // 左側索引必須小於右側索引,當右側數據大於基准元素,則將右側元素向左移動一位,繼續判斷,直到找到比基准元素小的數據 while (left < right && arr[right] > pivot) { right--; } // 左側索引必須小於右側索引,當左側數據小於等於基准元素,則將左側元素右移一位,繼續判斷,直到找到比基准元素大的數據位置 while (left < right && arr[left] <= pivot) { // 此處必須是左側元素“小於等於”基准元素 left++; } if (left < right) { // 通過以上兩輪while循環,已經找到左側大於基准元素的數據和右側小於基准元素的數據,交換它們 int tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; } } // 此時right和left值是相同的,將基准元素與重合位置元素交換 arr[startIndex] = arr[left]; arr[left] = pivot; return left; } }
討論
1、以上兩種方式只有在獲取分區索引的代碼不一樣,其他都一樣
2、指針交換法中,第二個內層while循環為什么必須要小於等於基准元素才行?
假設去掉等於,那么我們從左側取的第一個值是12,pivot為12,則arr[left]<pivot條件不成立,那么則left的值就不會變化,此時在下面的交換中,arr[left]的值始終不變,最終的排序結果也將是錯誤的,所以需要是小於等於基准元素
3、以上兩種方式是否有優化空間
在兩種處理方式的getPartitionIndex方法返回值前添加打印數組語句,輸出結果分別為
挖坑填數法輸出
[5, 1, 7, 12, 67, 23, 45, 21] [1, 5, 7, 12, 67, 23, 45, 21] [1, 5, 7, 12, 21, 23, 45, 67] [1, 5, 7, 12, 21, 23, 45, 67] [1, 5, 7, 12, 21, 23, 45, 67] [1, 5, 7, 12, 21, 23, 45, 67]
指針交換法輸出
[7, 5, 1, 12, 67, 23, 45, 21] [1, 5, 7, 12, 67, 23, 45, 21] [1, 5, 7, 12, 67, 23, 45, 21] [1, 5, 7, 12, 21, 23, 45, 67] [1, 5, 7, 12, 21, 23, 45, 67] [1, 5, 7, 12, 21, 23, 45, 67] [1, 5, 7, 12, 21, 23, 45, 67]
可以發現,挖坑填數法,在第三次輸出時已經是一個有序集合了,指針交換法在第四次輸出后也是一個有序集合,所以上述兩種代碼可以進行優化處理
優化,待添加。。。。。。