图(2)——有向图

来源:互联网 发布:linux安装tortoisesvn 编辑:程序博客网 时间:2024/06/03 22:14

有向图

在有向图中,边是单向的:每条边所连接的两个顶点是一个有序对,他们的邻接性是单向的。


术语

有向图:由一组由方向的边组成的,每条有方向的边都连接着有序的一对顶点。
有向路径:由一系列顶点组成,对于其中的每个顶点都存在一条有向边从他指向序列的下一个顶点。
有向环:为一条至少含有一条边且起点和终点相同的有向路径。
简单有向环:一条除了起点终点外不含重复的顶点和边的环。
路径或环的长度:即为其包含的边数。

有向图解决的问题

有向图处理问题:有向图的构建Digraph单点和多点的可达性DirectedDFS单点有向路径DepthFirstDirectedPaths单点最短路径BreadthFirstDiretedPaths有向环检测DirectedCycle深度优先的顶点排序DepthFirstOrder优先级限制下的调度问题Topological拓扑排序Topological强连通性KosarajuSCC顶点对的可达性TransitiveClosure

实现:

1.有向图的构建

有向图的构建与无向图基本相似,只是把边的双向输入变成了单向输入即可。
代码:
#ifndef DIGRAPH_H#define DIGRAPH_H#include<iostream>using std::cout;using std::endl;using std::cin;#include<vector>using std::vector;#include"Bag.h"class Digraph{private:int V = 0;//顶点数int E = 0;//边数vector<Bag<int>> adj;//邻接表public:Digraph(int V) :V(V), E(0), adj(V){}Digraph(int V, int E) :V(V), E(0), adj(V){int v, w;for (int i = 0; i < E; i++){cout << "输入第" << i << "条边的两个顶点:" << endl;cin >> v >> w;addEdge(v, w);}}int getV(){ return V; }int getE(){ return E; }void addEdge(int v, int w){adj[v].add(w);E++;}Bag<int> getadj(int v){ return adj[v]; }Digraph reverse(){Digraph R(V);for (int v = 0; v < V; v++)for (int w : getadj(v))R.addEdge(w, v);return R;}};#endif

测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"using namespace std;int main(){cout << "输入图顶点数和边数:" << endl;int v, e;cin >> v >> e;Digraph d(v, e);cout << "输出邻接表:" << endl;for (int i = 0; i < d.getV(); i++){cout << i << ": ";for (auto w : d.getadj(i))cout << w << " ";cout << endl;}Digraph R = d.reverse();cout << "图的反序:" << endl;for (int i = 0; i < R.getV(); i++){cout << i << ": ";for (auto w : R.getadj(i))cout << w << " ";cout << endl;}system("pause");}


2.有向图的可达性(基于深度优先搜索)

可达性也就是连通性,代码DepthFirstSearch基本没区别。
代码:
#ifndef DIRECTEDDFS_H#define DIRECTEDDFS_H#include<vector>using std::vector;#include"Digraph.h"class DiredtedDFS{private:vector<bool> marked;public:DiredtedDFS(Digraph G, int s) :marked(G.getV()){//单个顶点可达性dfs(G, s);}DiredtedDFS(Digraph G, vector<int> sources) :marked(G.getV()){//搜索数组中所有顶点的可达性(多点)for (int s : sources)if (!marked[s])dfs(G, s);}void dfs(Digraph G, int v){marked[v] = true;for (int w : G.getadj(v))if (!marked[w])dfs(G,w);}bool getmarked(int v){ return marked[v]; }//查询点是否可达};#endif


测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"#include"DirectedDFS.h"using namespace std;int main(){cout << "输入图顶点数和边数:" << endl;int v, e;cin >> v >> e;Digraph G(v, e);DiredtedDFS r1(G, 0);cout << "一个顶点的可达性:" << endl;for (int i = 0; i < G.getV(); i++)if (r1.getmarked(i))cout << i << " ";cout << endl;vector<int> sources = { 0,1, 2 };DiredtedDFS r2(G, sources);cout << "多个顶点的可达性:" << endl;for (int i = 0; i < G.getV(); i++)if (r2.getmarked(i))cout << i << " ";cout << endl;system("pause");return 0;}


