# 看動畫學算法之:排序-快速排序

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"簡介"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"快速排序也採用的是分而制之的思想。那麼快速排序和歸併排序的區別在什麼地方呢？"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歸併排序是將所有的元素拆分成一個個排好序的數組，然後將這些數組再進行合併。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而快速排序雖然也是拆分，但是拆分之後的操作是從數組中選出一箇中間節點，然後將數組分成兩部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"左邊的部分小於中間節點，右邊的部分大於中間節點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後再分別處理左邊的數組合右邊的數組。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"快速排序的例子"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假如我們有一個數組：29,10,14,37,20,25,44,15，怎麼對它進行快速排序呢？"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看一個動畫："}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/16/16cee42670c0457ac833d6205ec4824f.gif","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們再分析一下快速排序的步驟。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們選擇的是最左邊的元素29作爲中間點元素，然後將數組分成三部分：[0, 14, 15, 20, 25],[29],[44, 37]。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"中間節點29已經排好序了，不需要處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來我們再對左右分別進行快速排序。最後就得到了一個所有元素都排序的數組。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"快速排序的java代碼實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先來看最核心的部分partition，如何將數組以中間節點爲界，分成左右兩部分呢？"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們的最終結果，是要將array分割成爲三部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們選擇最左側的元素作爲中間節點的值。然後遍歷數組中的其他元素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假如m=middleIndex，k=要遍歷的元素index"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"考慮兩種情況，第一種情況是數組中的元素比中間節點的值要大。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4e/4e061e7201da99386d669bac6c087eed.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種情況下，m不需要移動，k+1繼續遍歷即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二種情況下，數組中的元素比中間節點的值要小。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/36/36bdd008130321a5a3591743d72f5f03.png","alt":"","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲m左邊的元素都要比中間節點的值要小，所以這種情況下m需要+1，即右移一位。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在m+1位置的元素要麼還沒有進行比較，要麼就是比中間節點的值要大，我們可以巧妙的將m+1位置的元素和k位置的元素互換位置，這樣仍然能夠保證m左側的元素要比中間節點的值要小。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將上面的分析總結成java代碼如下："}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" private int partition(int[] array, int i, int j) {\n //選擇最左側的元素作爲中心點,middleValue就是中心點的值\n int middleValue = array[i];\n int middleIndex = i;\n //從i+1遍歷整個數組\n for (int k = i+1; k <= j; k++) {\n //如果數組元素小於middleValue，表示middleIndex需要右移一位\n //右移之後，我們需要將小於middleValue的array[k]移動到middleIndex的左邊，\n // 最簡單的辦法就是交換k和middleIndex的值\n if (array[k] < middleValue) {\n middleIndex++;\n //交換數組的兩個元素\n swap(array, k , middleIndex);\n } //如果數組元素大於等於middleValue,則繼續向後遍歷,middleIndex值不變\n }\n // 最後將中心點放入middleIndex位置\n swap(array, i, middleIndex);\n return middleIndex;\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後我們需要將最左側的元素和中間節點應該在的index的元素互換下位置，這樣就將中間節點移動到了中間位置，並返回中間位置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再來看下divide的代碼："}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" public void doQuickSort(int[] array, int low, int high) {\n //遞歸的結束條件\n if (low < high) {\n //找出中心節點的值\n int middleIndex = partition(array, low, high);\n //數組分成了三部分：\n // a[low..high] ~> a[low..m–1], pivot, a[m+1..high]\n //遞歸遍歷左側部分\n doQuickSort(array, low, middleIndex-1);\n // a[m] 是中心節點，已經排好序了，不需要繼續遍歷\n //遞歸遍歷右側部分\n doQuickSort(array, middleIndex+1, high);\n log.info(\"QuickSort之後的數組:{}\",array);\n }\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"divide的代碼就很簡單了，找到中間節點的位置之後，我們再分別遍歷數組的左右兩邊即可。最後得到排好序的數組。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"隨機快速排序的java實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的例子中，我們的中間節點的選擇是數組的最左元素，爲了保證排序的效率，我們可以從數組中隨機選擇一個元素來作爲中間節點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" private int partition(int[] array, int i, int j) {\n //隨機選擇一個元素作爲中心點,middleValue就是中心點的值\n int randomIndex=i+new Random().nextInt(j-i);\n log.info(\"randomIndex:{}\",randomIndex);\n //首先將randomIndex的值和i互換位置,就可以複用QuickSort的邏輯\n swap(array, i , randomIndex);\n int middleValue = array[i];\n int middleIndex = i;\n //從i遍歷整個數組\n for (int k = i+1; k <= j; k++) {\n //如果數組元素小於middleValue，表示middleIndex需要右移一位\n //右移之後，我們需要將小於middleValue的array[k]移動到middleIndex的左邊，\n // 最簡單的辦法就是交換k和middleIndex的值\n if (array[k] < middleValue) {\n middleIndex++;\n //交換數組的兩個元素\n swap(array, k , middleIndex);\n } //如果數組元素大於等於middleValue,則繼續向後遍歷,middleIndex值不變\n }\n // 最後將中心點放入middleIndex位置\n swap(array, i, middleIndex);\n return middleIndex;\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的代碼，我們在分區的時候，先選擇出一個隨機的節點，然後將這個隨機的節點和最左側的元素交換位置，後面的代碼就可以重用上面的QuickSort的代碼邏輯了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"快速排序的時間複雜度"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面的分析我們可以看出，每次分區的時間複雜度應該是O(N)，而divide又近似二分法，所以總的時間複雜度是O(N logN)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文的代碼地址："}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/ddean2009/learn-algorithm/tree/master/sorting","title":null},"content":[{"type":"text","text":"learn-algorithm"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"本文作者：flydean程序那些事"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"本文鏈接："},{"type":"link","attrs":{"href":"http://www.flydean.com/algorithm-quick-sort/","title":null},"content":[{"type":"text","text":"http://www.flydean.com/algorithm-quick-sort/"}],"marks":[{"type":"italic"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"本文來源：flydean的博客"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"歡迎關注我的公衆號:程序那些事，更多精彩等着您！"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}