首先選一個基準 pivot,而後過一遍數組,java
這樣一來,這個 pivot 的位置就肯定了,也就是排好了 1 個元素。算法
而後對 pivot 左邊 👈 的數排序,
對 pivot 右邊 👉 的數排序,
就完成了。數組
那怎麼排左邊和右邊?dom
答:一樣的方法。ui
因此快排也是用的分治法的思想。spa
選擇一個 pivot,就把問題分紅了指針
這兩個問題。code
就是最開始描述的方法,直到每一個區間內沒有元素或者只剩一個元素就能夠返回了。blog
放在一塊兒天然就是。排序
可是如何選擇這個 pivot?
取中間的?
取第一個?
取最後一個?
好比選最後一個,就是 3.
而後咱們就須要把除了 3 以外的數分紅「比 3 大」和「比 3 小」的兩部分,這個過程叫作 partition(劃分)。
這裏咱們仍然使用「擋板法」的思想,不用真的弄兩個數組來存這兩部分,而是用兩個擋板,把區間劃分好了。
咱們用「兩個指針」(就是擋板)把數組分紅「三個區間」,那麼
那麼初始化時,咱們要保證「未排序區間」可以包含除了 3 以外的全部元素,因此
這樣左邊和右邊的區間就成了:
注意 ⚠️ i, j 是不包含在左右區間裏的呢。
那咱們的目的是 check 未排序區間裏的每個數,而後把它歸到正確的區間裏,以此來縮小未排序區間,直到沒有未排序的元素。
從左到右來 check:
5 > 3, 因此 5 要放在右區間裏,因此 5 和 j 指向的 0 交換一下:
這樣 5 就排好了,指針 j --,這樣咱們的未排序區間就少了一個數;
0 < 3,因此就應該在左邊的區間,直接 i++;
2 < 3,同理,i++;
1 < 3,同理,i++;
因此當兩個指針錯位的時候,咱們結束循環。
可是還差了一步,3 並不在正確的位置上呀。因此還要把它插入到兩個區間中間,也就是和指針 i 交換一下。
齊姐聲明:這裏並不鼓勵你們把 pivot 放最左邊。
基本全部的書上都是放右邊,既然放左右都是同樣的,咱們就按照你們默認的、達成共識的來,不必去「標新立異」。
就好比圍棋的四個星位,可是講究棋道的就是先落本身這邊的星位,而不是伸着胳膊去夠對手那邊的。
那當咱們把 pivot 換回到正確的位置上來以後,整個 partition 就結束了。
以後就用遞歸的寫法,對左右兩邊排序就行了。
最後還有兩個問題想和你們討論一下:
答:並很差。
由於咱們是想把數組分割的更均勻,均勻的時間複雜度更低;可是若是這是一個有序的數組,那麼老是取最後一個是最不均勻的取法。
因此應該隨機取 pivot,這樣就避免了由於數組自己的特色老是取到最值的狀況。
隨機選取以後,咱們仍是要把這個 pivot 放到整個數組的最右邊,這樣咱們的未排序區間纔是連續的,不然每次走到 pivot 這裏還要想着跳過它,心好累哦。
class Solution { public void quickSort(int[] array) { if (array == null || array.length <= 1) { return; } quickSort(array, 0, array.length - 1); } private void quickSort(int[] array, int left, int right) { // base case if (left >= right) { return; } // partition Random random = new Random(); // java.util 中的隨機數生成器 int pivotIndex = left + random.nextInt(right - left + 1); swap(array, pivotIndex, right); int i = left; int j = right-1; while (i <= j) { if (array[i] <= array[right]) { i++; } else { swap(array, i, j); j--; } } swap(array, i, right); //「分」 quickSort(array, left, i-1); quickSort(array, i+1, right); } private void swap(int[] array, int x, int y) { int tmp = array[x]; array[x] = array[y]; array[y] = tmp; } }
這裏的時空複雜度和分的是否均勻有很大關係,因此咱們分狀況來講:
若是每次都能差很少均勻分,那麼
若是每次都能取到最大/最小值,那麼遞歸樹就變成了這個樣子:
如上圖所示:O(n^2)
這棵遞歸樹的高度就變成了 O(n).
實際呢,大多數狀況都會接近於均勻的狀況,因此均勻的狀況是一個 average case.
爲何看起來最好的狀況其實是一個平均的狀況呢?
由於即便若是沒有取到最中間的那個點,好比分紅了 10% 和 90% 兩邊的數,那其實每層的時間仍是 O(n),只不過層數變成了以 9 爲底的 log,那總的時間仍是 O(nlogn).
因此快排的平均時間複雜度是 O(nlogn)。
那你應該能看出來了,在 swap 的時候,已經破壞了元素之間的相對順序,因此快排並不具備穩定性。
這也回答了咱們開頭提出的問題,就是
爲何對於 primitive type 使用快排,
爲何對於 object 使用歸併,
以上就是快排的全部內容了,也是很常考的內容哦!那下一篇文章我會講幾道從快排引伸出來的題目,猜猜是什麼?😉