3.单点有向路径

代码与DepthFirstPaths基本相同。

代码:
#ifndef DFDP_H#define DFDP_H#include<vector>using std::vector;#include"Digraph.h"#include"stack.h"class DepthFirstDirectedPaths{private:vector<bool> marked;//标记vector<int> edgeTo;//记录上一个结点int s;//起点public:DepthFirstDirectedPaths(Digraph G, int s) :marked(G.getV()), edgeTo(G.getV()), s(s){dfs(G, s);}void dfs(Digraph G, int v){marked[v] = true;for (int w : G.getadj(v))if (!marked[w]){dfs(G, w);edgeTo[w] = v;}}bool hasPathTo(int v){ return marked[v]; }stack<int> pathTo(int v){stack<int> path;if (!hasPathTo(v))return path;for (int i = v; i != s; i = edgeTo[i])path.push(i);path.push(s);return path;}};#endif 


测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"#include"DepthFirstDirectedPaths.h"using namespace std;int main()//DepthFirstDirectedPaths.h{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Digraph G(n, m);cout << "输入查询顶点:" << endl;cin >> s;DepthFirstDirectedPaths Paths(G, s);for (int v = 0; v < G.getV(); v++){cout << s << " to " << v << ": ";if (Paths.hasPathTo(v))for (int x : Paths.pathTo(v))if (x == s)cout << x;else cout << "-" << x;cout << endl;}system("pause");return 0;}


4.单点最短有向路径(基于广度优先搜索)

与BreadthFirstPaths基本相同。

代码:
#ifndef BFDP_H#define BFDP_H#include<vector>using std::vector;#include"Digraph.h"#include"queue.h"#include"stack.h"class BreadthFirstDirectedPaths{private:vector<bool> marked;//标记vector<int> edgeTo;//结点的上一位置int s;//起点public:BreadthFirstDirectedPaths(Digraph G, int s) :marked(G.getV()), edgeTo(G.getV()), s(s){dfs(G, s);}void dfs(Digraph G, int s){queue<int> que;marked[s] = true;que.enqueue(s);while (!que.Empty()){int v = que.dequeue();//取出结点for (auto w : G.getadj(v))//遍历结点链表if (!marked[w])//结点为被标记{ que.enqueue(w);//进队marked[w] = true;//标记edgeTo[w] = v;//记录到达结点的上一结点}}}bool haspathTo(int v){ return marked[v]; }stack<int> pathTo(int v){stack<int> path;if (!haspathTo(v))return path;for (int i = v; i != s; i = edgeTo[i])path.push(i);path.push(s);return path;}};#endif


测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"#include"BreadthFirstDirectedPaths.h"using namespace std;int main()//BreadthFirstDirectedPaths.h{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Digraph G(n, m);cout << "输入查询顶点:" << endl;cin >> s;BreadthFirstDirectedPaths paths(G, s);for (int v = 0; v < G.getV(); v++){cout << s << " to " << v << ": ";if (paths.haspathTo(v))for (int x : paths.pathTo(v))if (x == s)cout << x;else cout << "-" << x;cout << endl;}system("pause");return 0;}


5.有向环检测

