图算法题——晴问题库
此处记录图类型算法题的各种问题,以晴神的题库为基准
记录思路和题解核心
详细题目见晴问网站,感谢晴神
图类型算法题算是数据结构类型算法题中较为复杂的一种类型,概括来说有以下几种题型:
- 度
- 邻接矩阵
- 邻接表
- 连通
- 层号和顶点挂钩
- 判环
- 最短路径
- 最小生成树
- 拓扑排序
- 关键路径
前三者为图的基础,一定要掌握。
后四者加粗为图中重要算法考点,掌握核心思想的同时也需要掌握其变换的多种题型。如最短路径问题就有很多小分支衍生问题。详情可见Dijsktra算法理解笔记。
图的问题非常需要归纳,做题时可以动手画一画会变得清晰很多。
下面以晴问题库为基础进行练习和总结。
- 无向图的度
核心:在于构建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;
}
注意点:注意这里输出的空格规范。算法题常出这些空格膈应人,所以小心为上。
- 有向图的度
核心:和无向图做一个区别在于。有向图分出度,入度。所以设置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;
}
- 无向图的邻接矩阵
核心:在于建立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));
...
- 有向图的邻接矩阵
核心:与有向图不同的只在于不需要对称书写。
#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;
}
- 无向图的邻接表
核心:这里使用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()【常用】
这篇博文说的较为好理解
- 有向图的邻接表
核心:与无向图的区别就在于是否对称
#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;
}
- 无向图的连通块
核心:使用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;
}
- 无向连通图
与上一题挂钩,连通图即是只有一个连通块的无向图。
#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;
}
- 有向图判环【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