---
# System prepended metadata

title: Lecture 06 - Quick Sort
tags: [課堂筆記, 演算法]

---

# Lecture 06 - Quick sort
> 課程內容 : 中興大學資工系 范耀中教授 (112 學年度第 2 學期)
>
> 其他科目內容請參見 [[Here]](https://hackmd.io/@ChenZE/By4SOO6Jyl)

快速排序法(Quick sort)與 Merge sort 類似，都是使用 divide and conquer 的概念，將大的陣列分成小問題後逐一解決。但 Quick sort 引入了分割(partition)的概念來分割小問題

## 1. Quick sort 演進
### 1.1 Idea (1): Partition items 分割
> Procedure
> 1. 選擇一個基準(pivot)
> 2. 從左開始掃描，比基準小的放左側，比基準大的放右側
> 3. 結束後該基準就會是在正確的位置，且左右分別有比基準小/大的子陣列
> 4. 對左右的子陣列繼續做上述動作

假設陣列 `[4, 2, 6, 1, 5, 7, 3]` 並以 `4` 作為基準

| Scan | Comparison   | Left array  | Right array |
| :-:  | :-           | :-          | :-          |
| 4    | Nothing      | `[ ]`       | `[ ]`       |
| 2    | Less than    | `[2]`       | `[ ]`       |
| 6    | Greater than | `[2]`       | `[6]`       |
| 1    | Less than    | `[2, 1]`    | `[6]`       |
| 5    | Greater than | `[2, 1]`    | `[6, 5]`    |
| 7    | Greater than | `[2, 1]`    | `[6, 5, 7]` |
| 3    | Less than    | `[2, 1, 3]` | `[6, 5, 7]` |

最後結果為 `[2, 1, 3, 4, 6, 5, 7]`，可以發現基準元素 `4` 已經排好，且左邊是比 `4` 小的陣列，右邊是比 `4`  大的陣列。再繼續對左邊及右邊做排序

![S__2203650](https://hackmd.io/_uploads/S1hnk5640.jpg)

:::info
複雜度 : $N \cdot \log N$
缺點 : 浪費兩倍的記憶體空間
:::

![S__2203651](https://hackmd.io/_uploads/HJcFg9TNR.jpg)

### 1.2 Solution : In-place
> Procedure
> 1. 同樣選擇一個基準(pivot)元素
> 2. 由左至右開始掃描整個陣列
> 3. 若遇到比基準值大的元素，就從陣列尾端**依序**拿一個元素做交換
> 4. 若遇到比基準值小的元素，就直接與基準值交換
> 5. 直到所有元素都被交換過，則該基準值已被排好
> 6. 同樣可以得到兩個子陣列，依序做上述操作

假設陣列 $[4,\ 5,\ 3,\ 1,\ 6,\ 7,\ 8]$ 並以 $4$ 作為基準

| Scan | Comparison | Result                                            |
| :-:  | :-:        | :-:                                               |
| 4    | Nothing    | $[4,\ 5,\ 3,\ 1,\ 6,\ 7,\ 8]$                     |
| 5    | $>$        | $[4,\ 8,\ 3,\ 1,\ 6,\ 7,\ \bf{5}]$                |
| 8    | $>$        | $[4,\ 7,\ 3,\ 1,\ 6,\ \bf{8},\ \bf{5}]$           |
| 7    | $>$        | $[4,\ 6,\ 3,\ 1,\ \bf{7},\ \bf{8},\ \bf{5}]$      |
| 6    | $>$        | $[4,\ 1,\ 3,\ \bf{6},\ \bf{7},\ \bf{8},\ \bf{5}]$ |
| 1    | $<$        | $[1,\ 4,\ 3,\ \bf{6},\ \bf{7},\ \bf{8},\ \bf{5}]$ |
| 3    | $<$        | $[1,\ 3,\ 4,\ \bf{6},\ \bf{7},\ \bf{8},\ \bf{5}]$ |

**註 : 粗體字表示已經換過位置**

到最後因為 $6$ 已經交換過位置，因此排序結束且基準元素 $4$ 已經排好位置，左右兩側分別是比 $4$ 小/大的子陣列，將左右子陣列依序做上述操作

![S__2203652](https://hackmd.io/_uploads/BypQu9pEA.jpg)

:::info
優點 : 只需要一個 Array 的空間
缺點 : 會產生不必要的搬移，例如一開始明明 $5>4$ 但卻換了一個更大的 $8$ 來到前面
:::

### 1.3 Solution : 避免不必要的搬移
> Procedure
> 1. 同樣選定一個基準值(pivot)
> 2. 從陣列左右兩側同時做掃瞄
> 3. 左側(Left scan pointer)尋找比基準值大的元素
> 4. 右側(Right scan pointer)尋找比基準值小的元素
> 5. 找到以後做交換
> 6. 當左右交錯後，掃描停止，並取當前基準值與 Right scan pointer 交換

假設陣列 $[4,\ 5,\ 3,\ 1,\ 6,\ 7,\ 2,]$ 並以 $4$ 作為基準值

| Left pointer | Array                                             | Right pointer |
| :-:          | :-:                                               | :-:           |
| 5            | $[4,\ \overline 5,\ 3,\ 1,\ 6,\ 7,\ \mathbf{2},]$ | 2             |
| 3            | $[4,\ 2,\ \overline 3,\ 1,\ 6,\ \mathbf{7},\ 5,]$ | 7             |
| 1            | $[4,\ 2,\ 3,\ \overline 1,\ \mathbf{6},\ 7,\ 5,]$ | 6             |
| 6            | $[4,\ 2,\ 3,\ \mathbf{1},\ \overline 6,\ 7,\ 5,]$ | 1             |
|              | $[\mathbf{1},\ 2,\ 3,\ 4,\ \overline 6,\ 7,\ 5,]$ |               |

基準值 $4$ 已經排好，左右兩側分別為比 $4$ 小與大的子陣列，兩側繼續做上述操作

![S__2203654](https://hackmd.io/_uploads/BJVu3oaNR.jpg)


以上改進後的版本就是 Quick sort 的核心內容

#### 缺點
考慮遞減排序的陣列 $[7,\ 6,\ 5,\ 4,\ 3,\ 2,\ 1]$ 並以 $7$ 作為基準值

| Left pointer | Array                                             | Right pointer |
| :-:          | :-:                                               | :-:           |
| 6            | $[7,\ \overline 6,\ 5,\ 4,\ 3,\ 2,\ \mathbf{1}]$  | 1             |
| 5            | $[7,\ 6,\ \overline 5,\ 4,\ 3,\ 2,\ \mathbf{1}]$  | 1             |
| 4            | $[7,\ 6,\ 5,\ \overline 4,\ 3,\ 2,\ \mathbf{1}]$  | 1             |
| 3            | $[7,\ 6,\ 5,\ 4,\ \overline 3,\ 2,\ \mathbf{1}]$  | 1             |
| 2            | $[7,\ 6,\ 5,\ 4,\ 3,\ \overline 2,\ \mathbf{1}]$  | 1             |
| 1            | $[7,\ 6,\ 5,\ 4,\ 3,\ 2,\ \overline{\mathbf{1}}]$ | 1             |
|              | $[\overline{\mathbf{1}},\ 6,\ 5,\ 4,\ 3,\ 2,\ 7]$ |               |

從上述會發現排完後的陣列是 $[1,\ 6,\ 5,\ 4,\ 3,\ 2,\ 7]$，但尚未排序完成


## 2. Quick sort analysis
### 2.1 Best and worse case
由上述最後的例子可以發現 quick sort 的 worse case 以及 best case 所對應的情況。當挑選到最大或最小值作為基準值時，就是 quick sort 的 worse case，因為第一次 scan 後的結果這個基準值會在最左側或是最右側，此時的樹狀圖會是斜的 (如下圖)，複雜度為 $(N-1)+(N-2)+...=\cfrac{N^2}{2}$

![未命名](https://hackmd.io/_uploads/S1jUSA3ykx.png)

而 quick sort 的 best case ，是當選到的基準值是中位數，此時做完第一次 scan 後基準值會在中間，兩側的 sub-tree 會是平均分配的，複雜度是 $N \times \log N$。

### 2.2 Average case
假設 N 個元素的 array 做 quick sort 的複雜度是 $C_N$

$$
C_N = (N+1) + \frac{C_0+C_{N-1}}{N} + \frac{C_1+C_{N-2}}{N} + ... + \frac{C_{N-1}+C_{0}}{N}
$$

其中，

* $N+1$ 表示 left scan 與 right scan 掃過整個 array 並交錯的次數
* $\cfrac{C_0+C_{N-1}}{N}$ 中，$C_0+C_{N-1}$ 表示選到最小值 $C_0$ 所需要付出的成本，$\cfrac{1}{N}$ 表示選到最小值的機率
* 同理，$\cfrac{C_1+C_{N-2}}{N}$ 中，$C_1+C_{N-2}$ 表示選到第二小值 $C_1$ 所需要付出的成本，$\cfrac{1}{N}$ 表示選到最小值的機率

將上式兩側同 $\times N$ 後可得

$$
NC_N = N(N+1) + 2(C_0+C_1+...+C_{N-1})
$$

且由 $C_N$ 的算法可推得 $C_{N-1}$ 的複雜度如下 :

$$
(N-1)C_{N-1} = (N-1)N + 2(C_0+C_1+...+C_{N-2})
$$

將 $C_N$ 與 $C_{N-1}$ 兩式相減後可得 :

$$
NC_N - (N-1)C_{N-1} = 2N + 2C_{N-1}
$$

整理後可得 :

$$
\cfrac{C_N}{N+1} = \cfrac{C_{N-1}}{N} + \cfrac{2}{{N+1}}
$$

重複以上的式子做遞迴，可將上式化簡為 :

$$
\begin{align}
\cfrac{C_N}{N+1} &= \cfrac{C_{N-1}}{N} + \cfrac{2}{{N+1}}\\
&= \cfrac{C_{N-2}}{N-1} + \frac{2}{N} + \frac{2}{N+1}\\
&= \cfrac{C_{N-3}}{N-2} + \frac{2}{N-1} + \frac{2}{N} + \frac{2}{N+1}\\
&= \frac{2}{3} + \frac{2}{4} + \frac{2}{5} + ... + \frac{2}{N+1}
\end{align}
$$

:::info
為何最後沒有 $\cfrac{2}{1} + \cfrac{2}{2}$ ?
因為拆到最後的 $\cfrac{C_1}{2}$ 表示只剩下一個元素，沒有必要拆解
:::

將上移項後可得 :

$$
\begin{align}
C_N &= 2(N+1)(\frac{1}{3} + \frac{1}{4} + \frac{1}{5} + \frac{1}{N+1})\\
&= 2(N+1) \int_3^{N+1}\frac{1}{x}dx\\
&=2(N+1)\ln N
\end{align}
$$

### 2.3 Summary of performance

因為 quick sort 的複雜度會依據所選定的基準值不同而有不同的結果，為了避免基準值選到最大或最小值，在排序前會先打亂 array 的順序。

然而，再沒有隨機打亂的情況下，該如何避免選到極值 ?可以嘗試挑選 3 個數並取其中位數，就可保證不是最大或最小值。