挑战程序设计竞赛 算法和数据结构 第12章 图

挑战程序设计竞赛 算法和数据结构 第12章 图

12.2 图的表示

ALDS1_11_A:Graph

原书AC代码:

//ALDS1_11_A:Graph
#include <iostream>
using namespace std;
static const int N = 100;
int main(){
    int M[N][N];//0 0起点的邻接矩阵
    int n,u,k,v;
    cin>>n;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++)M[i][j]=0;
    } 
    for(int i=0;i<n;i++){
        cin>>u>>k;
        u--;//转换为0起点
        for(int j=0;j<k;j++){
            cin>>v;
            v--;//转换为0起点
            M[u][v]=1;//1 本人此处写成M[v][v]=1; 在u和v之间画出一条边 
        } 
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            if(j)cout<<" ";
            cout<<M[i][j];
        }
        cout<<endl;
    } 
    return 0;
}

这次没有仿照上述代码,本人未看书,直接编写的C语言AC代码如下:

//ALDS1_11_A:Graph
//提交,Presentation Error,明白格式要求比较高 ,修改格式后,提交AC 2017-9-29 
#include <stdio.h>
#include <string.h>
int a[110][110];
int main(){
    int n,u,k,v,i,j;
    memset(a,0,sizeof(0));
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d%d",&u,&k);
        for(j=1;j<=k;j++){
            scanf("%d",&v);
            a[u][v]=1;
        }
    }
    for(i=1;i<=n;i++){
        for(j=1;j<=n;j++){
            if(j==1)//1漏了该if语句 
                printf("%d",a[i][j]);//1漏了该功能 
            else
                printf(" %d",a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

该题,感觉比较新颖,加深了对邻接表与邻接矩阵的理解。

12.3 深度优先搜索

ALDS1_11_B:Depth First Search

看了样例的图,感觉耳目一新,该题对深度优先遍历的理解大有帮助,样例图如下:

挑战程序设计竞赛 算法和数据结构 第12章 图

原书AC代码:

方法一

//ALDS1_11_B:Depth First Search
//C(用递归实现的深度优先搜索)
#include <stdio.h>
#define N 100
#define WHITE 0
#define GRAY 1
#define BLACK 2
int n,M[N][N];
int color[N],d[N],f[N],tt;
//用递归函数实现的深度优先搜索
void dfs_visit(int u){
    int v;
    color[u]=GRAY;
    d[u]=++tt;//最初的访问
    for(v=0;v<n;v++){
        if(M[u][v]==0)continue;
        if(color[v]==WHITE){
            dfs_visit(v);
        }
    }
    color[u]=BLACK;
    f[u]=++tt;//访问结束
}
void dfs(){
    int u;
    //初始化
    for(u=0;u<n;u++)color[u]=WHITE;
    tt=0;
    for(u=0;u<n;u++){
        //以未访问的u为起点进行深度优先搜索
        if(color[u]==WHITE)dfs_visit(u);
    }
    for(u=0;u<n;u++){
        printf("%d %d %d\n",u+1,d[u],f[u]);
    }
}
int main(){
    int u,v,k,i,j;
    scanf("%d",&n);
    for(i=0;i<n;i++){
        for(j=0;j<n;j++)M[i][j]=0;
    }
    for(i=0;i<n;i++){
        scanf("%d %d",&u,&k);
        u--;
        for(j=0;j<k;j++){
            scanf("%d",&v);
            v--;
            M[u][v]=1;
        }
    }
    dfs();
    return 0;
}
方法二:

//ALDS1_11_B:Depth First Search
//C++(用栈实现的深度优先搜索)
#include <iostream>
#include <stack>
using namespace std;
static const int N = 100;
static const int WHITE = 0;
static const int GRAY = 1;
static const int BLACK = 2;
int n,M[N][N];
int color[N],d[N],f[N],tt;
int nt[N];
//按编号顺序获取与u相邻的v
int next(int u){
    for(int v=nt[u];v<n;v++){
        nt[u]=v+1;
        if(M[u][v])return v;
    }
    return -1;

void dfs_visit(int r){
    for(int i=0;i<n;i++) nt[i]=0;
    stack<int> S;
    S.push(r);
    color[r]=GRAY;
    d[r]=++tt;
    while(!S.empty()){
        int u=S.top();
        int v=next(u);
        if(v!=-1){
            if(color[v]==WHITE){
                color[v]=GRAY;
                d[v]=++tt;
                S.push(v);
            }
        }else{
            S.pop();
            color[u]==BLACK;
            f[u]=++tt;
        }
    }
}
void dfs(){
    //初始化
    for(int i=0;i<n;i++){
        color[i]=WHITE;
        nt[i]=0;
    } 
    tt=0;
    //以未访问的u为起点进行深度优先搜索
    for(int u=0;u<n;u++){
        if(color[u]==WHITE)dfs_visit(u);
    } 
    for(int i=0;i<n;i++){
        cout<<i+1<<" "<<d[i]<<" "<<f[i]<<endl;
    }
}
int main(){
    int u,k,v;
    cin>>n;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++)M[i][j]=0;
    }
    for(int i=0;i<n;i++){
        cin>>u>>k;
        u--;
        for(int j=0;j<k;j++){
            cin>>v;
            v--;
            M[u][v]=1;
        }
    }
    dfs();
    return 0;
}

这次没有仿照上述代码,本人未看书,直接编写的C语言AC代码如下:

该题是有向图,时间戳可以开一个全局变量进行计数。

在考虑是用转成邻接矩阵,还是直接用邻接表,思考过后,为了提升能力,还是采用邻接表,采用dfs方式进行处理。

//ALDS1_11_B:Depth First Search
//因所掌握的邻接表存储方式,编写了一通,与该题遍历顺序不一致,故改成邻接矩阵表示法
//样例通过,提交WA,对照书本代码,发现问题:
//书中给的样例全是强连通图,但测试点中有非强连通图。 修改提交AC.2017-9-30
#include <stdio.h>
#include <string.h>
int a[110][110];
int d[110],f[110],vis[110],tag=0,n;//第一次访问d[] 最后一次访问f[]
void dfs(int u){
    int v;
    vis[u]=1;
    tag++;
    d[u]=tag;
    for(v=1;v<=n;v++)
        if(vis[v]==0&&a[u][v])
            dfs(v);
    tag++;
    f[u]=tag;
}
int main(){
    int i,j,u,k,v;
    memset(a,0,sizeof(a));
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d%d",&u,&k);
        for(j=1;j<=k;j++){
            scanf("%d",&v);
            a[u][v]=1;
        }
    }
    for(i=1;i<=n;i++)//1漏了该功能,导致WA  //书中给的样例全是强连通图,但测试点中有非强连通图。
        if(vis[i]==0)
            dfs(i);
    for(i=1;i<=n;i++)
        printf("%d %d %d\n",i,d[i],f[i]);
    return 0;
}


根据上述方法二的思想,采用栈,根据自己的理解,写了如下AC代码:

//ALDS1_11_B:Depth First Search
//体会,算法清晰,便思如泉涌2017-10-1 9:38 AC方法二 
//C(用栈实现的深度优先搜索)
#include <stdio.h>
#include <string.h>
int n,a[110][110],vis[110],d[110],f[110],tag=0; 
void dfs(int x){
    int u,v,stack[110],top=0;//stack栈从数组元素1开始使用
    top++;//top指向栈顶元素 
    stack[top]=x;
    vis[x]=1;//1 此处写成 vis[top]=1; 低级错误 
    tag++;
    d[x]=tag;
    while(top){
        u=stack[top];
        for(v=1;v<=n;v++)
            if(vis[v]==0){
                if(a[u][v]){
                    top++;
                    stack[top]=v;
                    vis[v]=1;
                    tag++;
                    d[v]=tag;
                    break;
                }
            }
        if(v==n+1){//没有连线可到的点可用 
            tag++;
            f[u]=tag;
            top--;
        }
    }
}
int main(){
    int u,k,v,i,j;
    memset(a,0,sizeof(a));
    memset(vis,0,sizeof(vis));
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d%d",&u,&k);
        for(j=1;j<=k;j++){
            scanf("%d",&v);
            a[u][v]=1;
        }
    }
    for(i=1;i<=n;i++)
        if(vis[i]==0)
            dfs(i);
    for(i=1;i<=n;i++)
        printf("%d %d %d\n",i,d[i],f[i]);
    return 0;
}

