无向图的关节点问题
【问题描述】
对无向连通图,若删除某个结点使其成为非连通图,则称该结点为关节点。假设某一地区公路交通网,求解关节点。
【设计要求】
设计求解无向连通图关节点的模拟程序。
(1)采用邻接表或邻接矩阵存储结构。
(2)可以随机、文件及人工输入数据。
(3)采用深度优先遍历求解关节点。
(4)实现关节点的查询和统计功能。
(5)实现将关节点改造为非关节的功能。
(6)其它完善性或扩展性功能。
最终实现功能
看题目,嗯?关节点,没听过,再一看,这不是割点吗?那我知道这个可以用Tarjan算法,唯独要求5,将关节点改造为非关节点的功能不太清楚。但是后面清楚了,功能基本上就是题目所写的那些。
收获
第一个是Tarjan算法本身吧
第二个是图论相关知识
之前写图论的题目基本上都是凭直觉在写,但是遇见了这个洛谷对应的题解的严谨证明让我对图论很感兴趣了,于是后来也额外学了一些图论知识。
没有要分享的有趣代码片段。
将关节点改为非关节点
一个节点想改为非关节点,那只需要把这个节点周围的连通块相连即可,所以考虑在dfs处理时使用around数组记录节点的周围连通块(记录直接相连的点即可),在修改的时候直接增边即可。
完整代码
可以稍微修改AC洛谷的Tarjan算法的模板题目。
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include <set>
#include <fstream>
#include <algorithm>
#define ll long long
using namespace std;
namespace MyGraph
{
class my_graph
{
private:
typedef unsigned long long size_t;
vector<set<size_t>> graph;//为了防止输入重复边,使用set.一个简易的邻接表。
size_t arcnum;
public:
my_graph(size_t x):arcnum(0){graph.resize(x+1);}
~my_graph(){graph.clear();arcnum=0;}
const set<size_t>& get_arc(size_t x){return graph[x];}
void resize(size_t x){graph.resize(x+1);}
size_t getvnum(){return graph.size()-1;}
size_t getarcnum(){return arcnum;}
void add_edge(size_t x,size_t y){if(x==y||graph[x].count(y))return;graph[x].insert(y);++arcnum;}
};
}
void dfs(int now,MyGraph::my_graph&graph,int low[],int is_key[],set<int>&key_point,int dfn[],int &root,int &cnt,vector<int> around[])
{
dfn[now]=low[now]=++cnt;//时间戳
int son=0;
for(auto&it:graph.get_arc(now))
{
if(dfn[it]==0)
{
++son;
dfs(it,graph,low,is_key,key_point,dfn,root,cnt,around);
//补充around,为了modify
around[now].push_back(it);
around[it].push_back(now);
low[now]=min(low[now],low[it]);
if(low[it]>=dfn[now]&&now!=root&&is_key[now]==0)
key_point.insert(now),is_key[now]=1;
}
else
low[now]=min(low[now],dfn[it]);
}
if(root==now&&son>=2&&is_key[now]==0)
key_point.insert(now),is_key[now]=1;
}
void Tarjan(MyGraph::my_graph& graph,set<int>&key_point,vector<int> around[])
{
int dfn[graph.getvnum()+1]={0},low[graph.getvnum()+1]={0},is_key[graph.getvnum()+1]={0};
int root=0,cnt=0;
for(int i=0;i<=graph.getvnum();++i)
{
if(dfn[i]==0)
{
root=i;
dfs(i,graph,low,is_key,key_point,dfn,root,cnt,around);
}
}
}
//通过文件输入
void read_data_by_file(string &filename,MyGraph::my_graph &graph,int&query)
{
// 打开文件
ifstream infile(filename);
if (!infile.is_open())
{
cout << "Error: Unable to open file." << endl;
return;
}
int n, m; // 图的顶点数和边数
infile >> n >> m;
// 初始化图
graph.resize(n);
// 读入图的边信息
for (int i = 0; i < m; ++i)
{
int x, y;
infile >> x >> y;
graph.add_edge(x, y);
graph.add_edge(y, x); // 无向图需添加反向边
}
// 将新图赋值给传入的图对象
// 读入查询次数
infile >> query;
// 关闭文件
infile.close();
}
//通过手动输入
void read_data_by_input(MyGraph::my_graph&graph,int&query)
{
int n, m; // 顶点数和边数
cout << "please input point number and edge number :" << endl;
cin >> n >> m;
// 初始化图
MyGraph::my_graph new_graph(n);
// 读入图的边信息
cout << "please input the two ends of every edge :" << endl;
for (int i = 0; i < m; ++i)
{
int x, y;
cin >> x >> y;
new_graph.add_edge(x, y);
new_graph.add_edge(y, x); // 无向图需添加反向边
}
// 将新图赋值给传入的图对象
graph = new_graph;
// 读入查询次数
cout << "please input how many do you want to query :" << endl;
cin >> query;
}
//把 x点修改为非割点
void modify(int x,MyGraph::my_graph& graph,vector<int> around[])
{
for(int i=1;i<around[x].size();++i)
{
graph.add_edge(around[x][i],around[x][i-1]);
graph.add_edge(around[x][i-1],around[x][i]);
}
}
void solve()
{
char op;
cout<<"please input how do you want to input :(H is hand and F is file)"<<endl;
cin>>op;
MyGraph::my_graph graph(0);
int query=0;//query为询问或者操作次数
//手写输入 graph数据
if(op=='H')
read_data_by_input(graph,query);
else
{
// 通过文件输入graph数据,其他的还是手动输入
string filename;
cin>>filename;
read_data_by_file(filename,graph,query);
}
//获得结果。res里面全都是割点
set<int> res;
vector<int> around[graph.getvnum()+1];//保存父亲和孩子,用于modify函数
Tarjan(graph,res,around);//先初始化
cout<< "let's start to query :"<<endl;
while(query--)
{
cout<<endl;
cout<<"Q is querying one point whether is key point or not"<<endl;
cout<<"S is outputting all key points"<<endl;
cout<<"M is modifying a key point to a not key point"<<endl;
cout<<endl;
cin>>op;
int x;
if(op=='Q')//询问x点是不是割点
{
cin>>x;
cout<< (res.count(x)?"yes":"no")<<endl;
}
else if(op=='S')//输出所有割点
{
cout<<"the number of all key points is "<< res.size()<<" :"<<endl;
for(auto&it:res)
cout<<it<<' ';
cout<<endl;
}
else if(op=='M')// 把某个点修改成非割点
{
cin>>x;
//如果是,则修改
if(res.count(x))
{
modify(x,graph,around);
for(int i=0;i<=graph.getvnum();++i)
around[i].clear();
res.clear();
Tarjan(graph,res,around);
cout<< "modify finished"<<endl;
}
else//如果不是,不修改
{
cout<< "this point is not key point" <<endl;
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int t=1;
//cin>>t;
while(t--)
{
solve();
}
cout<<endl;
system("pause");
return 0;
}