PTA甲级之图的考查

图的存储

图常用的两种存储方式是邻接矩阵跟邻接表

邻接矩阵

邻接矩阵定义

    const int maxn=1005;	//最大顶点数 
    int G[maxn][maxn];  //G存储图的边

图的邻接矩阵的遍历方式

深度优先遍历(DFS)

    void dfs(int v){	//邻接矩阵的深度优先遍历
        visit[v]=1;     //标记已访问
        cout<<v<<' ';   //访问方式
        for(int i=1;i<=N;i++){
            if(visit[i]==0&&G[v][i]==1) //如果i未被访问且i与v有相连的边,则把i入队
                dfs(i); //访问i
        }
    }

广度优先遍历(BFS)
    void bfs(int s){         //s为访问的起点
        queue<int>q;         //定义队列
        q.push(s);           //把起始顶点入队
        visit[s]=1;          //把起始顶点标记为已访问
        while(!q.empty()){   //当队列不为空时,一直循环
            int v=q.front(); //取队首顶点
            cout<<v<<' ';    //访问的方式
            q.pop();         //访问完该顶点,即可将其出队
            for(int i=1;i<=N;i++){
                if(visit[i]==0&&G[v][i]==1){    //如果i未被访问且i与v有相连的边,则把i入队
                    visit[i]=1;     //访问标记要在此处写
                    q.push(i);      //入队
                }
            }
        }
    }

完整程序代码

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1005;    //最大顶点数 
    int G[maxn][maxn],N,M;
    int visit[maxn]={0};    //初始化未访问
    void dfs(int v){    //邻接矩阵的深度优先遍历
        visit[v]=1;     //标记已访问
        cout<<v<<' ';   //访问方式
        for(int i=1;i<=N;i++){  //零阶矩阵需要遍历1~N
            if(visit[i]==0&&G[v][i]==1) //如果i未被访问且i与v有相连的边,则dfs(i) 
                dfs(i); //访问i
        }
    }
    void bfs(int s){         //s为访问的起点
        queue<int>q;         //定义队列
        q.push(s);           //把起始顶点入队
        visit[s]=1;          //把起始顶点标记为已访问
        while(!q.empty()){   //当队列不为空时,一直循环
            int v=q.front(); //取队首顶点
            cout<<v<<' ';    //访问的方式
            q.pop();         //访问完该顶点,即可将其出队
            for(int i=1;i<=N;i++){
                if(visit[i]==0&&G[v][i]==1){    //如果i未被访问且i与v有相连的边,则把i入队
                    visit[i]=1;     //访问标记要在此处写
                    q.push(i);      //入队
                }
            }
        }
    }
    int main(){
        cin>>N>>M;  //N为定点数1~N,M为边的个数 
        for(int i=0;i<M;i++){
            int a,b;
            scanf("%d %d",&a,&b);
            G[a][b]=G[b][a]=1;//有向图的输入
            //G[a][b]=1;    有向图的输入
        }
    //  dfs(1);     //深度优先遍历 
    //  bfs(1);     //广度优先遍历 
        return 0;
    }
//输入
7 7
4 1
4 6
1 3
6 5
6 7
3 2
3 5

图

邻接表

邻接表的定义

    vector<int>G[maxn];		//初始化maxn 

图的邻接矩阵的遍历方式

深度优先遍历(DFS)
    void dfs(int v){
        visit[v]=1;
        cout<<v<<' ';
        for(int i=0;i<G[v].size();i++){
            if(visit[G[v][i]]==0)
                dfs(G[v][i]);
        }
    }

广度优先遍历(BFS)
    void bfs(int s){
        queue<int>q;
        q.push(s);
        visit[s]=1;
        while(!q.empty()){
            int v=q.front();
            q.pop();
            cout<<v<<' ';
            for(int i=0;i<G[v].size();i++){
                if(visit[G[v][i]]==0){	//注意顶点是G[v][i],而不是i 
                    visit[G[v][i]]=1;
                    q.push(G[v][i]);
                }
            }
        }
    }

