20号就是蓝桥杯的省赛了,准备了半年,现在进入了最后的准备阶段,把几大经典算法和一些C++ 上必备的技巧做一个总结。
第一,dijkstra。为什么从dijkstra说起,因为这是最经典,最基础,使用率最广的图算法之一。
void dijkstra(){
int path[v+1];
int shortest[v+1]
int shortestPoint;
int shortestDist;
int already;
bool ifVisited[v+1];
for(int i=1;i<=v;i++){
path[i]=1;
ifVisited[i]=false;
shortest[i]=g[1][i];
}
shortestDist=0;
shortestPoint=1;
shortest[1]=0;
ifVisited[1]=true;
already=1;
while(already<=v){
shortestDist=inf;
for(int i=1;i<=v;i++){
if(ifVisited[i]==false&&shortest[i]<shortestDist){
shortestPoint=i;
shortestDist=shortest[i];
}
}
ifVisited[shortestPoint]=true;
already++;
for(int i=1;i<=v;i++){
if(ifVisited[i]==false&&g[shortestPoint][i]+shortest[shortestPoint]<shortest[i]){
shortest[i]=g[shortestPoint][i]+shortest[shortestPoint];
}
}
}
return;
}
基本数据结构如下:
path数组,记录这个点的前驱顶点是什么;
shortest数组,这个就是最终的结果,记录每个点的最短距离。
shortestPoint,当前回合择优选取的点。
shortestDist,当前回合选择最优点时,已经产生的最短距离。
ifVisited数组,记录哪些点已经达到了最短距离。
already,记录目前已经有几个点达到了最短距离。
第二,bellman-ford算法,这个算法用于dijkstra不能求的带负权的回路中。
代码如下:
void bellman-ford(){
class line{
public:
int start,end,id,length;
};
line l[lineNumber+1];
int leng[v+1];
for(int i=1;i<=v;i++){
leng[i]=inf;
}
leng[1]=0;
for(int i=1;i<=v-1;i++){
for(int i=1;i<=lineNumber;i++){
if(leng[l[i].end]>leng[l[i].start]+l[i].length){
leng[l[i].end]=leng[l[i].start]+l[i].length;
}
}
}
for(int i=1;i<=lineNumber;i++){
if(leng[l[i].end]>leng[l[i].start]+l[i].length){
cout<<"error";
return;
}
}
return;
}
注意事项:bellman-ford是遍历边,遍历的循环次数是v-1次,v是点的数量,不是边的数量。
如果是无向图,则遍历边的时候,一条边遍历两次,相当于两条边,只是起点和重点的次序颠倒了。
第三:flyod算法。最直观的最短路算法,适用于要求任意两点间最短距离的情况,如果只需要求某一点到其他点最短路,则这个算法比较费时间,不推荐。
代码如下:
void flyod(){
for(int i=1;i<=v;i++){
for(int j=1;j<=v;j++){
for(int k=1;k<=v;k++){
if(g[j][k]>g[j][i]+g[i][k]){
g[j][k]=g[j][i]+g[i][k];
}
}
}
}
return;
}
没有太多的注意事项。
第四:最小生成树 prim算法。
最简单的最小生成树算法。
代码如下:
void prim(){
bool ifVisited[v+1];
int minPoint,minDist;
int minLength[v+1];
int result=0;
for(int i=1;i<=v;i++){
ifVisited[i]=false;
minLength[i]=inf;
}
ifVisited[1]=true;
minPoint=1;
minLength=0;
for(int i=1;i<=v-1;i++){
for(int j=1;j<=v;j++){
if(ifVisited[j]==false&&g[minPoint][j]<minLength[j]){
minLength[j]=g[minPoint][j];
}
}
minDist=inf;
for(int j=1;j<=v;j++){
if(ifVisited[j]==false&&minLength[j]<minDist){
minDist=g[minPoint][j];
minPoint=j;
}
}
result+=minDist;
ifVisited[minPoint]=true;
}
return;
}
仔细看,prim和dijkstra真是很神似,他们的区别在于,prim在选择哪个点作为下一个点的时候,比较的是上一个选点和当前遍历点这跳线的长度哪个最短。而dijkstra是上个选点的最短路径+两点间距离。实际上这两个算法本质上是相同的。
第五:kruskal算法。
我们把最小生成树的算法放到一块来,一般来说,如果比赛中发现存储数据比较适合用边来的话,建议用kruskal,如果适合用一般的二维数组表示图,则使用prim比较方便写代码。
int father[e+1];
class edge{
public:
int start,end,leng;
};
int cmp(const void *a,const void *b){
return *(edge*)a.leng<*(edge*)b.leng;
}
int find(int a){
return a==father[a]?a:find(father[a]);
}
bool join(int a,int b){
a=find(a);
b=find(b);
if(a==b){
return false;
}
else{
father[a]=b;
}
return true;
}
void kruskal(){
int result=0;
edge ed[e+1];
ed[1].leng=-inf;
qsort(ed,e+1,sizeof(ed[0]),cmp);
for(int i=1;i<=v;i++){
if(join(ed[i].start,ed[i].end)==true){
result+=ed[i].leng;
}
}
return;
}
又一大比赛中常常出现的算法。最小点覆盖=最大匹配数。匈牙利算法基本代码如下:
bool ifVisited[y+1];
int ancestor[y+1];
bool dfs(int a){
for(int i=1;i<=y;i++){
if(ifVisited[i]==false&&g[a][i]==true){
ifVisited[i]=true;
if(ancestor[i]==0||dfs(ancestor[i])==true){
return true;
}
}
}
return false;
}
void xiongyali(){
for(int i=1;i<=y;i++){
ancestor[i]=0;
}
for(int i=1;i<=x;i++){
for(int j=1;j<=y;j++){
ifVisited[j]=false;
}
if(dfs(i)==true){
ans++;
}
}
return;
}
另外,两大公式:
最大匹配数=最小点覆盖;
最小路径覆盖=拆点前点数量-最大匹配数/2;
拿0-1背包和子序列来说事,是因为这两个问题是动态规划的启蒙问题,具有非常高的代表性。
首先说一下0-1背包问题,典型解法:
for(int i=0;i<=itemNumber;i++){
dp[0][i]=0;
}
for(int i=1;i<=itemNumber;i++){
for(int j=1;j<=itemNumber;j++){
dp[i][j]=dp[i-1][j]>dp[i-1][j-1]+weight[j]?dp[i-1][j]:dp[i-1][j-1]+weight[j];
}
}
初始化dp[0][n];然后从i开始遍历。
两段最短子序列问题,这个问题涉及三个数组,分别为前i个字符包括i组成的最大连续序列长度,后i个字符包括i组成的最大连续序列长度,前i个字符可以组成的最大序列,不包括i:
dp[0]=0;
a[100];
b[100];
for(int i=1;i<=n;i++){
dp[i]=dp[i-1]+value[i]>value[i]?dp[i-1]+value[i]:value[i];
}
a[0]=0;
for(int i=1;i<=n;i++){
if(a[i-1]>dp[i]){
a[i]=a[i-1];
}
else{
a[i]=dp[i];
}
}
b[n]=0;
for(int i=n-1;i>=0;i--){
b[i]=value[i]>b[i+1]+value[i]?:value[i]:b[i+1]+value[i];
}
result=-inf;
for(int i=0;i<n;i++){
if(a[i]+b[i+1]>result){
result=a[i]+b[i+1];
}
}
return result;
第八:最大流算法,ek
最大流是用ek做,当然也可以km,ek比较直观,好上手,在比赛中比较合适。
int flow[v];
int former[v];
bool ifVisited[v];
bool bfs(){
queue<int> q;
while(!q.empty()){
q.pop();
}
for(int i=1;i<=v;i++){
flow[i]=0;
former[i]=0;
ifVisited[i]=false;
}
former[1]=1;
flow[1]=inf;
ifVisited[1]=true;
push(1);
while(!q.empty()){
int cur=q.front();
q.pop();
for(int i=1;i<=v;i++){
if(ifVisited[i]==false&&g[cur][i]>0){
ifVisited[i]=true;
flow[i]=flow[cur]>g[cur][i]?g[cur][i]:flow[cur];
push(i);
}
}
}
if(flow[v]==0){
return false;
}
else{
retur true;
}
}
int ek(){
bfs();
int min=flow[v];
while(min>0){
int c=v;
while(former[c]!=1){
g[former[c]][c]-=flow[v];
g[c][former[c]]+=flow[v];
}
result+=flow[v];
bfs();
min=flow[v];
}
return result;
}
九,排序算法
直接插入排序,希尔排序,堆排序,冒泡排序,快速排序,归并排序