挑战程序设计竞赛 算法和数据结构 第15章 高等图算法
15.1 所有点对间最短路径
GRL_1_C:All Pairs Shortest Path
原书AC代码:
//GRL_1_C:All Pairs Shortest Path
//C++
#include <iostream>
#include <algorithm>
#include <vector>
#include <climits>
using namespace std;
static const int MAX=100;
static const long long INFTY=(1LL<<32);
int n;
long long d[MAX][MAX];
void floyd(){
for(int k=0;k<n;k++){
for(int i=0;i<n;i++){
if(d[i][k]==INFTY)continue;
for(int j=0;j<n;j++){
if(d[k][j]==INFTY)continue;
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
int main(){
int e,u,v,c;
cin>>n>>e;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
d[i][j]=((i==j)?0:INFTY);
}
}
for(int i=0;i<e;i++){//1此处写成 for(int i=0;i<n;i++)
cin>>u>>v>>c;
d[u][v]=c;
}
floyd();
bool negative=false;
for(int i=0;i<n;i++)if(d[i][i]<0)negative=true;
if(negative){
cout<<"NEGATIVE CYCLE"<<endl;
}else{
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(j)cout<<" ";
if(d[i][j]==INFTY)cout<<"INF";
else cout<<d[i][j];
}
cout<<endl;
}
}
return 0;
}
仿照上述代码,本人编写的C语言AC代码如下:
//GRL_1_C:All Pairs Shortest Path
//样例很快通过,提交Wrong Answer,马上翻出题目,对输出字符 ,没问题
//突然想到之前编写的书中代码,用的是long long,明白应该是int 数据溢出,要改成long long
//修改,提交Wrong Answer, 修改#define INF 2147483647//2 #define INF 1999999999
//提交Wrong Answer.多次提交未果,想起了书中代码的写法,明白了要在floyd算法里进行优化 ,优化的目的,防止数据越界
//修改,提交Wrong Answer, 修改 #define INF ((long long)1<<35)//4 总长度1000*2*10^7=2*10^10提交Wrong Answer
//多次提交未果,无奈,与书中代码进行对照,找出差别。
//查了一圈下来,发现 CYCLE 写成GYCLE愣是没看出来。真是愚蠢的错误 ,提交AC 2017-10-5 21:05
//重新对代码进行修改,发现删除floyd中的两个continue语句,提交Wrong Answer,为何,思考未果。
#include <stdio.h>
#define INF ((long long)1<<35)//4 总长度1000*2*10^7=2*10^10 3#define INF 2147483647 2 #define INF 1999999999 1 #define INF 999999999
int v,e;
long long a[110][110];//1 int 改成 long long
void floyd(){
int i,j,k;
for(k=0;k<v;k++)
for(i=0;i<v;i++){
if(a[i][k]==INF)continue;//3 floyd算法优化
for(j=0;j<v;j++){
if(a[k][j]==INF)continue;//3 floyd算法优化
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
}
}
}
int main(){
int i,j,s,t,d;
scanf("%d%d",&v,&e);
for(i=0;i<v;i++)
for(j=0;j<v;j++)
a[i][j]=(i==j)?0:INF;
for(i=1;i<=e;i++){
scanf("%d%d%d",&s,&t,&d);
a[s][t]=d;
}
floyd();
for(i=0;i<v;i++)//判断负环
if(a[i][i]<0){
printf("NEGATIVE CYCLE\n");//5 此处写成 printf("NEGATIVE GYCLE\n");
return 0;
}
for(i=0;i<v;i++){
for(j=0;j<v;j++){
if(j)printf(" ");
if(a[i][j]==INF)printf("INF");
else printf("%lld",a[i][j]);//1 原来printf("%d",a[i][j]);
}
printf("\n");
}
return 0;
}
收获,floyd算法判断负环,以及floy算法的优化:减少运算次数,避免数据的溢出。
15.2 拓扑排序
GRL_4_B:Topological Sort
样例数据对应的图:
原书AC代码:
//GRL_4_B:Topological Sort
//C++(广度优先搜索实现的拓扑排序)
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <list>
using namespace std;
static const int MAX=100000;
static const int INFTY=(1<<29);
vector<int> G[MAX];
list<int> out;
bool V[MAX];
int N;
int indeg[MAX];
void bfs(int s){
queue<int> q;
q.push(s);
V[s]=true;
while(!q.empty()){
int u=q.front();q.pop();
out.push_back(u);
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
indeg[v]--;
if(indeg[v]==0&&!V[v]){
V[v]=true;
q.push(v);
}
}
}
}
void tsort(){
for(int i=0;i<N;i++){
indeg[i]=0;
}
for(int u=0;u<N;u++){
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
indeg[v]++;
}
}
for(int u=0;u<N;u++)
if(indeg[u]==0&&!V[u])bfs(u);
for(list<int>::iterator it=out.begin();it!=out.end();it++){
cout<<*it<<endl;
}
}
int main(){
int s,t,M;
cin>>N>>M;
for(int i=0;i<N;i++)V[i]=false;
for(int i=0;i<M;i++){
cin>>s>>t;
G[s].push_back(t);
}
tsort();
return 0;
}
//GRL_4_B:Topological Sort
//C++(深度优先搜索实现的拓扑排序)
//该程序对应的样例输出数据为:
//3
//4
//5
//0
//1
//2
//当然,提交,同样能AC
//理解该算法花了很长时间,比较纠结的是,没有入度,尽然能拓扑排序,
//对样例中 点1 的打印,感觉疑惑,如何在点1之前先打印点0,点3
//仿照样例,根据自己的疑惑,自个创设了一组输入输出数据,如下:
//创设数据对应的图
//输入:
//6 6
//0 1
//0 4
//1 2
//2 5
//3 4
//4 5
//输出:
//3
//0
//4
//1
//2
//5
//基本弄明白了样例中,点1(入度为2)的打印机理
//第一次访问点1的时候,就将其标记为访问过,之后的访问中自然不会出现点1
//并且整个数据的打印顺序,是按栈的方式来打印的。
//再说一遍,该题的神奇之处是,没有入度,同样能拓扑排序,深搜的拓扑排序,该书的写法让人耳目一新。
//2017-10-8 7:38
#include <iostream>
#include <vector>
#include <algorithm>
#include <list>
using namespace std;
static const int MAX=100000;
vector<int> G[MAX];
list<int> out;
bool V[MAX];
int N;
void dfs(int u){
V[u]=true;
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
if(!V[v])dfs(v);
}
out.push_front(u);
}
int main(){
int s,t,M;
cin>>N>>M;
for(int i=0;i<N;i++)V[i]=false;
for(int i=0;i<M;i++){
cin>>s>>t;
G[s].push_back(t);
}
for(int i=0;i<N;i++){
if(!V[i])dfs(i);
}
for(list<int>::iterator it=out.begin();it!=out.end();it++){
cout<<*it<<endl;
}
return 0;
}
//GRL_4_B:Topological Sort
//C(广度优先搜索实现的拓扑排序)
//邻接表,队列
//本程序,样例对应的输出数据为:
//0
//3
//4
//1
//5
//2
//原因为addedge函数,加入边长与书中C++(广度优先搜索实现的拓扑排序)代码加入边长方式不一样。
//虽然输出结果与原书略有不同,但是提交同样能AC, 2017-10-7 19:44 AC
#include <stdio.h>
#include <string.h>
struct node{
int to,next;
}e[100100];//有向边
int n,m,head[10100],cnt=0,rd[10100],vis[10100],q[10100];//rd[]入度
void addedge(int u,int v){
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void bfs(int x){
int h,t,u,v,b;//h队首,t队尾 b边
h=t=1;
q[t]=x;
t++;
while(h<t){
u=q[h];
vis[u]=1;
printf("%d\n",u);
for(b=head[u];b;b=e[b].next){
v=e[b].to;
if(vis[v]==0){
rd[v]--;
if(rd[v]==0){
q[t]=v;
t++;
}
}
}
h++;
}
}
int main(){
int u,v,i;
memset(rd,0,sizeof(rd));
memset(vis,0,sizeof(vis));
memset(head,0,sizeof(head));
scanf("%d%d",&n,&m);//n点数 m边数
for(i=1;i<=m;i++){
scanf("%d%d",&u,&v);
addedge(u,v);
rd[v]++;
}
for(i=0;i<n;i++)
if(vis[i]==0&&rd[i]==0)
bfs(i);
return 0;
}
体会,感觉拓扑排序代码编写得比以前更加自然。
//GRL_4_B:Topological Sort
//C(深度优先搜索实现的拓扑排序)
//邻接表,深搜
//样例对应的输出数据如下:
//3
//4
//5
//0
//1
//2
//2017-10-8 8:29 AC
#include <stdio.h>
#include <string.h>
struct node{
int to,next;
}e[100100];
int cnt=0,head[10100],vis[10100],stack[10100],top=0;
void addedge(int u,int v){
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void dfs(int x){
int b,v;
vis[x]=1;
for(b=head[x];b;b=e[b].next){
v=e[b].to;
if(vis[v]==0)
dfs(v);
}
stack[++top]=x;//1 此处写成 stack[++top]=v; 查了会,真是大失水准
}
int main(){
int n,m,i,j,u,v;
memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%d%d",&u,&v);
addedge(u,v);
}
for(i=0;i<n;i++)
if(vis[i]==0)
dfs(i);
while(top){
printf("%d\n",stack[top--]);
}
return 0;
}
以前写的拓扑排序,真的是很别扭,光是书中的两个拓扑排序解法,真的是物超所值。
15.3 关节点
关节点Tarjan算法dfn[] low[]理解花了很长时间
http://blog.csdn.net/logo_fc/article/details/77489369 此文概念写得很不错,摘抄如下:
dfn[v]——v点的深搜编号
low[v]——是从v点出发的所有路径中,所能到达的点的dfn最小值
加上自己的理解,样例1,dfs过程中将无向图化成有向图,如下图:
样例2,dfs过程中将无向图化成有向图,如下图:
GRL_3_A:Articulation Points
原书AC代码:
//GRL_3_A:Articualtion Point
#include <iostream>
#include <vector>
#include <set>
using namespace std;
#define MAX 100000
vector<int> G[MAX];
int N;
bool visited[MAX];
int prenum[MAX],parent[MAX],lowest[MAX],timer;
void dfs(int current,int prev){
//访问结点current之后立刻执行的处理
prenum[current]=lowest[current]=timer;
timer++;
visited[current]=true;
int next;
for(int i=0;i<G[current].size();i++){
next=G[current][i];
if(!visited[next]){
//即将通过结点current访问结点next时执行的处理
parent[next]=current;
dfs(next,current);
//结点next搜索完毕之后立刻执行的处理
lowest[current]=min(lowest[current],lowest[next]);
}else if(next!=prev){
//边current-->next为Back-edge时的处理
lowest[current]=min(lowest[current],prenum[next]);
}
}
//结点current搜索完毕之后立刻执行的处理
}
void art_points(){
for(int i=0;i<N;i++)visited[i]=false;
timer=1;
//计算lowest
dfs(0,-1);//0==root
set<int> ap;
int np=0;
for(int i=1;i<N;i++){
int p=parent[i];
if(p==0)np++;
else if(prenum[p]<=lowest[i])ap.insert(p);
}
if(np>1)ap.insert(0);
for(set<int>::iterator it=ap.begin();it!=ap.end();it++)
cout<<*it<<endl;
}
int main(){
int m;
cin>>N>>m;
for(int i=0;i<m;i++){
int s,t;
cin>>s>>t;
G[s].push_back(t);
G[t].push_back(s);
}
art_points();
return 0;
}
//GRL_3_A:Articualtion Point
//光靠书上这点总结性说明,理解关节点的算法,是不够的,搜索网络,找到一篇好文
//http://www.cnblogs.com/yu-chao/archive/2011/12/17/2291403.html
//http://www.cnblogs.com/en-heng/p/4002658.html此文也写得不错
//https://wenku.baidu.com/view/112c64096c85ec3a87c2c527.html?re=view此文运算过程写得真不错。
//理了思路,邻接表,dfs确定dfn[],low[],parent[],最后结果要靠快排,
//样例通过后,提交Wrong Answer,进行1修改,提交Wrong Answer ,停滞了一天时间。
//对照书中代码,还是找不出问题,抓狂,无奈,自个造了好多组数据,与输出程序进行对拍,找到一组好数据,如下
//输入:
//7 6
//0 1
//0 2
//1 3
//1 4
//2 5
//2 6
//输出:
//0
//1
//2
//配图如下:
//测试自身程序,与上述输出结果不同,本人输出结果如下:
//0
//1
//1
//2
//2
//这就好办了,可以开始排错了。
//就书中代码进行研究,发现中间过程也存在如上数据:
//0
//1
//1
//2
//2
//也就是说,该算法多次找到的关节点可能是同一个点,只是set<int> ap;ap.insert(p);有避免重复数据的功能,故书中代码最后输出的结果为:
//0
//1
//2
//问题找到了,那解决的办法就很简单了。对关节点数据用book[]进行标记,不怕重复了。连快排都不需要了。
//该题编的磕磕绊绊,很快上述例子通过,提交AC。 2017-10-11 21:26
#include <stdio.h>
#include <string.h>
int n,m,head[100100],cnt=0,parent[100100],dfn[100100],low[100100],vis[100100],tag=0,book[100100],count=0;
struct node{
int to,next;
}e[100000*2+100];//无向图
void addedge(int u,int v){
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void dfs(int current,int previous){//设置 dfn[] low[]
int next,b;//next点 b边
vis[current]=1;
parent[current]=previous;
tag++;
dfn[current]=low[current]=tag;
for(b=head[current];b;b=e[b].next){
next=e[b].to;
if(vis[next]==0){
dfs(next,current);
low[current]=low[current]>low[next]?low[next]:low[current];
}else if(next!=previous){
low[current]=low[current]>low[next]?low[next]:low[current];//改成low[current]=low[current]>dfn[next]?dfn[next]:low[current];也能AC,本人倾向于用low[current]=low[current]>low[next]?low[next]:low[current];更容易理解.dfn[]用来标记访问顺序,low[]识别能到达最小访问顺序的点 2017-10-11 22:35 细细思考,觉得还是用low[current]=low[current]>dfn[next]?dfn[next]:low[current];比较好,当然,到底用哪个比较好,还是由读者自行决定。2017-10-12 11:05
}
}
}
void art_point(){
int i,p,p_num=0;
for(i=0;i<n;i++){
p=parent[i];
if(p==0)p_num++;
else if(p>0&&dfn[p]<=low[i])book[p]=1;
}
if(p_num>1)book[0]=1;
}
int main(){
int i,u,v;
memset(vis,0,sizeof(vis));
memset(head,0,sizeof(head));
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
parent[0]=-1;
dfs(0,-1);
art_point();
for(i=0;i<n;i++)
if(book[i])
printf("%d\n",i);
return 0;
}
15.4 树的直径
GRL_5_A:Diameter of a Tree
原书AC代码:
//GRL_5_A:Diameter of a Tree
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
#define MAX 100000
#define INFTY (1<<30)
class Edge{
public:
int t,w;
Edge(){};
Edge(int t,int w):t(t),w(w){}
};
vector<Edge> G[MAX];
int n,d[MAX];
bool vis[MAX];
int cnt;
void bfs(int s){
for(int i=0;i<n;i++)d[i]=INFTY;
queue<int> Q;
Q.push(s);
d[s]=0;
int u;
while(!Q.empty()){
u=Q.front();Q.pop();
for(int i=0;i<G[u].size();i++){
Edge e=G[u][i];
if(d[e.t]==INFTY){
d[e.t]=d[u]+e.w;
Q.push(e.t);
}
}
}
}
void solve(){
//从任选的结点s出发,选择距离s最远的结点tgt
bfs(0);
int maxv=0;
int tgt=0;
for(int i=0;i<n;i++){
if(d[i]==INFTY)continue;
if(maxv<d[i]){
maxv=d[i];
tgt=i;
}
}
//从tgt出发,求结点tgt到最远节点的距离maxv
bfs(tgt);
maxv=0;
for(int i=0;i<n;i++){
if(d[i]==INFTY)continue;
maxv=max(maxv,d[i]);
}
cout<<maxv<<endl;
}
main(){
int s,t,w;
cin>>n;
for(int i=0;i<n-1;i++){
cin>>s>>t>>w;
G[s].push_back(Edge(t,w));
G[t].push_back(Edge(s,w));
}
solve();
}
//GRL_5_A:Diameter of a Tree
//请注意,因为树边的特点,vis[]数组是不需要的
//样例通过,提交Runtime Error,马上想到无向图,边设置得少了。修改,提交AC 2017-10-14 12:09
#include <stdio.h>
#include <string.h>
#define INF 999999
struct node{
int to,next,w;
}e[100100*2];//2 此处写成 e[100100]
int cnt=0,head[100100],n,d[100100],q[100100];
void addedge(int u,int v,int w){//邻接表
cnt++,e[cnt].to=v,e[cnt].next=head[u],e[cnt].w=w,head[u]=cnt;
}
void bfs(int x){
int h,t,i,u,v,b;
for(i=0;i<n;i++)d[i]=INF;
h=t=1;
q[t]=x;
t++;
d[x]=0;
while(h<t){
u=q[h];
for(b=head[u];b;b=e[b].next){
v=e[b].to;
if(d[v]==INF){
d[v]=d[u]+e[b].w;
q[t]=v;
t++;
}
}
h++;
}
}
int main(){
int i,u,v,w,k,maxv;
memset(head,0,sizeof(head));
scanf("%d",&n);
for(i=1;i<n;i++){
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);//无向图
addedge(v,u,w);
}
bfs(0);
maxv=-1;
for(i=0;i<n;i++)
if(d[i]!=INF&&maxv<d[i]){
maxv=d[i];
k=i;
}
bfs(k);
maxv=-1;
for(i=0;i<n;i++)
if(d[i]!=INF&&maxv<d[i])
maxv=d[i];
printf("%d\n",maxv);//1 一个想不到的错误,此处写成 printf("%d\n",d[i]); 竟然查了会
return 0;
}
15.5 最小生成树
GRL_2_A:Minimum Spanning Tree
原书AC代码:
//GRL_2_A:Minimum Spanning Tree
//C++
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define MAX 10000
#define INFTY (1<<29)
class DisjointSet{
//请参考DSL_1_A的参考答案
public:
vector<int> rank,p;
DisjointSet(){}
DisjointSet(int size){
rank.resize(size,0);
p.resize(size,0);
for(int i=0;i<size;i++)makeSet(i);
}
void makeSet(int x){
p[x]=x;
rank[x]=0;
}
bool same(int x,int y){
return findSet(x)==findSet(y);
}
void unite(int x,int y){
link(findSet(x),findSet(y));
}
void link(int x,int y){
if(rank[x]>rank[y]){
p[y]=x;
}else{
p[x]=y;
if(rank[x]==rank[y]){
rank[y]++;
}
}
}
int findSet(int x){
if(x!=p[x]){
p[x]=findSet(p[x]);
}
return p[x];
}
};
class Edge{
public:
int source,target,cost;
Edge(int source=0,int target=0,int cost=0):
source(source),target(target),cost(cost){}
bool operator < (const Edge &e)const{
return cost < e.cost;
}
};
int kruskal(int N,vector<Edge> edges){
int totalCost=0;
sort(edges.begin(),edges.end());
DisjointSet dset=DisjointSet(N+1);
for(int i=0;i<N;i++)dset.makeSet(i);
int source,target;
for(int i=0;i<edges.size();i++){
Edge e=edges[i];
if(!dset.same(e.source,e.target)){
//MIS.push_back(e);
totalCost+=e.cost;
dset.unite(e.source,e.target);
}
}
return totalCost;
}
int main(){
int N,M,cost;
int source,target;
cin>>N>>M;
vector<Edge> edges;
for(int i=0;i<M;i++){
cin>>source>>target>>cost;
edges.push_back(Edge(source,target,cost));
}
cout<<kruskal(N,edges)<<endl;
return 0;
}
参考了:
http://blog.csdn.net/mrcrack/article/details/61625530
//P3366 【模板】最小生成树
代码
//GRL_2_A:Minimum Spanning Tree
//C
//快排编写出现些小错误,修改,提交AC 2017-10-15 15:28
#include <stdio.h>
struct node{
int u,v,w;
}e[100100],e_t;
int f[10100];
int getf(int u){
if(f[u]==u)
return u;
f[u]=getf(f[u]);
return f[u];
}
int merge(int u,int v){//左靠
int f1,f2;
f1=getf(u);
f2=getf(v);
if(f1==f2)//无需合并,同一个父亲
return 0;
f[f2]=f1;
return 1;
}
void quicksort(int left,int right){//自小到大排序
int i=left,j=right,mid=e[(left+right)/2].w;
while(i<=j){
while(e[i].w<mid)i++;//低级错误,此处写成 if(e[i].w<mid)i++;
while(e[j].w>mid)j--;//低级错误,此处写成 if(e[j].w>mid)j--;
if(i<=j){
e_t=e[i];
e[i]=e[j];
e[j]=e_t;
i++;
j--;
}
}
if(left<j)quicksort(left,j);
if(i<right)quicksort(i,right);
}
int main(){
int n,m,i,u,v,w,count=0,sum=0;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++)
f[i]=i;
for(i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
e[i].u=u,e[i].v=v,e[i].w=w;
}
quicksort(1,m);
for(i=1;i<=m;i++)
if(merge(e[i].u,e[i].v)){
sum+=e[i].w;
count++;
if(count==n-1)
break;
}
printf("%d\n",sum);
return 0;
}
2017-10-15 15:28 AC该章节内容