完整程序代码

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1005;	//最大定点数 
    int N,M;
    vector<int>G[maxn];		//初始化maxn 
    int visit[maxn]={0};    //初始化全部顶点为未访问
    void dfs(int v){
        visit[v]=1;     //标记已访问
        cout<<v<<' ';   //访问方式
        for(int i=0;i<G[v].size();i++){ //与邻接矩阵不同的是,只需要遍历与v相连接的顶点
            if(visit[G[v][i]]==0)   //如果未访问
                dfs(G[v][i]);   //访问
        }
    }
    void bfs(int s){
        queue<int>q;
        q.push(s);
        visit[s]=1;
        while(!q.empty()){
            int v=q.front();
            q.pop();
            cout<<v<<' ';
            for(int i=0;i<G[v].size();i++){
                if(visit[G[v][i]]==0){	//注意顶点是G[v][i],而不是i 
                    visit[G[v][i]]=1;
                    q.push(G[v][i]);
                }
            }
        }
    }
    int main(){
        cin>>N>>M;	//N为定点数1~N,M为边的个数 
        for(int i=0;i<N;i++){
            int a,b;
            scanf("%d %d",&a,&b);
            //无向图输入 
            G[a].push_back(b);  //表示a与b相连
            G[b].push_back(a);	//表示b与a相连
            
            //有向图输入
            //G[a].push_back(b); //表示a指向b 
        }
    	dfs(1);		//深度优先遍历 
    //	bfs(1);		//广度优先遍历 
        return 0;
    }

图的相关算法

最短路径

单源最短路径(Dijktra)

Dijkstra脱单法则:找到所有自己亲近的朋友,以这些朋友为中介,让他们找到你想追的女孩的最短路径🤪


    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1005;
    const int inf=0xfffffff;	//设inf为无穷大 
    int G[maxn][maxn];	//存放图 
    int N,M,dis[maxn+1];//N为顶点数,M为边数,dis是存放源点到各个顶点的距离 
    bool visit[maxn]={false};//访问 
    void Dijkstra(int s){	//s为源点 
        fill(dis,dis+maxn+1,inf);//初始化源点到各顶点的距离都为无穷 
        dis[s]=0;	//源点到源点的距离为0 
        for(int i=1;i<=N;i++){	//循环N次 
            int u=-1,min=inf;	 
            for(int j=1;j<=N;j++){	//找到未访问,且与源点距离最短的点 
                if(dis[j]<min&&visit[j]==false){
                    u=j;
                    min=dis[j];
                }
            }
            visit[u]=true;	//标记未已访问 
            if(u==-1) return;	//如果找不到则u还是为-1,即该源点与其他顶点不连通 
            for(int v=1;v<=N;v++){
                if(visit[v]==false&&G[v][u]!=inf&&dis[u]+G[u][v]<dis[v]){	//以顶点u作为标尺优化所有能使距离变小的且未访问的顶点 
                    dis[v]=G[u][v]+dis[u];	//更新dis[v] 
                }
            }
        }
    }

    int main(){
        cin>>N>>M;
        fill(G[0],G[0]+maxn*maxn,inf);    //初始化所有边的距离为无穷
        for(int i=0;i<M;i++){
            int a,b,c;		//a,b为顶点,c为权值(距离) 
            cin>>a>>b>>c;
            G[a][b]=G[b][a]=c;	//无向图
            //有向图可写成以下
            //G[a][b]=1; 
        }
        Dijkstra(1);	//源点为1 
        for(int i=1;i<=N;i++){
            cout<<dis[i]<<' ';
        }
        return 0;
    }
    /**输入**
    5 6
    1 2 3
    1 3 1
    2 4 2
    2 5 1
    3 4 6
    3 5 5
    */

    /**输出** 
    0 3 1 5 4
    */ 

Dijkstra+DFS求解算法

