2016年蓝桥个人赛赛前总复习 个人经验总结

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背包和子序列来说事,是因为这两个问题是动态规划的启蒙问题,具有非常高的代表性。

首先说一下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;
}


九,排序算法

直接插入排序,希尔排序,堆排序,冒泡排序,快速排序,归并排序




  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值