在有向图相关的实际应用中,有向环特别重要。所以检测有向环是否存在,自然也就变得很重要了。
有向无环图:不含环的有向图。
与代码Cycle相似,但是我们不仅需要查询环是否存在,还需要输出环的结点,所以需要添加一个栈用于记录环,一个数组记录经过结点是否可能在环中。
代码:
#ifndef DC_H#define DC_H#include<vector>using std::vector;#include"Digraph.h"#include"stack.h"class DirectedCycle{private:vector<bool> marked;vector<int> edgeTo;stack<int> cycle;//有向环中的顶点vector<bool> onstack;//递归调用的栈上的所有顶点public:DirectedCycle(Digraph G) :marked(G.getV()), edgeTo(G.getV()), onstack(G.getV()){for (int v = 0; v < G.getV(); v++)if (!marked[v])dfs(G, v);}void dfs(Digraph G, int v){onstack[v] = true;//标记结点,可能在环中marked[v] = true;//标记结点,已经过for (auto w : G.getadj(v))//遍历子链表if (hasCycle()) return;//已经有环else if (!marked[w])//没有经过这个结点{edgeTo[w] = v;//记录结点来时的路径dfs(G, w);//查询结点子链表}else if (onstack[w])//在次出现该结点,说明有该结点的环{//逆序追踪到上一次出现该结点的地点for (int x = v; x != w; x = edgeTo[x])cycle.push(x);cycle.push(w);cycle.push(v);}onstack[v] = false;//检测完毕,除去该结点记录}bool hasCycle(){ return !cycle.isEmpty(); }//是否有环stack<int> getcycle(){ return cycle; }//输出环};#endif


测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"#include"DirectedCycle.h"using namespace std;int main()//DirectedCycle.h{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Digraph G(n, m);DirectedCycle cy(G);stack<int> cycle = cy.getcycle();if (cy.hasCycle()){for (auto w : cycle)cout << w << " ";cout << endl;}else cout << "No cycle" << endl;system("pause");return 0;}


6.顶点排序(基于深度优先搜索)

顶点排序有三种方式:
前序:在递归调用之前将顶点加入队列;
后序:在递归调用之后将顶点加入队列;
逆后序:在递归调用之后将顶点压入栈。
代码:
#ifndef DFO_H#define DFO_H#include<vector>using std::vector;#include"Digraph.h"#include"stack.h"#include"queue.h"class DepthFirstOther{private:vector<bool> marked;queue<int> pre;//先序queue<int> post;//后序stack<int> reversepost;//逆后序public:DepthFirstOther(Digraph G) :marked(G.getV()){for (int v = 0; v < G.getV(); v++)if (!marked[v])dfs(G, v);}void dfs(Digraph G, int v){pre.enqueue(v);//先序入队marked[v]=true;for (auto w : G.getadj(v))if (!marked[w])dfs(G, w);post.enqueue(v);//后序入队reversepost.push(v);//逆后续入栈}queue<int> getpre(){ return pre; }queue<int> getpost(){ return post; }stack<int> getreversepsot(){ return reversepost; }};#endif


测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"#include"DepthFirstOther.h"using namespace std;int main()//DepthFirstOther.h{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Digraph G(n, m);DepthFirstOther Do(G);queue<int> pre = Do.getpre();queue<int> post = Do.getpost();stack<int> rpost = Do.getreversepsot();for (auto w : pre)cout << w << " "; cout << endl;for (auto w : post)cout << w << " ";cout << endl;for (auto w : rpost)cout << w << " ";cout << endl;system("pause");return 0;}


7.拓扑排序

当且仅当一幅有向图是无环图时他才能进行拓扑排序。
一幅有向无环图的拓扑排序即为所有顶点的逆后序排序。
总的来说,拓扑排序就是在确定图是有向无环图之后进行逆后序的排序。

代码:
#ifndef TOPOLOGICAL_H#define TOPOLOFICAL_H#include<vector>using std::vector;#include"Digraph.h"#include"DirectedCycle.h"#include"DepthFirstOther.h"#include"stack.h"class Topological{private:stack<int> order;public:Topological(Digraph G){DirectedCycle cy(G);if (!cy.hasCycle())//不是有环图{DepthFirstOther dfs(G);order = dfs.getreversepsot();//逆后序排序}}stack<int> getorder(){ return order; }//输出排序bool isDAG(){ return !order.isEmpty(); }//是否有拓扑排序};#endif