算法思路分两部分:
1、对源点进行Dijkstra,在求最短路径的时候,需要用到二维数组pre[maxn]记录前驱顶点
2、通过对终点进行DFS,可枚举所用的最短路径,可统计最短路径的条数、或者计算每条路径的优化尺度等等,选择最优的路径。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1005;	//最大顶点数 
    const int inf=0x3fffffff;    //设inf为无穷大 
    int G[maxn][maxn];  //存放图 
    int N,M,st,ed,dis[maxn+1],n;//N为顶点数,M为边数,dis是存放源点到各个顶点的距离,n为最短路径的条数
    bool visit[maxn]={false};//访问 
    vector<int>pre[maxn],path;//pre[]为二维数组,来存每个顶点的前驱顶点,path为dfs时走的路径 
    void Dijkstra(int s){   //s为源点 
        fill(dis,dis+maxn+1,inf);//初始化源点到各顶点的距离都为无穷 
        dis[s]=0;   //源点到源点的距离为0 
        for(int i=1;i<=N;i++){  //循环N次 
            int u=-1,min=inf;    
            for(int j=1;j<=N;j++){  //找到未访问且与源点距离最短的点 
                if(dis[j]<min&&visit[j]==false){
                    u=j;
                    min=dis[j];
                }
            }
            visit[u]=true;  //标记未已访问 
            if(u==-1) return;   //如果找不到则u还是为-1,即该源点与其他顶点不连通 
            for(int v=1;v<=N;v++){
                if(visit[v]==false&&G[u][v]!=inf){
                    if(dis[u]+G[u][v]<dis[v]){   //以顶点u作为标尺优化所有能使距离变小的且未访问的顶点 
                        dis[v]=G[u][v]+dis[u];  //更新dis[v]
                        pre[v].clear();			//找到更短的路径,则删除顶点v的所有的前驱顶点 
                        pre[v].push_back(u);	//将u作为新的前驱顶点 
                    }
                    else if(dis[u]+G[u][v]==dis[v]){//如果相等则说明短路路径不只一条 
                        pre[v].push_back(u);//同样把u作为v的前驱结点 
                    }		
                }

            }
        }
    }
    void DFS(int ed){	//ed为终点 
        path.push_back(ed);//将顶点ed放入路径中 
        if(ed==st){	//如果dfs到起点,这时一条以起点为st终点为ed的最短路径生成
            n++;
            for(int i=path.size()-1;i>=0;i--){	//由于是从终点往前dfs,故所走的路径是倒过来的,逆序输出即可 
                cout<<path[i]<<' ';
            }
            cout<<endl;
        }
        for(int i=0;i<pre[ed].size();i++){	//DFS顶点ed所有的前驱顶点 
            DFS(pre[ed][i]);
        }
        path.pop_back();	//递归到此处,所有经过顶点ed的路径已枚举完毕,即可删除。	
    }
    int main(){
        cin>>N>>M>>st>>ed;	//N、M分别为顶点数跟边数,st为源点,ed为终点 
        fill(G[0],G[0]+maxn*maxn,inf);    //初始化所有边的距离为很大的数来表示无穷 
        for(int i=0;i<M;i++){
            int a,b,c;      //a,b为顶点,c为权值(距离) 
            cin>>a>>b>>c;
            G[a][b]=G[b][a]=c;  //无向图
            //有向图可写成以下
            //G[a][b]=1; 
        }
        Dijkstra(st);    //源点为st
        DFS(ed);    //终点为ed
        return 0;
    }
    /**输入**
    6 7 1 6
    1 2 3
    1 3 4
    1 5 1
    2 3 1
    2 4 1
    3 6 2
    4 5 1
    */

    /**输出**
    1 3 6
    1 2 3 6
    1 5 4 2 3 6
    */ 

全源最短路径(Floyd)


    #include<bits/stdc++.h>
    using namespace std;
    const int maxv=500;	//最大顶点数
    const int INF=0xfffffff;	//设INF为很大的数
    int N,M;	//N、M分别为顶点数跟边数 
    int dis[maxv][maxv];
    void Floyd(){
        for(int k=0;k<N;k++){
            for(int i=0;i<N;i++){
                for(int j=0;j<N;j++){
                    if(dis[i][k]!=INF&&dis[k][j]!=INF&&dis[i][k]+dis[k][j]<dis[i][j]){
                        dis[i][j]=dis[i][k]+dis[k][j];
                    }
                }
            }
        }
    }
    int main(){
        cin>>N>>M;
        fill(dis[0],dis[0]+maxv*maxv,INF);	//初始化图中的所有边都为INF,即所有边都不存在 
        for(int i=0;i<N;i++)
            dis[i][i]=0;		//顶点i到i的距离为0 
        for(int i=0;i<M;i++){
            int u,v,w;	//u、v为顶点,w为边权 
            cin>>u>>v>>w;
            //dis[u][v]=G[v][u]=w;	//无向图
            dis[u][v]=w;	//有向图 
        }
        Floyd();	//算法入口 
        for(int i=0;i<N;i++){	//打印 
            for(int j=0;j<N;j++)
                cout<<dis[i][j]<<' ';
            cout<<endl;
        } 
        return 0;
    }
    /**输入**
    6 8
    0 1 1
    0 3 4
    0 4 4
    1 3 2
    2 5 1
    3 2 2
    3 4 3
    4 5 3
    */

    /**输出**
    0 1 5 3 4 6
    268435455 0 4 2 5 5
    268435455 268435455 0 268435455 268435455 1
    268435455 268435455 2 0 3 3
    268435455 268435455 268435455 268435455 0 3
    268435455 268435455 268435455 268435455 268435455 0
    */ 
    

最小生成树

最小生成树之Prim

