# 快速排序法
by: hush
---
## 概念
----
1. 先**隨機選擇**一個基準點(pivot)
2. 把比它小的元素放在它的左邊,大的放右邊
3. 對左右兩邊做重複步驟1, 2,直到剩一個元素
----
實作上會利用雙指標來進行步驟2
(白板解釋)
----
code:
```cpp=
#include<bits/stdc++.h>
#define int long long
#define colinorz cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
using namespace std;
void Quick_sort(int l,int r,int a[]) //[l,r]
{
if (l>=r) return;
int piv=l,lp=l,rp=r;
while (lp<rp)
{
while (lp<rp && a[rp]>a[piv]) rp--;
while (lp<rp && a[lp]<=a[piv]) lp++;
swap(a[lp],a[rp]);
}
swap(a[piv],a[lp]);
Quick_sort(l,lp-1,a);
Quick_sort(lp+1,r,a);
}
signed main()
{
//colinorz;
int n,a[100005]; cin >> n;
for (int i=0;i<n;i++) cin >> a[i];
Quick_sort(0,n-1,a);
for (int i=0;i<n;i++) cout << a[i] << ' ';
}
```
----
- 時間複雜度:
期望上每次可以把陣列平均分成兩半遞迴,遞迴的深度會是$O(log_2n)$層,每一層的迴圈加起來會跑完整個陣列,時間複雜度是$O(n)$,所以總時間複雜度是$O(n log n)$
----
但剛剛算的是**期望上**的,若在最差的情況下,每次基準點都選到最大值,那遞迴深度會到$O(n)$層,總時間複雜度會是$O(n^2)$,故基準點通常會隨機選擇
----
```cpp=10
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
int piv=rnd()%(r-l+1)+l,lp=l,rp=r;
```
----
{%youtube 8hEyhs3OV1w%}
---
## 快速選擇
----
快速選擇(quick select)是快速排序法的一個應用
用來尋找無序序列中第$k$大(小)的值
----
- 第一個想法:
先將序列排序好再$O(1)$查詢
----
但是其實只需要讓序列第$k$個值是對的就好
所以可以小改一下快速排序遞迴的部分
- 若$pivot=k$就結束
- 若$pivot\lt k$就只呼叫右半邊
- 若$pivot\gt k$就只呼叫左半邊
----
```cpp=7
int Quick_select (int l,int r,int k)
{
int piv=l,lp=l,rp=r;
while (lp<rp)
{
while (lp<rp&& a[rp]>a[piv]) rp--;
while (lp<rp&& a[lp]<=a[piv]) lp++;
if (lp<rp) swap (a[lp],a[rp]);
}
swap (a[piv],a[lp]);
if (lp==k) return a[lp];
if (lp<k) return Quick_select (lp+1,r,k);
return Quick_select (l,lp-1,k);
}
```
----
- 時間複雜度:
1. 期望上每次陣列會剩一半,感性理解就是有可能多也可能少且機率一樣,所以平均起來就是剩一半
2. 每次都會跑過剩下的陣列,總次數是$n+\frac{n}{2}+\frac{n}{4}+...\approx 2n$,時間複雜度是$O(n)$
----
但若每次都找到極端值也有可能會到$O(n^2)$,所以pivot也建議隨機選取,以免被邪惡測資搞
----
- 練習題:
[csdc251](https://csdc.tw/problem/251/)
----
- 題解:
用quick-select找第$k-n$大的值
建議使用I/O優化(時間很緊)
----
AC code
```cpp=
#include <bits/stdc++.h>
#define colinorz cin.tie(0);cout.tie(0);ios::sync_with_stdio(false);
using namespace std;
int a[10005];
int quick_select (int l,int r,int k)
{
int piv=a[l],lp=l,rp=r;
while (lp<rp)
{
while (lp<rp&& a[rp]>piv) rp--;
while (lp<rp&& a[lp]<=piv) lp++;
if (lp<rp) swap (a[lp],a[rp]);
}
swap (a[l],a[lp]);
if (lp==k) return a[lp];
if (lp<k) return quick_select (lp+1,r,k);
return quick_select (l,lp-1,k);
}
int main()
{
colinorz;
int n,k; cin >> n;
for (int i=0;i<n;i++) cin >> a[i];
cin >> k;
cout << quick_select (0,n-1,n-k) << '\n';
}
```
---
# 謝謝大家
{"title":"快速排序法","description":"type: slide","contributors":"[{\"id\":\"b49547c8-0e7f-46d0-99b2-8a45dfee8e90\",\"add\":3539,\"del\":740}]"}