圖文講解 QuickSort 快速排序演算法(Java版)
什麼是快速排序?
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列。
演算法原理
單單看以上解釋還是有些模糊,可以通過例項來理解它,下面通過一組資料來進行排序過程的解析:
原陣列:{3,7,2,9,1,4,6,8,10,5}
期望結果:{1,2,3,4,5,6,7,8,9,10}

花了點時間擼了下面這張快速排序示意圖:

快速排序示意圖
步驟解析:
1)一開始選定陣列的最後一個元素5作為基準值,也就是最終排序結果應該是以5為界限劃分為左右兩邊。
2)從左邊開始,尋找比5大的值,然後與5進行調換(因為如果比5小的值本來就應該排在5前面,比5大的值調換之後就去到了5的後面),一路過來找到了7,將7與5調換,結束此次遍歷。
3)從右邊開始,由於7已經是上一輪排好序的便不再動它,從10開始,一路向左遍歷,尋找比5小的值,然後與5進行調換(因為如果比5大的值本來就應該排在5後面,比5小的值調換之後就去到了5的後前面),一路過來找到了4,將4與5調換,結束此次遍歷。
4)從左邊開始,由於3和4都是前兩輪已經排好序的便不再動它,從2開始,一路向右遍歷,尋找比5大的值,然後與5進行調換(道理同步驟2),一路過來找到了9,將9與5調換,結束此次遍歷。
5)從右邊開始,由於排在9後面的那幾個數字都是上幾輪排好序的便不再動它,從1開始,一路向右遍歷,尋找比5小的值,然後與5進行調換(道理同步驟3),一下子就找到了1,將1與5調換,結束此次遍歷。
6)這個時候,發現5的左右兩邊都是排好序了的,所以結束此輪排序,5的左右兩邊抽出來各自進行下一輪的排序,規則同上,直到無法再拆分下去,即完成了整體的快速排序。
演算法實現
既然思路理清了,程式碼就容易上手了:
/** * 快速排序 * @author Android小Y */ public class QuickSort { /** * 將陣列的某一段元素進行劃分,小的在左邊,大的在右邊 * @param a * @param start * @param end * @return */ public static int divide(int[] a, int start, int end){ //每次都以最右邊的元素作為基準值 int base = a[end]; //start一旦等於end,就說明左右兩個指標合併到了同一位置,可以結束此輪迴圈。 while(start < end){ while(start < end && a[start] <= base) //從左邊開始遍歷,如果比基準值小,就繼續向右走 start++; //上面的while迴圈結束時,就說明當前的a[start]的值比基準值大,應與基準值進行交換 if(start < end){ //交換 int temp = a[start]; a[start] = a[end]; a[end] = temp; //交換後,此時的那個被調換的值也同時調到了正確的位置(基準值右邊),因此右邊也要同時向前移動一位 end--; } while(start < end && a[end] >= base) //從右邊開始遍歷,如果比基準值大,就繼續向左走 end--; //上面的while迴圈結束時,就說明當前的a[end]的值比基準值小,應與基準值進行交換 if(start < end){ //交換 int temp = a[start]; a[start] = a[end]; a[end] = temp; //交換後,此時的那個被調換的值也同時調到了正確的位置(基準值左邊),因此左邊也要同時向後移動一位 start++; } } //這裡返回start或者end皆可,此時的start和end都為基準值所在的位置 return end; } /** * 排序 * @param a * @param start * @param end */ public static void sort(int[] a, int start, int end){ if(start >= end){ //如果只有一個元素,就不用再排下去了 return; } else{ //如果不止一個元素,繼續劃分兩邊遞迴排序下去 int partition = divide(a, start, end); sort(a, start, partition-1); sort(a, partition+1, end); } } }
採用幾組資料測試了下結果
public static void main(String[] args) { int[] a = new int[]{2,7,4,5,10,1,9,3,8,6}; int[] b = new int[]{1,2,3,4,5,6,7,8,9,10}; int[] c = new int[]{10,9,8,7,6,5,4,3,2,1}; int[] d = new int[]{1,10,2,9,3,2,4,7,5,6}; sort(a, 0, a.length-1); System.out.println("排序後的結果:"); for(int x : a){ System.out.print(x+" "); } }
演算法優缺點
快速排序最“快”的地方在於左右兩邊能夠快速同時遞迴排序下去,所以最優的情況是基準值剛好取在無序區的中間,這樣能夠最大效率地讓兩邊排序,同時最大地減少遞迴劃分的次數。此時的時間複雜度僅為O(NlogN)。快速排序也有存在不足的情況,當每次劃分基準值時,得到的基準值總是當前無序區域裡最大或最小的那個元素,這種情況下基準值的一邊為空,另一邊則依然存在著很多元素(僅僅比排序前少了一個),此時時間複雜度為O(N*N)。快速排序的速度快慢關鍵在於基準值的選取,它決定了劃分次數以及比較次數,決定了快排的效率,因此,還有一些針對於基準值選取的優化方法,例如“三資料取中法”等,能夠有效優化快速排序存在的不足之處。
結語
如果將日常寫程式碼比作擰螺絲,那資料結構和演算法就好比一個扳手,單純只是為了編碼而編碼,就好比徒手擰螺絲,有時候會特別吃力,而且擰完的效果也可能不佳,但如果藉助扳手,就可以更高效精準地完成我們的工作。以上是本人對快速排序的一點淺見,如果覺得寫得還不錯的動動小手點個喜歡~~
GitHub: GitHubZJY
CSDN部落格: IT_ZJYANG
簡 書: Android小Y
最後祝大家元宵佳節快樂~~~