Prim跟Dijkstra的思路基本相同,区别是dis数组的意义不同,在Dijsktra算法中dis数组记录的是顶点到源点的距离,而Prim算法中dis记录的是顶点到集合的距离。

    
    #include<bits/stdc++.h>
    using namespace std;
    const int maxv=1005;	//最大顶点数
    const int INF=0xfffffff;	//设INF为很大的数
    int N,M,G[maxv][maxv];	//N、M分别为顶点数跟边数 
    int dis[maxv];	//顶点与集合S的最短距离
    bool visit[maxv]={false};	//标记是否访问的数组,初始化为未访问
    int Prim(int s){	//参数s为源点 
        fill(dis,dis+maxv,INF);	//初始化源点s到所有点的距离为INF 
        dis[s]=0;	//源点到自己的距离为0 
        int sum=0;	//记录边权之和 
        for(int i=0;i<N;i++){
            int u=-1,MIN=INF;
            for(int j=0;j<N;j++){	
                if(visit[j]==false&&dis[j]<MIN){
                    u=j;
                    MIN=dis[j];
                }
            }
            if(u==-1)return -1;
            visit[u]=true;	//标记已访问 
            sum+=dis[u];	//计算边权 
            for(int v=0;v<N;v++){
                //如果以u为中介能使dis[v]距离更短,则优化
                if(visit[v]==false&&G[u][v]!=INF&&G[u][v]<dis[v]){ 
                    dis[v]=G[u][v];	//将G[u][v]赋给dis[v] 
                }
            }
        }
        return sum;	//返回最小生成树的边权之和 
    } 
    int main(){
        cin>>N>>M;
        fill(G[0],G[0]+maxv*maxv,INF);	//初始化图中的所有边都为INF,即所有边都不存在 
        for(int i=0;i<M;i++){
            int u,v,w;	//u、v为顶点,w为边权 
            cin>>u>>v>>w;
            G[u][v]=G[v][u]=w;	//无向图
            //G[u][v]=w;	有向图 
        }
        cout<<Prim(0);	//以0为源点 
        return 0;
    }
    /**输入**
    6 10
    0 1 4
    0 4 1
    0 5 2
    1 2 6
    1 5 3
    2 3 6
    2 5 5
    3 4 4
    3 5 5
    4 5 3
    */

    /**输出**
    15
    */ 
 

最小生成树之Kruskal

Kruskal算法最耗时的部分是排序,所以适合用于边少的图(稀疏图)

算法思想:
1、对所有边按边权从小到大排序。
2、然后遍历所有边,依次判断该边的两个顶点是否在同一个集合中(利用并查集中的查找父亲函数Find()),如果不在同一个集合则将它们合并(利用并查集中的Union(a,b)函数)且统计合并的边数跟总边权。
3、遍历完后,判断边数是否是顶点数减一,是则是连通图,反之不是连通图。


    #include<bits/stdc++.h>
    using namespace std;
    struct Node{
        int u,v;
        int cost;
    };
    const int Maxn = 10005; //最大顶点数
    int N,M,cost=0,egen=0;	//N、M分别为顶点数跟边数,cost为边权,egen为生成树的边 
    vector<Node >T;
    int father[Maxn]; 
    int Find(int x){
        while(x!=father[x]){	//找树根 
            x=father[x];
        }
        return x;
    }
    void Union(int x,int y){	//并查集合并两个元素到一个集合中 
        x=Find(x);
        y=Find(y);
        if(x<y)
            father[x]=y;
        else
            father[y]=x;
    }

    int cmp(Node a,Node b){
        return a.cost<b.cost;
    }

    int Kruskal(){
        for(int i=0;i<=N;i++)	//初始化顶点的根为自己 
            father[i]=i;
        sort(T.begin(),T.end(),cmp);	//对边进行排序 
        for(int i=0;i<T.size();i++){	//遍历所有边 
            if(Find(T[i].u) != Find(T[i].v)){	//如果两个顶点不在一个集合中
                Union(T[i].u,T[i].v);	//合并一个集合中 
                cost+=T[i].cost;	//统计边权 
                egen++;		//统计边数 
            }
        }
        if(egen!=N-1)return -1;	//如果边数 != 顶点数-1。说明该图不连通返回-1 
        else return cost; 
    }

    int main(){
        cin>>N>>M;
        for(int i=0;i<M;i++){
            Node temp;
            cin>>temp.u>>temp.v>>temp.cost;
            T.push_back(temp);
        }
        cout<<Kruskal();
        return 0;
    }

    /**输入** 
    6 10
    0 1 4
    0 4 1
    0 5 2
    1 2 1
    1 5 3
    2 3 6
    2 5 5
    3 4 5
    4 5 3
    3 5 4

    */ 
    /**输出** 
    11
    */
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值