12.4 广度优先搜索

ALDS1_11_C:Breadth First Search

原书AC代码:2017-10-1 21:30抄写完成

//ALDS1_11_C:Breadth First Search
#include <iostream>
#include <queue>
using namespace std;
static const int N=100;
static const int INFTY=(1<<21);
int n,M[N][N];
int d[N];//通过距离管理访问状态(color)
void bfs(int s){
    queue<int> q;//使用标准库中的queue
    q.push(s);
    for(int i=0;i<n;i++)d[i]=INFTY;
    d[s]=0;
    int u;
    while(!q.empty()){
        u=q.front();q.pop();
        for(int v=0;v<n;v++){
            if(M[u][v]==0)continue;
            if(d[v]!=INFTY)continue;
            d[v]=d[u]+1;
            q.push(v);
        }
    }
    for(int i=0;i<n;i++){
        cout<<i+1<<" "<<((d[i]==INFTY)?(-1):d[i])<<endl;
    }
}
int main(){
    int u,k,v;
    cin>>n;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++)M[i][j]=0;
    }
    for(int i=0;i<n;i++){
        cin>>u>>k;
        u--;
        for(int j=0;j<k;j++){
            cin>>v;
            v--;
            M[u][v]=1;
        }
    }
    bfs(0);
    return 0;
}


