c++演算法模板(一):基礎演算法模板:
1.高精演算法:
加法:
while(i<=a加數的位數|| i<=b加數的位數)
{ c[i]=a[i]+b[i]+x;
x=c[i]/10;
c[i]%=10;
i++;
}
注意:加法需要逆序儲存,因為如果正序儲存,那麼當加數相加會超過陣列的範圍。
減法:
While(lenc<=lena||lenc<=lenb)
{
If(a[i]<b[i])
{
a[i]+=10;
a[i+1]--;
}
c[i]=a[i]-b[i];
i++;
}
乘法:
for(i=1;i<=lena;i++)
{
x=0;
for(j=1;j<=lenb;j++)
{
c[i+j-1]=a[i]*b[j]+x+c[i+j-1];
x=c[i+j-1]/10;
c[i+j-1]%10;
}
c[i+lenb]=x;
}
Lenc=lena+lenb;
2.排序
<1>桶排序
#include<iostream> using namespace std; int a[10001]; int n,k; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>k; a[k]++; } for(int i=1;i<=10000;i++) { while(a[i]>0) { cout<<i<<" "; a[i]--; } } return 0; }
桶排序演算法,複雜度為常數。特別快但是要注意空間。適當的時候用雜湊的思想來解決。比如排序:1 4 100000000 7.一共就4個元素但是如果用正常的桶排序的話就要開一個100000001的陣列,太費空間。不如就雜湊一下。
<2>快排
#include<iostream> using namespace std; void qsort(int,int); int n; int a[1001]; int main() { cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; qsort(1,n); for(int i=1;i<=n;i++) cout<<a[i]<<" "; return 0; } void qsort(int l,int r) { int i,j,mid,p; i=1; j=r; mid=a[(l+r)/2]; do { while(a[i]<mid) i++; while(a[j]>mid) j--; if(i<=j) { p=a[i]; a[i]=a[j]; a[j]=p; i++; j--; } }while(i<=j); if(1<j) qsort(l,j); //這裡是一不是L if(i<r) qsort(i,r); }
快排的思想在於首先是把數先正好順序的排列一下。然後一個從頭找,一個從找,當前面的大於中間值,後面的小於中間值。這兩個交換。當然就有疑問當後面值交換過來比前面的值小怎麼辦。後面的兩個if語句就可以解決這種情況。
<3>歸併排序
#include<iostream>
using namespace std;
int a[101],r[101];
void mergesort (int ,int);
int main()
{
int n,i;
cin>>n;
for(i=1;i<=n;i++)
cin>>a[i];
mergesort(1,n);
for(i=1;i<=n;i++)
cout<<a[i]<<" ";
return 0;
}
void mergesort (int s,int t)
{
int m,i,j,k;
if(s==t) return ;
m=(s+t)/2;
mergesort(s,m);
mergesort(m+1,t);
i=s;
j=m+1;
k=s;
while(i<=m && j<=t)
{
if(a[i]<=a[j])
{
r[k]=a[i];
i++;
k++;
}
else
{
r[k]=a[j];
j++;
k++;
}
}
while(i<=m)
{
r[k]=a[i];
i++;
k++;
}
while(j<=t)
{
r[k]=a[j];
j++;
k++;
}
for(i=s;i<=t;i++) a[i]=r[i];
}
歸併排序在於利用另一個數組進行排序。利用二分查詢的思想來實現。一開始二分二分從第一個開始在一個範圍裡進行排序,使得在這一個區域裡所有的數字以升序排列。返回這個序列給上一次呼叫,然後與上一層呼叫在以升序。以此類推到最後則呼叫結束。
3.遞推
<1>斐波那契數列:
F[x]=f[x-1]+f[x-2];
邊界條件:f[0]=0,f[1]=1;
前幾項資料:0,1,1,2,3,5,8,13,21……
<2>漢羅塔問題:
H[n]=2*H[n-1]+1;
邊界:H[1]=1;
前幾項資料:1,3,7,15,31,63……
<3>平面分割問題:
A[n]=A[n-1]+2*(n-1);
邊界:A[1]=1;
前幾項資料:1,3,7,13,21,31……
<4>Catalan數
<5>第二類Stirling數
4.搜尋與回溯
int dfs(int k)
{
for(int i=1;i<=n;i++)
{
if(符合要求 )
{
進行標記
if(到達目的地)
輸出
else
dfs();
恢復標記。
}
}
}
int dfs(int k)
{
if(到達目的地)
輸出
for(int i=1;i<=n;i++)
{
if(符合條件 )
{
儲存標記
dfs();
取消標記
}
}
}
5.貪心與分治
貪心思想:貪心是區域性最優解最後推算到全域性最優解得過程對最終結果無影響則可以使用。必須給出嚴格的數學證明。
分治思想:把一個大問題化解成多個小問題,然後在把問題的解合起來組成最後的解。例如歸併排序。
6.廣度優先搜尋
int bfs()
{
int head,tail;
head=0;tail=1;
while(head<tail)
{
head++
for(int i=1;i<=目標 ;i++)
if(v[i]==0 && map[a[head]][i]==0)
{
tail++;
a[tail]=i;
b[tail]=head;
v[i]=1;
if(i==目標 )
{
輸出解
head=tail;
break;
}}}}
}
7.動態規劃
for(int i=1;i<=目標 ;i++)
for(int j=1;j<= 目標;j++)
for(int k=1;k<=目標 ;k++)
f[j]=max(f[j],f[j-v[i]]+k*w[i]);
cout<<f[n]<<endl;
0-1揹包:注意為倒序
完全揹包:為正序
多重揹包:為倒序多個新增一個迴圈
說實話對於動態規劃的話,沒有什麼固定的演算法,因為他只是一種思想,沒錯就是一種思想。所謂的動歸,也是一種搜尋的系列。只不過它的每一次搜尋都會進行儲存,但跟記憶化搜尋不一樣。比如說你有78元錢,我問你這78元錢有多少種組成方案?記憶化搜尋一般跟遞迴搭配。思想不就是,首先從1開始,然後將搜尋到的內容存到一個數組裡,當再次呼叫時出來就ok了。而動歸的思想在於,我78元錢我不知道怎麼分,但是1塊錢我知道塊分。1塊錢的組成方案不就是1張一塊的。然後到2塊錢,2張1塊。以此類推,後一種階段需要前一種階段的解讀。這樣當我知道78元的方案,同時在76的方案數我也計算出來了。這樣可以將大規模的資料進行壓縮。當然每一次的選擇要找最優的(並不是區域性最優),例如:當我有abcde五個點,a可以到b,c,c可以到d,b也可以到d。而d可以到e。因為bc兩個點都可以到c所以, 比較b,c兩條路的長度,選擇最優的,因為這樣就可以與後面的決策無關。從而選出最優的一條路。這也是dijskra的單源最短路演算法的思想。\
棧、佇列
關於棧的定義就是說,只能在一段進行插入或刪除的特殊線性表。
進棧:
If(top<=n) //棧是否溢位?
{
Top++;
S[top]=x; //入棧
}
退棧:
If(top>=0) //判斷棧是否為空?
{
X=s[top];
Top--; //出棧;
}
佇列是指在限定的一段進行插入,另一端進行刪除的特殊線性表。
常和BFS進行搭配。
圖論
<1>有向圖鄰接矩陣儲存:map[x][y]=z;
無向圖鄰接矩陣儲存:map[x][y]= map[y][x]=z;
Double memset(map,127,sizeof(map));
Int memset(map,0x7f,sizeof(map);
<2>強連通分量:1à2à3à1
在鄰接矩陣中: 01102
00102
00022
00002
上面的有向圖鄰接矩陣共有兩個連通分量。後面有關於程式碼的實現。
<3>鄰接表儲存
#include<iostream>
#define maxn 1001
using namespace std;
struct Edge
{
int next;//下一個點;
int to;//這條邊所到達的點;
int dis;//邊的長度;
}edge[maxn];
int head[maxn],num,n,m,u,v,d;
void add_edge(int from,int to,int dis)
{
num++;
edge[num].next=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
int main()
{
num=0;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>u>>v>>d;
add_edge(u,v,d);
}
for(int i=head[i];i!=0;i=edge[i].next)
{
//add 其他程式碼;
}
return 0;
}
本方法的儲存方法比較費腦子,有一點邏輯思維。儲存時為正序儲存,讀取時為逆序。
Next
To
Dis
0
2
6
1
3
5
2
4
11
Head[from]=0
Head[from]=1
Head[from]=2
讀入的樣例:1 2 6
1 3 5
1 4 11
<4>圖論的遍歷
Dfs();從一個點開始,訪問一個點,並把此點標記,遞迴訪問下一個,並回朔一步。
關於連通分量的程式碼:
for(int i=1;i<=n;i++)
if(v[i]==0)
{
dfs(i); //bfs(i)列舉一個點進行訪問,並標記。
sum++;
}
<4>尤拉道路問題:
定理1:存在尤拉路的條件:圖是連通的,並且有2個奇點;
定理2:存在歐拉回路的條件:圖是連通的:有0個奇點;
(ps:奇點是連線此點邊數有奇數個。)
演算法模板:
#include<iostream>
#include<cstring>
#define maxn 1001
using namespace std;
int n,m,start,map[maxn][maxn],x,y,len,c[maxn],ans[maxn];
void dfs (int i)
{
for(int j=1;j<=n;j++)
{
if(map[i][j]==1)
{
map[i][j]=map[j][i]=0;
dfs(j);
}
}
len++;
ans[len]=i;
}
int main()
{
memset(map,0,sizeof(map));
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y;
map[x][y]=map[y][x]=1;
c[x]++;
c[y]++;
}
start=1;
for(int i=1;i<=n;i++)
if(c[i]%2==1)
start=i;
len=0;
dfs(start);
for(int i=1;i<=len;i++)
cout<<ans[i]<<" ";
return 0;
}
<5>哈密爾頓環
演算法模板:
#include<iostream>
#include<cstring>
#define maxn 1001
using namespace std;
int len,x,y,ans[maxn],map[maxn][maxn];
int v[maxn],vis[maxn],num[maxn],n,m,start;
void dfs (int last,int i)
{
vis[i]=1;
v[i]=1;
len++;
ans[len]=i;
for(int j=1;j<=num[i];j++)
{
if(map[i][j]==start && map[i][j]!=last)
{
ans[len+1]=start;
for(int i=1;i<=len+1;i++)
cout<<ans[i]<<" ";
break;
}
if(vis[map[i][j]]==0) dfs(i,map[i][j]);
}
len--;
vis[i]=0;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y;
map[x][++num[x]]=y;
map[y][++num[y]]=x;
}
for(start=1;start<=n;start++)
{
if(v[start]==0)
{
len=0;
dfs(0,start);
}
}
return 0;
}