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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值