这次没有仿照上述代码,本人未看书,直接编写的C语言AC代码如下:

为了方便大家,提供一组样例:

输入:

5
1 2 2 4
2 1 4
3 0
4 1 3
5 0


输出:

1 0
2 1
3 2
4 1
5 -1

//ALDS1_11_C:Breadth First Search
//迪杰斯特拉算法肯定能解决该题,但有《啊哈!算法》的基础,广搜也肯定能解决该题,先自己动手 
//样例通过,提交WA,重读题目,发现If there are no path from vertex 11 to vertex uu, print -1 as the shortest distance.
//即无路可达,需要输出-1,马上修改,提交WA ,4 此处写成 for(j=1;j<=n;j++) 大失水准,该句,查了10分钟。
//继续修改,提交WA ,马上对应输入输出,发现测试语句未删除,果断删除,提交AC 2017-10-1 10:59

//通过该题,发现对迪杰斯特拉算法以及广度优先遍历的理解又上了一个台阶。
#include <stdio.h>
#include <string.h>
struct node{
    int v,s;//v点,到起点的步数 
}q[110]; 
int n,a[110][110],vis[110],head,tail,step[110];//step[]记录各点步数 
void breadth(){
    int u,v;
    head=tail=1;
    vis[1]=1;//3 漏了该句 
    q[head].v=1;
    q[head].s=0;
    step[1]=0;
    tail++;
    while(head<tail){
        u=q[head].v;
        for(v=1;v<=n;v++)
            if(vis[v]==0&&a[u][v]){
                vis[v]=1;//2 漏了该句 
                q[tail].v=v;
                q[tail].s=q[head].s+1;//1 该句写成  q[tail].s+=1;
                step[v]=q[tail].s;
                tail++;
            }
        head++;
    }
}
int main(){
    int u,k,v,i,j;
    memset(a,0,sizeof(a));
    memset(vis,0,sizeof(vis));
    memset(step,-1,sizeof(step));//3此处写成 memset(step,0,sizeof(step)); 即无路可达,需要输出-1,
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d%d",&u,&k);
        for(j=1;j<=k;j++){//4 此处写成 for(j=1;j<=n;j++) 大失水准,该句,查了10分钟。 
            scanf("%d",&v);
            a[u][v]=1;
        }
    }
    breadth();
    for(i=1;i<=n;i++)
        printf("%d %d\n",i,step[i]);
    return 0;
}

12.5 连通分量

ALDS1_11_D:Connected Components

很不幸,在在线测评网站搜索该题,已无踪影,看来只有样例好用了,无法知道程序是否AC,重新进行寻找,发现在线测评网站,该题还在,眼花了啊。

原书AC代码:

//ALDS1_11_D:Connected Components
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
static const int MAX=100000;
static const int NIL=-1;
int n;
vector<int> G[MAX];
int color[MAX];
void dfs(int r,int c){
    stack<int> S;
    S.push(r);
    color[r]=c;
    while(!S.empty()){
        int u=S.top();S.pop();
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i];
            if(color[v]==NIL){
                color[v]=c;
                S.push(v);
            }
        }
    }
}
void assignColor(){
    int id=1;
    for(int i=0;i<n;i++)color[i]=NIL;
    for(int u=0;u<n;u++){
        if(color[u]==NIL)dfs(u,id++);
    }
}
int main(){
    int s,t,m,q;
    cin>>n>>m;
    for(int i=0;i<m;i++){
        cin>>s>>t;
        G[s].push_back(t);
        G[t].push_back(s);
    }
    assignColor();
    cin>>q;
    for(int i=0;i<q;i++){
        cin>>s>>t;
        if(color[s]==color[t]){
            cout<<"yes"<<endl;
        }else{
            cout<<"no"<<endl;
        }
    }
    return 0;
}

仿照上述代码,本人编写的C语言AC代码如下:

方法一:广度优先遍历

