图算法题——晴问题库

图算法题——晴问题库

此处记录图类型算法题的各种问题,以晴神的题库为基准
记录思路和题解核心
详细题目见晴问网站,感谢晴神

图类型算法题算是数据结构类型算法题中较为复杂的一种类型,概括来说有以下几种题型:

  • 邻接矩阵
  • 邻接表
  • 连通
  • 层号和顶点挂钩
  • 判环
  • 最短路径
  • 最小生成树
  • 拓扑排序
  • 关键路径

前三者为图的基础,一定要掌握。
后四者加粗为图中重要算法考点,掌握核心思想的同时也需要掌握其变换的多种题型。如最短路径问题就有很多小分支衍生问题。详情可见Dijsktra算法理解笔记
图的问题非常需要归纳,做题时可以动手画一画会变得清晰很多。

下面以晴问题库为基础进行练习和总结。

  1. 无向图的度

核心:在于构建degree[ ](这个一般放在主函数前面定义)。无向图顶点的度是该顶点有几条边就是度多少。

#include<cstdio>
#define maxv 100
int degree[maxv]={0};

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){//核心循环
        int a,b;
        scanf("%d%d",&a,&b);
        degree[a]++;
        degree[b]++;
    }
    printf("%d",degree[0]);
    for(int i=1;i<n;i++){
        printf(" %d",degree[i]);
    }
    return 0;
}

注意点:注意这里输出的空格规范。算法题常出这些空格膈应人,所以小心为上。

  1. 有向图的度
    核心:和无向图做一个区别在于。有向图分出度,入度。所以设置inDegree[ ]和outDegree[ ]。
#include<cstdio>
#define maxv 100
int inDegree[maxv]={0};
int outDegree[maxv]={0};

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        outDegree[a]++;
        inDegree[b]++;
    }
    for(int i=0;i<n-1;i++){
        printf("%d %d\n",inDegree[i],outDegree[i]);
    }
    printf("%d %d",inDegree[n-1],outDegree[n-1]);
    return 0;
}
  1. 无向图的邻接矩阵
    核心:在于建立e[ ][ ]邻接矩阵。无向图的核心在于它是对称的,所以要对称的加一。
#include<cstdio>
#define maxv 100
int e[maxv][maxv]={0};

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        e[a][b]++;
        e[b][a]++;//对称的加1
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            printf("%d",e[i][j]);
            if(j<n-1) printf(" ");
        }
        printf("\n");
    }
    return 0;
}

题解里面有一个初始化邻接矩阵的语句,相对来说比较规范。

const int MAXN = 100;
int G[MAXN][MAXN];

int main() {
	...
    memset(G, 0, sizeof(G));
    ...
  1. 有向图的邻接矩阵
    核心:与有向图不同的只在于不需要对称书写。
#include<cstdio>
#define maxv 100
int e[maxv][maxv]={0};

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        e[a][b]++;
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            printf("%d",e[i][j]);
            if(j<n-1) printf(" ");
        }
        printf("\n");
    }
    return 0;
}
  1. 无向图的邻接表
    核心:这里使用vector建立邻接表。这是在建立邻接表时常用的方法,在最短路径问题时也有vector的使用,用于存储边权和边的结构体数组。
#include<cstdio>
#include<vector>
using namespace std;
const int maxv=100;
vector<int> G[maxv];//核心!!

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        G[a].push_back(b);
        G[b].push_back(a);
    }
    for(int i=0;i<n;i++){
        printf("%d(%d)",i,(int)G[i].size());
        for(int j=0;j<G[i].size();j++){
            printf(" %d",G[i][j]);
        }
        printf("\n");
    }
    return 0;
}

多说几句:vector添加操作为push_back(),返回个数为.size()【常用】
这篇博文说的较为好理解

  1. 有向图的邻接表
    核心:与无向图的区别就在于是否对称
#include<cstdio>
#include<vector>
using namespace std;
const int maxv=100;
vector<int> G[maxv];int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        G[a].push_back(b);//核心区别!!
    }
    for(int i=0;i<n;i++){
        printf("%d(%d)",i,(int)G[i].size());
        for(int j=0;j<G[i].size();j++){
            printf(" %d",G[i][j]);
        }
        printf("\n");
    }
    return 0;
}
  1. 无向图的连通块
    核心:使用DFS来判断是否有连通块。BFS也可以。这里使用DFS。核心就在于要遍历几遍才可以访问完一个图。
    DFS的核心代码【邻接表】
bool vis[maxv];

void DFS(int u){
    vis[u]=true;
    for(int i=0;i<G[u].size();i++){
        int v = G[u][i];
        if(!vis[v]){
            DFS(v);
        }
    }

DFS代码的核心是从邻接表的一个起点出发,一路向前,到头了再回头。
【完整代码】

#include<cstdio>
#include<vector>
using namespace std;
const int maxv=100;
vector<int> G[maxv];
bool vis[maxv];

void DFS(int u){
    vis[u]=true;
    for(int i=0;i<G[u].size();i++){
        int v = G[u][i];
        if(!vis[v]){
            DFS(v);
        }
    }
    
}

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        G[a].push_back(b);
        G[b].push_back(a);
    }
    int count=0;
    for(int i=0;i<n;i++){
        if(!vis[i]){
            DFS(i);
            count++;
        }
    }
    printf("%d",count);
    return 0;
}
  1. 无向连通图
    与上一题挂钩,连通图即是只有一个连通块的无向图。
#include<cstdio>
#include<vector>
using namespace std;
const int maxv=100;
vector<int> G[maxv];
bool vis[maxv];

void DFS(int u){
    vis[u]=true;
    for(int i=0;i<G[u].size();i++){
        int v = G[u][i];
        if(!vis[v]){
            DFS(v);
        }
    }
    
}

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        G[a].push_back(b);
        G[b].push_back(a);
    }
    int count=0;
    for(int i=0;i<n;i++){
        if(!vis[i]){
            DFS(i);
            count++;
        }
    }
    printf(count==1?"Yes":"No");
    return 0;
}
  1. 有向图判环【UPUPUP】
    核心:这个有点复杂,主要是要探讨的情况有点杂乱。首先,明确环就是访问的过程中遇到了访问过的点。抓住这一核心定义判环函数isCircle()。
    【判环函数】判环过程选择一点起始,标记已访问,然后去访问它出发指向的结点。
    若有环,这些结点无疑只有两种情况:
  • 它被访问过
  • 它没被访问过,但它再往下有环

将这两种情况挑出来就可以了。
若访问完,那么这个起始点出发没有环。

bool isCircle(int u){
    vis[u]=0;
    for(int i=0;i<G[u].size();i++){
        int v = G[u][i];
        if(vis[v]==-1&&isCircle(v)) return true;
        else if(vis[v]==0) return true;
    }
    vis[u]=1;
    return false;
}

由此就可以书写【完整代码】。在主函数中,遍历整个图的每一个部分。判断该部分是否有环。

#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int maxv=100;
vector<int> G[maxv];
int vis[maxv];

bool isCircle(int u){
    vis[u]=0;
    for(int i=0;i<G[u].size();i++){
        int v = G[u][i];
        if(vis[v]==-1&&isCircle(v)) return true;
        else if(vis[v]==0) return true;
    }
    vis[u]=1;
    return false;
}

int main(){
    memset(vis,-1,sizeof(vis));
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        G[a].push_back(b);
    }
   int result=false;
    for(int i=0;i<n;i++){
        if(vis[i]==-1&&isCircle(i)){
            result=true;
        }
        if(result) break;
    }
    printf(result?"Yes":"No");
    return 0;
}

最新更新2021.1.12

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值