### 演算法 > 具有時效性,不會形成無窮迴圈,能夠解決問題的一道方法。 ### 分治演算法 > 將一個大問題拆解成許多的子問題 > 再由各子問題的**解**合併出答案 #### 1. 費伯納數列 * 直接遞迴 > 在遞迴函數中直接呼叫本身 ``` int Fun(...) { ... if(...) Fun(...) ... } ``` * 間接遞迴 > 在遞迴函數中呼叫其他遞迴,再從其他遞迴呼叫回遞迴函數。 ``` int Fun(...) { . if(...) Fun2(...) ... } int Fun2(...) { . if (...) Fun(...) ... } ``` For example: ``` n! =nX(n-1) * (n-2) .....*1 ``` code ``` int factorial(int i) { int sum; if (i==0) /*終結*\ { return(1); } else { sum = i*factorial(i-1); /*sum =n*(n-1)*\ } return sum; } ``` For example2: > 第零項為0 第一項為1,每個項目皆由前兩項相加所得的數_費伯納序列(Fibonacci)。 code ``` int fib(int n) { if (n==0) return 0; if (n==1) return 1; else return fib(n-1)+fib(n-2);/*呼叫兩次*/ } ``` #### 2.河內塔問題 結論: > 步驟1: 將n-1盤子,從木樁1移到2 > 步驟2: 移動第n個最大的盤子,從木樁1移到3 > 步驟3: 將n-1盤子,從木樁2移到3 限制: > 1. 直徑較小套環永遠置於直徑較大的上方 > 2. 套還可隨意從木樁移到其他木樁 > 3. 每一次僅能移動一個套環,且為最上面開始移動 ![](https://hackmd.io/_uploads/rkeJDhe1a.png) code: ```c #include<stdio.h> void hanoi(int n, int p1, int p2, int p3) { if (n==1) /*出口*/ printf("第%d個套環從 %d 移到 %d\n",n,p1,p3); else { hanoi(n-1,p1,p3,p2); printf("第%d個套環從 %d 移到 %d\n",n,p1,p3); hanoi(n-1,p2,p1,p3); } } int main() { int n = 3; // 套環的數量 hanoi(n, 1, 2, 3); // 從柱子1移動到柱子3,使用柱子2作為中介 return 0; } ``` ### 排序演算法 #### 1.選擇排序 目的:找到數列中最小值往前排。 ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #define N 10 /* run this program using the console pauser or add your own getch, system("pause") or input loop */ int data[N]; int main(void) { srand(time(NULL)); int i,j,k,p,tmp; for(i=0; i<N ; i++){ data[i] =rand()%100; } for(i=0; i<N ; i++){ printf("%d ",data[i]); } k=0; for(j=0;j<N-1;j++) { for(i=j+1 ; i<N ; i++){ if(data[i]<data[k]) k=i; } tmp=data[j]; data[j]=data[k]; data[k]=tmp; } printf("\n"); for(i=0; i<N ; i++){ printf("%d ",data[i]); } } ``` #### 2.插入排序 目的:從頭開始,一直往前找做交換。 ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #define N 10 int data[N]; int main(void) { srand(time(NULL)); int i,j,k,p,tmp; for(i=0; i<N ; i++){ data[i] =rand()%100; } for(i=0; i<N ; i++){ printf("%3d ",data[i]); } printf("\n"); for(j=1;j<N;j++) { tmp=data[j]; for(i=j;i>0;i--){ if(data[i-1]>data[i]){ data[i] =data[i-1]; data[i-1]=tmp; } else break; } for(i=0; i<N ; i++){ printf("%3d ",data[i]); } printf("\n"); } } ``` #### 3.快速排序(quick sort) 核心:每次開始定一個虛擬值k(Pivot),使數列切割點k的左側全部小於這個值,右側則大於這個值,最終就能抵達正確位置,再切割後兩邊繼續選k,如此循環便能由小到大排序。分割後數列繼續切割,不斷下去就能完成排序。 屬於一個較不穩定的排序,若每次挑選中間值為最大或最小,會造成最壞情況 $$時間:O(n²)$$$$空間:O(n)$$ > 最壞時變成單鏈深度為n,遞迴處理量: $$\underbrace{n+(n-1)+\cdots+1}_{\frac {n\times(n-1)}{2}}\;=\;O(n)\rightarrow\times\;O(n)\;=\; O(n^2)$$ * 處理量乘上深度 > 陣列大量重複元素或已經排序好的陣列選擇頭與尾會造成worse case 較好以及平均而言為: $$時間:O(n\times \log_2n)、空間:O(\log_2n)。$$ [題目:a104. 排序](https://zerojudge.tw/ShowProblem?problemid=a104) ![image](https://hackmd.io/_uploads/H1gVWzqYlx.png) > 由於選擇位置+是否有大量重複元素,a233只得45%。 程式碼: ```cpp! #include<iostream> #include<vector> using namespace std; void quick_sort(vector<int> &nums,int ,int ); int partition_find(vector<int> &nums,int ,int); int partition_find(vector<int> &nums, int l, int r) { int privot = nums[l]; int i=l+1,j=r; while(i<=j){ if(nums[i]<privot){ i++; } else if(nums[j]>=privot){ j--; } else{ swap(nums[i],nums[j]); } } swap(nums[l], nums[j]); return j; } void quick_sort(vector<int> &nums, int l, int r) { if (l < r) { int privot = partition_find(nums, l, r); quick_sort(nums, l, privot - 1); // left quick_sort(nums, privot + 1, r); // right } } int main(void){ int n; vector<int> nums; while(scanf("%d",&n)!=EOF){ nums.clear(); int a; for(int i=0;i<n;i++){ scanf("%d",&a); nums.push_back(a); } quick_sort(nums,0,n-1); for(int i=0;i<n-1;i++){ printf("%d ",nums[i]); } printf("%d",nums[n-1]); } } ``` #### 4.合併排序(Merge sort) 核心:將數列拆解兩個兩個一組,排序後合併,再排序合併。 簡單來說分為拆分與合併兩步驟。 拆分: 1. 把大數列切一半成為兩個數列 2. 把切好的兩個數列再各自切一半 3. 重複步驟2直到每個數列都只剩一個元素 合併: 1. 排序兩個只剩一個元素的數列並合併 2. 把兩邊排序好的數列合併並排序成一個數列 3. 重複步驟2直到所有數列都合併成一個完整數列 平均而言: $$總共要處理\;O(\log_2n),每次合併時間複雜度\;O(n)\;(排序部分)。因此\;O(n\times\log_2n)。 空間複雜度\;O(n),需要另外的空間。$$ [題目:a233. 排序法~~~ 挑戰極限](https://zerojudge.tw/ShowProblem?problemid=a233) ![image](https://hackmd.io/_uploads/B1Yx2hHKee.png) 程式碼: ```cpp! #include<iostream> #include<vector> using namespace std; vector<int> v; int data[1000000]; void merge_sort(int ,int ); void merge_sort(int L,int R){ if (L==R) return; //拆分 int M=(L+R)/2; merge_sort(L,M); merge_sort(M+1,R); //合併 int i=L,j=M+1,k=L; while(i<=M || j<=R){ if( j>R || (i<=M && v[i]<v[j])){ data[k]=v[i]; i++; } else{ data[k]=v[j]; j++; } k++; } //記得複製成新的陣列 for(i=L;i<=R;i++){ v[i]=data[i]; } } int main(void){ cin.tie(0),cout.tie(0), ios::sync_with_stdio(false); int N,a; cin>>N; for(int i=0;i<N;i++) { cin>>a; v.push_back(a); } //merge sort merge_sort(0,N-1); for(int i=0;i<N-1;i++){ cout<<v[i]<<' '; } cout<<v[N-1]; } ``` #### 5.各類排序 做比較 ```c #include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> int N; int data[1000000]; int test[1000000]; void bubble_sort(int,int); //氣泡排序 void selection_sort(int,int); //選擇排序 void merge_sort(int,int); void show_test(); //顯示 test 陣列結果 int main(void){ printf("請輸入要產生的數量(1000000以內): "); scanf("%d",&N); if(N>=2000000){ printf("數量太大"); return 0; } clock_t start_t; srand(time(NULL)); int i,j,k,p,tmp; for(i=0; i< N; i++) data[i]=(rand()%30000)*(rand()%30000); //產生資料 for(i=0; i< N; i++) test[i]=data[i]; //恢復資料 start_t = clock(); //開始計時 merge_sort(0,N-1); //排序ing... printf("合併排序: %.3f\n", (double)(clock() - start_t) / CLOCKS_PER_SEC); for(i=0; i< N; i++) test[i]=data[i]; //恢復資料 start_t = clock(); //開始計時 bubble_sort(0,N-1); //排序ing... printf("氣泡排序: %.3f\n", (double)(clock() - start_t) / CLOCKS_PER_SEC); for(i=0; i< N; i++) test[i]=data[i]; //恢復資料 start_t = clock(); //開始計時 selection_sort(0,N-1); //排序ing... printf("選擇排序: %.3f\n", (double)(clock() - start_t) / CLOCKS_PER_SEC); } void show_test(){ int a; for(a=0;a<N;a++) printf("%d ",test[a]); printf("\n"); } void bubble_sort(int a,int b){ int i,j,k,tmp; for(i=b;i>0;i--){ for(j=1;j<=i;j++){ if(test[j-1]>test[j]){ tmp=test[j-1],test[j-1]=test[j],test[j]=tmp; } } } } void selection_sort(int a,int b){ int i,j,k,tmp; for(i=a;i<=b;i++){ k=i; for(j=i+1;j<=b;j++){ if(test[j]<test[k]){ k=j; } } tmp=test[i],test[i]=test[k],test[k]=tmp; } } int result[1000000]; void merge_sort(int L, int R) { if(L==R) return; int M=(L+R)/2; merge_sort(L,M); merge_sort(M+1,R); int i=L,j=M+1,p=L; while(i<=M || j<=R) { if(j>R || (i<=M && test[i]<test[j])) { result[p]=test[i]; i++; } else { result[p]=test[j]; j++; } p++; } for(i=L;i<=R;i++) { test[i]=result[i]; } } ``` #### 6.基數演算法(Radix sort)[MDS+LSD] LSD 是從鍵值的最邊開始,所以適合位數小的資料來做排序。反之MDS從最左邊,也就是最高位數來開始排序。如果位數很多的話,MSD會來得更有效率一些。 ##### MDS ### 搜尋演算法 #### 1.二分搜 核心:數列必須經過排序。並且左右邊界一個指著可能的答案;一個指著不可能的答案,每次搜尋從中間開始搜尋。當答案比搜尋出來的結果大,表示答案在右側,更新左邊界,反之更新右邊界,記住過程中仍要遵守左右邊界一個可能一個不可能(注意是否+1;或是在while中做判斷) $$時間複雜度:\;O(\log_2n),範圍\log_2n\sim \log_2 (n+1)。$$ [題目:d732. 二分搜尋法](https://zerojudge.tw/ShowProblem?problemid=d732) ![image](https://hackmd.io/_uploads/H1zzBi6Ogl.png) 程式碼: ```cpp! #include<bits/stdc++.h> using namespace std; int main(void){ int n,k; scanf("%d%d",&n,&k); int i,a; vector<int> v; for(i=0;i<n;i++){ scanf("%d",&a); v.push_back(a); } v.push_back(-1); for(i=0;i<k;i++){ scanf("%d",&a); int l=0,r=n; int ans=0; if (a>v[n-1] || a<=0){ printf("%d\n",ans); continue; } while(l<r){ int mid=(r+l)/2; if(v[mid]==a){ ans=mid+1; break; } else if(v[mid]>a){ r=mid; } else if(v[mid]<a){ l=mid+1; } } printf("%d\n",ans); } } ``` #### 2.內插搜尋法 核心:二分搜改良版。預測資料時使用斜率+二分搜藉以逼近答案。因此資料越平均分布時間複雜度越小。 時間複雜度:平均優於 $O(log_2 n)$ 程式碼: ```cpp! int l=0,r=n-1,ans=0; while(l<=r){ int mid=l+((a-v[l])/(v[r]-v[l]))*(r-l); if(v[mid]==a){ ans=mid+1; break; } if(v[mid]>a){ r=mid-1; } else{ l=mid+1; } } printf("%d\n",ans); ``` ### 經典演算法 #### 貪心 #### 動態規劃(DP) #### 枚舉 #### 質數求解 ##### 歐拉篩法 將i*質數表內去刪除非質數,將篩內true送入質數表內,邊篩選邊做表。 ```cpp #include <iostream> #include <vector> #define N 1000000 using namespace std; int main(void) { bool sieve[N]; for (int i=0;i<N;i++) { sieve[i] = true; } vector<int> prime; for (int i=2;i<N;i++) { if (sieve) prime.push_back(i); for (int j=0 ; i*prime[j] < N ;j++) { sieve[i*prime[j]] = false; if (i%prime[j]==0) break; } } int n; cin>>n; if (n<N) { if (sieve[n]) cout<<"Prime"; else cout<<"Not Prime"; } else { bool ok=true; for (int p:prime) { if (n%p==0) { cout<<"Not Prime"; ok=false; break; } } if (ok) cout<<"Prime"; } return 0; } ```