//ALDS1_11_D:Connected Components
//仔细想了想该题,对同一联通区块的节点,染上相同的颜色,不同区块,染色不同,采用广度优先遍历来实现。
//染色后,对于每次查找,都可以保证O(1)的算法时间复杂度
//请注意,朋友之间的关系,属无向图,为此还偷瞄了书中代码。2017-10-2 10:58 书中样例通过,遗憾该题已被在线测评网站移除。
//重新进行寻找,发现在线测评网站,该题还在,眼花了啊。
//提交Presentation Error, 少一个回车都不行啊。修改,printf("no\n");//1  printf("no");提交Presentation Error;提交 Wrong Answer
//重新读题,发现The users in the SNS are identified by IDs 0,1,...,n-1. 本人误以为是1-n,马上修改 for(i=0;i<=n-1;i++)//2  for(i=1;i<=n;i++)
//提交Wrong Answer, 怎么查都没问题,突然想到,无向图,边数应为关系的两倍, e[100100*2];//3 e[100100] 无向图,故边数应为2*m
//修改,提交AC. 2017-10-2 11:18 调试错误,整整用了20分钟。一看,还都是以往遇到的问题。
#include <stdio.h>
#include <string.h>
struct node{
    int to,next;
}e[100100*2];//3 e[100100] 无向图,故边数应为2*m
int cnt=0,head[100100],n,m,q[100100],vis[100100],color[100100];
void add(int u,int v){
    cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void bfs(int x,int c){//广度优先遍历,染色
    int h,t,u,v,b;//b边
    h=t=1;
    vis[x]=1;
    q[t]=x;
    color[x]=c;
    t++;
    while(h<t){
        u=q[h];
        for(b=head[u];b;b=e[b].next){
            v=e[b].to;
            if(vis[v]==0){
                vis[v]=1;
                q[t]=v;
                color[v]=c;
                t++;
            }
        }
        h++;
    }
}
int main(){
    int i,u,v,c=0,k;
    memset(head,0,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(color,0,sizeof(color));
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        add(u,v);//无向图
        add(v,u);
    }
    for(i=0;i<=n-1;i++)//2  for(i=1;i<=n;i++)
        if(vis[i]==0)
            bfs(i,++c);
    scanf("%d",&k);
    for(i=1;i<=k;i++){
        scanf("%d%d",&u,&v);
        if(color[u]==color[v])
            printf("yes\n");
        else
            printf("no\n");//1  printf("no");提交Presentation Error
    }
    return 0;
}

方法二:深度优先遍历

//ALDS1_11_D:Connected Components
//突发奇想,用该题练练一题多解。
//方法:深度优先遍历
#include <stdio.h>
#include <string.h>
struct node{
    int to,next;
}e[100100*2];
int cnt=0,head[100100],n,m,q[100100],vis[100100],color[100100];
void add(int u,int v){
    cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void dfs(int x,int c){//深度优先遍历,染色
    int v,b;//b边
    vis[x]=1;
    color[x]=c;
    for(b=head[x];b;b=e[b].next){//1此处写成 for(b=head[u];b;b=e[b].next)
        v=e[b].to;
        if(vis[v]==0)
            dfs(v,c);
    }
}
int main(){
    int i,u,v,c=0,k;
    memset(head,0,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(color,0,sizeof(color));
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        add(u,v);//无向图
        add(v,u);
    }
    for(i=0;i<=n-1;i++)
        if(vis[i]==0)
            dfs(i,++c);
    scanf("%d",&k);
    for(i=1;i<=k;i++){
        scanf("%d%d",&u,&v);
        if(color[u]==color[v])
            printf("yes\n");
        else
            printf("no\n");
    }
    return 0;
}
方法三:并查集,Wrong Answer算法
//ALDS1_11_D:Connected Components
//方法:并查集
//样例很快通过,提交Wrong Answer ,经检验,感觉是算法复杂度过大,引起的错误,故还是留在该篇文章中,待日后水平更高后,来解决。2017-10-2 17:05
#include <stdio.h>
int n,m,f[100100];
int getf(int u){
    if(f[u]==u)
        return u;
    f[u]=getf(f[u]);
    return f[u];
}
void merge(int u,int v){//靠左
    int f1=getf(u),f2=getf(v);
    if(f1!=f2)
        f[f2]=f1;
}
int main(){
    int i,j,u,v,k;
    scanf("%d%d",&n,&m);
    for(i=0;i<n;i++)
        f[i]=i;
    for(i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        merge(u,v);
    }
    scanf("%d",&k);
    for(i=1;i<=k;i++){
        scanf("%d%d",&u,&v);
        if(f[u]==f[v])
            printf("yes\n");
        else
            printf("no\n");
    }
    return 0;
}


2017-10-2 11:18AC该章节内容。