---
# System prepended metadata

title: C++演算法(上)

---

### 演算法
> 具有時效性，不會形成無窮迴圈，能夠解決問題的一道方法。
### 分治演算法
>  將一個大問題拆解成許多的子問題
>  再由各子問題的**解**合併出答案
#### 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;
}
```