测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"#include"Topological.h"using namespace std;int main()//Topological.h{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Digraph G(n, m);Topological top(G);if (top.isDAG()){stack<int> order = top.getorder();for (auto w : order)cout << w << " ";cout << endl;}else cout << "No top" << endl;system("pause");return 0;}


8.强连通性

强连通性:两个顶点是互相可达的。
强连通图:一幅有向图的任意两个顶点都是强连通的(只要可达即可,不一定是两个结点相连)。
kosaraju算法:在CC算法中见过类似的算法,查找无向图的连通分量。
而计算有向图的强连通分量,只需要对CC算法进行一些修改即可:
1)使用DepthFirstOrder计算图的反向图的逆后序排序
2)进行深度优先搜索,但是要按照刚才计算得到的顺序而非标准的顺序来访问所有未被标记的顶点。

简单来说,就是先将结点进行拓扑排序之后,在进行深度优先搜索。

代码:
#ifndef KOSARAJUSCC_H#define KOSARAJUSCC_H#include<vector>using std::vector;#include"DepthFirstOther.h"#include"Digraph.h"class KosarajuSCC{private:vector<bool> marked;vector<int> id;//强连通分量标识符int count;//强连通分量的数量public:KosarajuSCC(Digraph G) :marked(G.getV()), id(G.getV()), count(0){DepthFirstOther order(G);for (int s : order.getreversepsot())if (!marked[s]){dfs(G, s);count++;}}void dfs(Digraph G, int v){marked[v] = true;id[v] = count;//记录v属于哪一个连通分量for (auto w : G.getadj(v))if (!marked[w])dfs(G, w);}bool stronglyConnected(int v, int w)//v和w是否连通{return id[v] == id[w];}int getid(int v){ return id[v]; }//v属于哪一个连通分量int getcount(){ return count; }//连通分量数vector<Bag<int>> componentsTo(Digraph G)//储存连通分量{int m = getcount();vector<Bag<int>> components(m);for (int v = 0; v < G.getV(); v++)components[getid(v)].add(v);return components;}};#endif


测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"#include"KosarajuSCC.h"using namespace std;int main()//KosarajuSCC.h{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Digraph G(n, m);KosarajuSCC scc(G);m = scc.getcount();vector<Bag<int>> components = scc.componentsTo(G);for (int i = 0; i < m; i++){cout << i << " : ";for (auto v : components[i])cout << v << " ";cout << endl;}system("pause");return 0;}


9.顶点对的可达性

顶点对的可达性:是否存在一条从一个给定顶点到能一个顶点的路径。
方法:给DiredtedDFS开一个数组,将所有顶点的可达性都存储再来。这种方法不适用与大型有向图的情况。

代码:
#ifndef TC_H#define TC_H#include"DirectedDFS.h"#include"Digraph.h"class TransitiveClosure{private:vector<DiredtedDFS> all;//可达性数组public:TransitiveClosure(Digraph G) {for (int v = 0; v < G.getV(); v++)all.push_back(DiredtedDFS(G, v));//存储顶点v的可达性}bool reachable(int v, int w)//v能到达w么{return all[v].getmarked(w);}};#endif



测试用例:
#include<iostream>#include<vector>#include<string>#include"Digraph.h"#include"TransitiveClosure.h"using namespace std;int main()//TransitiveClosure.h{cout << "输入顶点数和边数:" << endl;int n, m;cin >> n>> m;Digraph G(n, m);TransitiveClosure tc(G);cout << "查询顶点a的可达性:" << endl;cout << "输入顶点a:" << endl;int a;cin >> a;for (int i = 0;i<G.getV();i++)if (tc.reachable(a, i))cout << "顶点" << a << "能达到" << i << endl;else cout << "顶点" << a << "不能达到" << i << endl;system("pause");return 0;}







原创粉丝点击