# 排序與搜尋
## 5/28 資訊讀書會
---
# 排序
----
## 排序
* 排序是將一群資料根據某種順序排列,通常是由小到大。
* c++有內建排序函式`sort()`可以將一段指標範圍內的資料依照預設或給定的比較方法做排序。
* 也可以自己寫排序。
----
## `sort()`函式
* `sort(begin, end, comp)`有兩個必要參數和一個預設參數。
* 前兩個參數是排序的起點和終點的指標(位置),不過終點代表排序結束的下一位。
* 第三個參數是比較,預設是小的會在前面。
----
## `comp`比較運算
* `comp`的預設是嚴格小於,也就是前面的比後面的小才會成立。
* `comp`有列一個內建函式`greater`,也就是嚴格大於。
* `comp`函式必須是嚴格比較,也就是相等時不能回傳`true`,不然會卡住。
* `sort`會把回傳`true`的放前面。
----
### 例子:
```cpp=
#include <bits/stdc++.h>
using namespace std;
bool comp(pair<int, int> a, pair<int,int> b){
if(a.first+a.second==b.first+b.second){
return (a.first<b.first);
}
else return (a.first+a.second<b.first+b.second);
}
int main() {
int n=10;
pair<int,int> arr1[n];
// 隨機產生
for (int i=0;i<n;i++){
arr1[i]={rand()%10, rand()%10};
printf("(%d,%d) ", arr1[i].first, arr1[i].second);
}
printf("\n");
// 一般排序
sort(arr1, arr1+n);
for(int i=0; i<n; i++)
printf("(%d,%d) ", arr1[i].first, arr1[i].second);
printf("\n");
// 反向排序
sort(arr1, arr1+n, greater< pair<int,int> >());
for(int i=0; i<n; i++)
printf("(%d,%d) ", arr1[i].first, arr1[i].second);
printf("\n");
// 自訂排序
sort(arr1, arr1+n, comp);
for(int i=0; i<n; i++)
printf("(%d,%d) ", arr1[i].first, arr1[i].second);
printf("\n");
return 0;
}
```
### 一組結果
```
(3,6) (7,5) (3,5) (6,2) (9,1) (2,7) (0,9) (3,6) (0,6) (2,6)
(0,6) (0,9) (2,6) (2,7) (3,5) (3,6) (3,6) (6,2) (7,5) (9,1)
(9,1) (7,5) (6,2) (3,6) (3,6) (3,5) (2,7) (2,6) (0,9) (0,6)
(0,6) (2,6) (3,5) (6,2) (0,9) (2,7) (3,6) (3,6) (9,1) (7,5)
```
---
# 排序方法
----
## merge sort(合併排序法)
### 不建議自己寫
* merge sort是分治的方法,時間複雜度$O(NlogN)$。
* 核心程式是把兩串已經排好的資料合併成一串排好的資料,時間複雜度$O(N)$。
* 排列時會先不斷把資料分成兩半,直到每個都是一個,然後再不斷合併,總共執行$O(logN)$次。
----
## quick sort(快速排序法)
* quick sort會選擇一個值當基準,把所有比它大的放右邊,比它小的放左邊。於是基準就會在對的位置上,這個步驟的複雜度為$O(N)$。
* 然後會在兩邊再各自進行quick sort,直到每組只剩一個元素,此時就都會在正確的位置了。
* 平均時間複雜度$O(NlogN)$,最差複雜度$O(N^2)$,不過比merge sort省空間。
----
## bubble sort(泡沫排序法)
* bubble sort是每次掃過,發現有左邊比右邊小的,就交換左右。
* 因為每次掃過後,最大的都會移到右邊,所以右界可以每次少1。
* 最佳複雜度為$O(N)$,最差為$O(N^2)$。
* 因為每次未排序區內最大的都會浮上來,所以叫泡沫。
----
## selection sort(選擇排序法)
* 每次會在未排序區找到最小的,放在未排序區的左邊,加入已排序區。
* 複雜度$O(N^2)$。
----
## insertion sort(插入排序法)
* 一開始已排序區為第一個數字,每次排序會把未排序區的第一個放入已排序區。
* 要排序的會從尾端往回比較大小,將未排序區的數字插入到已排序區的適當位置。
* 複雜度$O(N^2)$。不過對於快排好的資料排序較快。
----
## comb sort(梳排序法)
* 改良自bubble sort和quick sort,不過bubble sort每次的間距都是1,而comb sort則是從全長遞減,通常是每次\*0.8。到1的時候再用bubble sort檢查與修正。
* 為了消除bubble sort中,在末端的小值會讓排序變很慢的問題。
* 時間複雜度最佳$O(N)$,最差$O(N^2)$。
* 因為在開始時效率較好,在間距便小時可改用insertion sort。
----
## shell sort(希爾排序法)
* 改良自insertion sort,不過往回比較的間距不是1,且會不斷變小。
* 最佳間距序列為(1, 5, 19, 41, 109,...),為兩個數列的交錯合併。
第一個為 $9*4^i-9*2^i+1$,
另一個為 $2^{i+2}*(2^{i+2}-3)+1$。
* 最糟複雜度$O(Nlog^2N)$,比$O(NlogN)$差一點。
* 也可以用 $2^k-1$,最糟複雜度 $O(N^{3/2})$
* 對幾乎排好的資料組有$O(N)$複雜度。
----
## heap sort(堆積排序)
* heap sort是利用堆積的性質的排序法。把陣列變成最大堆積來排序。
* 堆積類似完全二元樹,並且每個子節點的索引都大於父節點。
* 節點i的左子節點在$2𝑖+1$,右子節點在$2i+2$
節點i的父節點在$(i+1)/2$
* 複雜度$O(NlogN)$
* [補充點這個](https://www.youtube.com/watch?v=Q1yi1eaqN7I)
---
# 程式區
### 不建議自己寫
----
## merge sort
```cpp=
void merge(int *arr, int L, int M, int R){
int l = L; // 左半部執行到第幾個
int r = M+1; // 右半部執行到第幾個
int tmp[R-L], i = 0; // i是tmp的索引
while((l<M)&&(r<R)){ // 左邊和右邊都沒跑完
// 把比較小的放進去,到下一個
if(arr[l]<arr[r]){
tmp[i]=arr[l];
l++;
}
else{
tmp[i]=arr[r];
r++;
}
i++;
}
while(l<=M){ //剩下的也放進去
tmp[i]=arr[l];
i++; l++;
}
while(r<=R){
tmp[i]=arr[r];
i++; r++;
}
for(i=0; L+i<R; i++) arr[i+L] = tmp[i];
return;
}
void mergesort(int *arr, int L, int R){
int M = (L+R+1)/2;
if(L<R){
mergesort(arr, L, M); //排序左半部
mergesort(arr, M, R); // 排序右半部
merge(arr, L, M, R); // 合併
}
return;
}
```
----
## quick sort
```cpp=
void quicksort(int *arr, int l, int r){
if(l==r) return;
int p = arr[r]; // 把pivot訂為結尾
int idx = l;
for(int i=l; i<r; i++){
if(arr[i] < p){
swap(arr[idx], arr[i]);
idx++;
}
}
swap(arr[r], arr[idx]); // 把pivot移到它最後的地方
quicksort(arr, l, idx-1); // 排序左半部
quicksort(arr, idx+1, r); // 排序右半部
return;
}
```
----
## bubble sort
```cpp=
void bubblesort(int *arr, int l, int r;){
bool u = true; // 還要不要繼續排
for(int i=l; i<r-1; i++){
u=false;
for(int j=l; j<r-1-i; j++)
if(a[j] > a[j+1]){
swap(a[j], a[j+1]);
u = true;
}
if(!u) break;
}
return;
}
```
----
## selection sort
```cpp=
void selectionsort(int *arr, int l, int r){
for(int i=l; i<r; i++){
int idx=i;
for(int j=i; j<r; j++){ // 找最小
if(arr[idx]>arr[j]) idx=j;
}
swap(arr[idx], arr[i]);
}
return;
}
```
----
## insertion sort
```cpp=
void insertionsort(int *arr, int l, int r){
for(int i=l+1; i<r; i++){
int ins = arr[i]; // 儲存要排序的數
int j=i-1; // 往回比大小
while(j>=l && arr[j]>ins){
arr[j+1] = arr[j]; // insert不會變
j--;
}
arr[j+1] = ins; // while退出時j會-1,所以要+1
}
return;
}
```
----
## comb sort
```cpp=
void comb_sort(int *arr, int l, int r){
double d = 0.8;
int g = r-l; // 間隔
bool u=true; // 還要不要排
while(u || g>1) {
if(g>1)
g=(int)(g*d);
u=false;
for(int i=0; l+i+g<r; i++)
if(arr[i] > arr[i + g]) {
swap(arr[i], arr[i + g]);
u=true;
}
}
}
```
----
## shell sort
```cpp=
void shellsort(int *arr, int l, int r){
int d=1;
while(d < (r-l) / 3) {
d = 3*d+1;
}
while(d>=1) {
for(int i=d; l+i<r; i++) {
for(int j=l+i; j>=l+d && arr[j]<arr[j-d]; j-=d) {
swap(arr[j], arr[j - d]);
}
}
d /= 3;
}
}
```
----
## heap sort
```cpp=
// 調整為最大堆積
void max_heap(int *arr, int l, int f, int r) {
// 左子節點索引
int s = l+2*(f-l)+1;
while(s<r){ // 子節點在範圍內
// 選大的子節點
if(s+1<r && arr[s]<arr[s+1])
s++;
if(arr[f]>arr[s]) // 父節點比子節點大,調整完畢
return;
else{ // 否則交換父子內容,再往下一層比較
swap(arr[f], arr[s]);
f = s;
s = l+2*(f-l)+1;
}
}
}
void heapsort(int *arr, int l, int r){
// 從最後一個父節點開始調整
for (int i=(r+l)/2-1; i>=l; i--)
max_heap(arr, l, i, r);
// 先把第一個(最大)和最後一個交換,再調整沒排好的,直到排完
for (int i=r-1; i>l; i--) {
swap(arr[l], arr[i]);
max_heap(arr, l, l, i);
}
}
```
---
# 搜尋
----
## 搜尋
* 在一群資料中尋找某筆資料,通常用線性搜尋或二分搜。
* 線性搜尋就是一個一個找過去,用在未排序的資料,時間複雜度O(N)。
* 資料如果已經排序好,就可以用二分搜,會比線性搜尋快很多,時間複雜度O(logN)。
----
## 二分搜
* 二分搜是每次都跟中間比較,比中間的大就把範圍縮減成中間到結束,較小的話則是變成開始到中間。
* 因為每次的範圍都會減半,所以最多花log(2,N)次,而存取陣列、比較都是O(1)。
----
## 程式
```cpp=
int bsearch(int *arr, int l, int r, int x) {
int m;
while (l<=r) {
m=(l+r)/2;
if(arr[m]==x) return m;
if(arr[m]<x) l=m+1; //右半邊
else r=m-1; //左半邊
}
return -1; //沒找到
}
```
----
## 三分搜
* 三分搜是用來找出一組從遞減變成遞增(或反過來)的資料的最小值。
* 利用上下界不斷縮小範圍,直到找到最小值。
----
## 程式(凹口向上)
```cpp=
int tsearch(int *arr, int l, int r) {
int ml, mr;
while (l<r) {
ml=(2*l+r)/3;
mr=(l+2*r)/3;
if(arr[ml]>arr[mr]) l=ml; // 左邊比較大,往右
else r=mr;
}
return arr[l];
}
```
{"title":"排序與搜尋","contributors":"[{\"id\":\"00ad9127-6491-4b3d-829b-7847a217f8e5\",\"add\":16063,\"del\":8630}]","description":"5/28 資訊讀書會"}