PAT甲级备考——图论
题目
PAT (Advanced Level) Practice
图论:1150, 1142,1126, 1122
图的遍历:1076, 1034, 1021, 1013
图的连通分量: 1013, 1021
【1126】dfs计算连通图节点数,判断 Eulerian图
【1122】图的遍历 Hamiltonian cycle
【1150】图的遍历,旅行商人问题
【1142】图论、最大的连通小圈子
【1076】图的遍历、bfs递归超时改用queue代替(😀)
【1034】图的遍历 dfs 队列(😀)
【1021】图的遍历、dfs、图的连通分量
【1013】图的遍历、dfs、图的连通分量(😀)
【1126】dfs计算连通图节点数,判断 Eulerian图
Eulerian Path (25 分)
Eulerian Graph: connected graphs with all vertices of even degree 连通图+每个节点的度均为偶数
Semi-Eulerian Graph: exactly two vertices of odd degree, all Eulerian paths start at one of them and end at the other. A graph that has an Eulerian path but not an Eulerian circuit 连通图+有且仅有两个节点的度为奇数
Non-Eulerian Graph:其他情况
方法:
先构建邻接矩阵,计算每个节点的度;判断每个节点度的奇偶性
再dfs遍历图,判断连通图中节点数目,若遍历计算的节点数目为输入的节点数目,则整个图为连通图
#include<iostream>
#include<vector>
using namespace std;
vector<int> degrees;
vector<int> visited;
int vertices[510][510] = {0};
int N, M, count = 0;
// 深度遍历计算连通图的节点数目
void dfs(int index){
visited[index] = 1; // 访问节点index
count++ ; // 节点数目加1
for(int i=1; i<N+1; i++){
if(vertices[index][i]==0) continue;
if(visited[i]==0){ // 如果i节点与index节点有联通边,且没有被访问过
dfs(i);
}
}
}
int main(){
cin>>N>>M;
int x, y;
for(int i=0; i<M; i++){
scanf("%d %d", &x, &y);
vertices[x][y] = vertices[y][x] = 1;
}
// 计算每个节点的度
int odd_number = 0;
degrees.resize(N+1);
visited.resize(N+1);
for(int i=1; i<N+1; i++){
for(int j=1; j<N+1; j++){
degrees[i] += vertices[i][j];
}
if(degrees[i]%2==1) odd_number += 1;
printf("%d%s", degrees[i], i==N?"\n":" ");
}
// 得25分一个测试点不过的原因是,没有测试这个图是否是连通图!!!
// if(odd_number == 0) printf("Eulerian\n");
// else printf("%s-Eulerian", odd_number==2?"Semi":"Non");
dfs(1);
if(count==N && odd_number==0) printf("Eulerian\n");
else printf("%s-Eulerian", (count==N && odd_number==2)?"Semi":"Non");
return 0;
}
【1122】图的遍历 Hamiltonian cycle
Hamiltonian Cycle (25 分)
Hamiltonian cycle:to find a simple cycle that contains every vertex in a graph
方法:
构建邻接矩阵
根据以下两个条件判断是否是 Hamiltonian cycle:
judge1:按照这个路径遍历,是否有不存在的边
judge2:(1)路径中节点数是否是所有节点数加1
(2)路径中第一个节点与最后一个节点是否相同
(3)路径中是否出现了所有节点
#include<iostream>
#include<vector>
#include<set>
using namespace std;
int main(){
int N, M, x, y, count;
scanf("%d%d", &N, &M);
int vertices[201][201] = {0};
for(int i=0; i<M; i++){
scanf("%d %d",&x, &y);
vertices[x][y] = 1;
vertices[y][x] = 1;
}
cin>>count;
while(count--){
int number;
scanf("%d", &number);
vector<int> num_lst(number);
set<int> num_set;
int a, b;
bool judge1 = true, judge2 = true;
scanf("%d", &num_lst[0]);
num_set.insert(num_lst[0]);
for(int i=1; i<num_lst.size(); i++){
scanf("%d", &num_lst[i]);
num_set.insert(num_lst[i]);
a = num_lst[i-1];
b = num_lst[i];
if(vertices[a][b] == 0){
judge1 = false;
}
}
if(number!=N+1 || num_lst[number-1] != num_lst[0] || num_set.size()!=N){
judge2 = false;
}
printf("%s",(judge1&&judge2)?"YES\n":"NO\n");
}
return 0;
}
【1150】图的遍历,旅行商人问题
Travelling Salesman Problem (25 分)
给出⼀条路径,判断这条路径是这个图的旅⾏商环路、简单旅⾏商环路还是⾮旅⾏商环路
方法:
先根据输入构建图的邻接矩阵;
接着根据所给的path,判断路径中的节点是否包含了所有节点,路径中第一个节点是否和最后一个节点相同,及路径是否有效(是否连通)
输出最短路径和最短路径的长度
注意:
引入INT_MAX, INT_INF的时候,需要#include
#include<iostream>
#include<vector>
#include<set>
#include<algorithm>
using namespace std;
int N, M;
int vertices[210][210] = {0};
int main(){
cin>>N>>M;
int a,b, dist;
for(int i=0; i<M; i++){
scanf("%d %d %d", &a, &b, &dist);
vertices[a][b] = vertices[b][a] = dist;
}
int K;
cin>>K;
int shortest_path = -1, shortest_path_dist = 1000000;
for(int k=0; k<K; k++){
printf("Path %d: ",k+1);
// 输入构建图的邻接矩阵
int n;
scanf("%d", &n);
vector<int> path(n);
set<int> city_set;
for(int i=0; i<n; i++){
scanf("%d", &path[i]);
city_set.insert(path[i]);
}
bool cycle = true;
int total_distance = 0;
for(int i=0; i<n-1; i++){
if(vertices[path[i]][path[i+1]] == 0){ //如果在节点path[i]和path[i+1]之间没有边,则这条路径失效,设置cycle为false
printf("NA");
cycle = false;
break;
}
total_distance += vertices[path[i]][path[i+1]]; // 计算总距离
}
if(cycle) printf("%d",total_distance); //如果路径有效则打印距离,否则打印NA
if(city_set.size()==N && path[0]==path[n-1] && cycle){ // 判断路径中的节点是否包含了所有节点,路径中第一个节点是否和最后一个节点相同,及路径是否有效
if(n==N+1){
printf(" (TS simple cycle)\n");
}else{
printf(" (TS cycle)\n");
}
if(total_distance<shortest_path_dist){ // 选择路径总长度最短的路径
shortest_path = k+1;
shortest_path_dist = total_distance;
}
}else{
printf(" (Not a TS cycle)\n");
}
}
printf("Shortest Dist(%d) = %d\n", shortest_path, shortest_path_dist);
}
【1142】最大的连通小圈子
这里节点数目不多,可以用二维数组保存边信息。
#include<iostream>
#include<vector>
#include<set>
#include<algorithm>
using namespace std;
int N, M;
int vertices[210][210] = {0};
int main(){
cin>>N>>M;
int a,b, dist;
for(int i=0; i<M; i++){
scanf("%d %d %d", &a, &b, &dist);
vertices[a][b] = vertices[b][a] = dist;
}
int K;
cin>>K;
int shortest_path = -1, shortest_path_dist = 1000000;
for(int k=0; k<K; k++){
printf("Path %d: ",k+1);
// 输入构建图的邻接矩阵
int n;
scanf("%d", &n);
vector<int> path(n);
set<int> city_set;
for(int i=0; i<n; i++){
scanf("%d", &path[i]);
city_set.insert(path[i]);
}
bool cycle = true;
int total_distance = 0;
for(int i=0; i<n-1; i++){
if(vertices[path[i]][path[i+1]] == 0){ //如果在节点path[i]和path[i+1]之间没有边,则这条路径失效,设置cycle为false
printf("NA");
cycle = false;
break;
}
total_distance += vertices[path[i]][path[i+1]]; // 计算总距离
}
if(cycle) printf("%d",total_distance); //如果路径有效则打印距离,否则打印NA
if(city_set.size()==N && path[0]==path[n-1] && cycle){ // 判断路径中的节点是否包含了所有节点,路径中第一个节点是否和最后一个节点相同,及路径是否有效
if(n==N+1){
printf(" (TS simple cycle)\n");
}else{
printf(" (TS cycle)\n");
}
if(total_distance<shortest_path_dist){ // 选择路径总长度最短的路径
shortest_path = k+1;
shortest_path_dist = total_distance;
}
}else{
printf(" (Not a TS cycle)\n");
}
}
printf("Shortest Dist(%d) = %d\n", shortest_path, shortest_path_dist);
}
【1021】图的遍历、dfs、图的连通分量、判断图中最高高度的树,正向dfs+反向dfs可以找全
题⽬⼤意:给出n个结点(1~n)之间的n条边,问是否能构成⼀棵树,如果不能构成则输出它有的连通分量个数,如果能构成⼀棵树,输出能构成最深的树的⾼度时,树的根结点。如果有多个,按照从⼩到⼤输出。
分析:⾸先深度优先搜索判断它有⼏个连通分量。
(1)如果有多个,那就输出Error: x components,
(2)如果只有⼀个,就两次深度优先搜索,先从⼀个结点dfs后保留最⾼⾼度拥有的结点们,然后从这些结点中的其中任意⼀个开始dfs得到最⾼⾼度的结点们,这两个结点集合的并集就是所求
#include<iostream>
#include<vector>
#include<set>
using namespace std;
int max_height=-1;
vector<int> result;
vector<vector<int>> v;
set<int> s; // 保存符合最高高度条件的节点
int visited[10010] = {0};
void dfs(int index, int height){
if(height>max_height){
max_height = height;
result.clear();
result.push_back(index);
}else if(height==max_height){
result.push_back(index);
}
visited[index] = 1;
for(int i=0; i<v[index].size(); i++){
if(visited[v[index][i]]==0)
dfs(v[index][i], height+1);
}
}
int main(){
// 输入建图
int N, a, b, ts, cnt=0;
cin>>N;
v.resize(N+1);
for(int i=0; i<N-1; i++){
scanf("%d %d", &a, &b);
v[a].push_back(b);
v[b].push_back(a);
}
// 判断有多少连通分量
for(int i=1; i<=N; i++){
if(visited[i]==0){
dfs(i,1);
if(i==1){
if(result.size()!=0) ts = result[0];// 在当前最高高度中的节点中随便选一个保存为ts
for(int j=0; j<result.size(); j++)
s.insert(result[j]); // 保存最高高度的节点
}
cnt++;// 计算连通分量的数目
}
}
if(cnt > 1){
printf("Error: %d components\n", cnt);
return 0;
}
// 从最高高度出发
result.clear();
max_height = -1;
fill(visited, visited+10010, 0);
dfs(ts,1);
for(int j=0; j<result.size(); j++){
s.insert(result[j]);
}
for(auto it = s.begin(); it != s.end(); it++)
printf("%d\n", *it);
}