图(1)——无向图

来源:互联网 发布:js 添加dom 动画 编辑:程序博客网 时间:2024/06/16 03:06

无向图

术语表

:由一组顶点个一组能够将两个顶点相连的边组成。

自环:一条连接一个顶点和其自身的边。

平行边:连接同一对顶点的两条边称为平行边。

多重图:含有平行边。

简单图:没有平行边和自环。

相邻:两个顶点通过一条边相连,并称这条边依附这两个顶点。

顶点的度数:依附于他的边的数量。

子图:一幅图的所有边的一个子集(以及所依附的所有顶点)组成的图。

路径:由边顺序连接的一系列顶点。

简单路径:一条没有重复顶点的路径。

:一条至少含有一条边且起点和终点相同的路径。

简单环:除起点外不含重复顶点和边的环。

路径或环的长度:包含的边数。

文中环和路径指简单环和简单路径,一般环和路径指有可重复的顶点。

连通图:如果任意一个顶点都存在一条路径到达另一个任意顶点,我们称这幅图是连通图。

极大连通子图:一幅非连通的图由若干连通部分组成,他们都是极大连通子图。

无环图:一种没有环的图。

:一幅无环连通子图。

森林:换不相连的树。

连通图的生成树:他的一幅子图,含有图中所有的顶点且是一棵树。

图的生成树森林:他的所有连通子图的生成树的集合。


当一幅含有V个结点的图G满足下列5个条件之一时,他就是一棵树:

1)G有V-1条边且不含有环;

2)G有V-1条边且是连通的:

3)G是连通的,但删除任意一条边都会使他不在连通;

4)G是无环图,但添加任意一条边都会产生一条环;

5)G中的任意一对顶点之间仅存在一条简单路径。


图的密度:已经链接的顶点对占所有可能被连接的顶点对的比例,稀疏图中,被链接的顶点对很少;而稠密图中很多。如果一幅图中不同的边的数量在顶点总数v的一个小的常数倍以内为稀疏图,否则为稠密图。

二分图:一种能够将所有结点分为两部分的图,每条边所连接的两个顶点都分别属于不同的部分。


图的表示方式:

要求:

1)它必须为可能在应用中碰到的各种类型的图预留出足够的空间。

2)图的实例方法一定要快——他们是开发处理图的各种用例的基础。

1.邻接矩阵:V*V的布尔矩阵,满足不了空间。

2.边的数组:满足不了第二个条件。

3.邻接表数组:一个以顶点为索引的列表数组,其中的每个元素都是和该顶点相邻的列表数组。(类似于拉链式的散列表)


本章解决的无向图问题:

问题解决方法无向图的创建Graph单点连通性DepthFirstSearch单点路径DepthFirstPaths单点最短路径BreadthFirstPaths连通性CC检测环Cycle双色问题(二分)TwoColor

实现:

1.无向图的创建

具体方法是构建一个邻接表存储所有结点,每个结点指向一条链表存储结点相连的所有

代码:

#ifndef GRAPH_H#define GRAPH_H#include<iostream>#include<vector>#include"Bag.h"using std::cout;using std::endl;using std::cin;using std::vector;class Graph{private:int V = 0;//顶点数int E = 0;//边数vector<Bag<int>> adj;//邻接表public:Graph(int V):V(V),E(0),adj(V){}//构造结点Graph(int V, int E) :V(V), 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)//连接v,w{adj[v].add(w);//将连接的结点倒入自己的链表adj[w].add(v);E++;}Bag<int> getadj(int v){ return adj[v]; }};#endif



测试用例:

#include<string>#include<iostream>#include<vector>#include"Graph.h"#include"Bag.h"using namespace std;int main(){cout << "输入顶点数和边数:" << endl;int n,m;cin >> n>>m;Graph G(n,m);for (int v = 0; v < G.getV(); v++){cout << v << ": ";G.getadj(v).trave();cout << endl;}system("pause");return 0;}


2.单点连通性(深度优先搜索)

搜索和一个结点连通的所以结点,具体步骤:

1)设置一个bool数组,记录结点是否已经经过,也就是是否连通;

2)遍历结点的链表,被查看的链表元素没有经过则记录元素,然后递归遍历被查看的元素的链表;

3)有记录则查看链表下一个元素。

深度优先搜索的意思是在图中优先搜索一条路径,走到头之后(也就是该结点连接的所以链表元素都被记录),返回上一路口,走另一条路。

代码:

#ifndef DEPTHFIRSTSEARCH_H#define DEPTHFIRSTSEARCH_H#include<vector>#include"Graph.h"using std::vector;class DepthFirstSearch//深度优先搜索{private:vector<bool> marked;//记录顶点是否和起点S连通int count=0;//与起点S连通的顶点总数public:DepthFirstSearch(Graph G, int s) :marked(G.getV()){dfs(G, s);}void dfs(Graph G, int v){marked[v] = true;count++;for (const auto & w: G.getadj(v))if (!marked[w])dfs(G, w);}bool getmarked(int w){ return marked[w]; }int getcount(){ return count; }};#endif


测试用例:

#include<string>#include<iostream>#include<vector>#include"Graph.h"#include"Bag.h"#include"DepthFirstSearch.h"using namespace std;int main(){cout << "输入顶点数和边数:" << endl;int n,m;cin >> n>>m;Graph G(n,m);DepthFirstSearch search(G, 0);for (int v = 0; v < G.getV(); v++)if (search.getmarked(v))cout << v << " ";cout << endl;system("pause");return 0;}


3.寻找路径(基于深度优先搜索)

在深度优先搜索的基础上加一个数组,用来存储每个结点来时的路径(前置结点),然后用栈逆向输出即可。

代码:

#ifndef DEPTHFIRSTPATHS_H#define DEPTHFIRSTPATHS_H#include<vector>#include"Graph.h"#include"stack.h"using std::vector;class  DepthFirstPaths{private:vector<bool> marked;//记录结点是否经过vector<int> edgeTo;//记录结点的上一结点int s=0;//起点public:DepthFirstPaths(Graph G, int s) :marked(G.getV()), edgeTo(G.getV()), s(s){dfs(G, s);}void dfs(Graph G, int v){marked[v] = true;//已经过vfor (int w : G.getadj(v))//遍历v的链表if (!marked[w])//W没有经过{edgeTo[w] = v;//记录此结点的上一路径dfs(G, w);//查询w的链表元素}}bool hasPathTo(int v){ return marked[v]; }//是否存在s到v的路径stack<int> pathTo(int v)//输出s到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<string>#include<iostream>#include<vector>#include"Graph.h"#include"Bag.h"#include"DepthFirstPaths.h"using namespace std;int main()//DepthFirstPaths.h{cout << "输入顶点数和边数:" << endl;int n, m,s;cin >> n >> m;Graph G(n, m);cout << "输入查询顶点:" << endl;cin >> s;DepthFirstPaths 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.单点最短路径(基于广度优先搜索)

广度优先搜索与深度优先搜索的区别在于,我们先将与结点相连的结点搜索完,之后在查询下一层级,也就是说,我们先搜索结点的链表,结束之后,搜索结点链表首元素的链表,次元素的链表,依次下去直到全部元素所有元素都查完。所以这里我们就需要用一个队列记录搜索过的结点,以便返回使用,具体步骤:

1)设置队列记录首元素;

2)首元素出队,检查元素链表,经过元素全部入队,并记录;

3)对不为空,下一元素出队,继续进行检查,记录;

4)为空,结束。

代码:

#ifndef BREADTHFIRSTPATHS_H#define BREADTHFIRSTPATHS_H#include<vector>#include"Graph.h"#include"queue.h"#include"stack.h"using std::vector;class BreadthFirstPaths{private:vector<bool> marked;//记录是否已经过vector<int> edgeTo;//记录上一路径int s=0;//起点public:BreadthFirstPaths(Graph G, int s) :marked(G.getV()), edgeTo(G.getV()), s(s){bfs(G, s);}void bfs(Graph G, int s){queue<int> que;marked[s] = true;//标记起点que.enqueue(s);while (!que.Empty()){int v = que.dequeue();//取出下一个结点for (int w : G.getadj(v))//遍历结点链表if (!marked[w])//链表元素未被标记{edgeTo[w] = v;//记录他上一路径marked[w] = true;//标记元素que.enqueue(w);//入队}}}bool hasPathTo(int v){ return marked[v]; }//是否存在s->v的路径stack<int> pathTo(int v)//输出s->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<string>#include<iostream>#include<vector>#include"Graph.h"#include"Bag.h"#include"BreadthFirstPaths.h"using namespace std;int main()//BreadthFirstPaths{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Graph G(n, m);cout << "输入查询顶点:" << endl;cin >> s;BreadthFirstPaths 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.连通分量(基于深度优先搜索)

深度优先搜索的下一个直接应用就是找出一幅图的所有连通分量。

连通分量:即图中,一个最大连通子图就是一个连通分量。

查找图的所有连通分量,只需要深度优先搜索所有的结点,用一个数组记录是相同连通分量的结点打上同样的标记即可。

代码:

#ifndef CC_H#define CC_H#include<vector>#include"Graph.h"#include"Bag.h"using std::vector;class CC{private:vector<bool> marked;//结点是否被记录vector<int> id;//记录结点在那一连通分量int count;//连通分量的数目public:CC(Graph G) :marked(G.getV()), id(G.getV()), count(0){for (int s=0 ; s < G.getV();s++)if (!marked[s]){dfs(G, s);count++;}}void dfs(Graph G, int v){marked[v] = true;//标记vid[v] = count;//记录v输入那一连通分量for (int w : G.getadj(v))if (!marked[w])dfs(G, w);}bool connected(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(Graph 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<string>#include<iostream>#include<vector>#include"Graph.h"#include"Bag.h"#include"CC.h"using namespace std;int main()//cc{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Graph G(n, m);CC cc(G);m=cc.getcount();vector<Bag<int>> components = cc.componentsTo(G);for (int i = 0; i < m; i++){cout << i << " : ";for (auto v : components[i])cout << v << " ";cout << endl;}system("pause");return 0;}


5.无环图(基于深度优先搜索)

检测图是不是无环图。

在深度优先探索中当探索子元素的链表时,发现一个已经标记的结点且不是结点本身时,说明图是有环图。

代码:

#ifndef CYCLE_H#define CYCLE_H#include<vector>using std::vector;#include"Graph.h"class Cycle{private:vector<bool> marked;bool hasCycle;//无环图public:Cycle(Graph G) :marked(G.getV()){for (int s = 0; s < G.getV(); s++)if (!marked[s])dfs(G, s, s);}void dfs(Graph G, int v, int u)//正在探测的元素u的链表{marked[v] = true;for (int w : G.getadj(v))if (!marked[w])dfs(G, w, v);else if (w != u)hasCycle = true;//探索到一个已标记的结点,且不是自己}bool hasCycleTo(){ return hasCycle; }};#endif


测试用例:

#include<string>#include<iostream>#include<vector>#include"Graph.h"#include"Bag.h"#include"Cycle.h"using namespace std;int main(){cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Graph G(n, m);Cycle cy(G);if (cy.hasCycleTo())cout << "有环图" << endl;else cout << "无环图" << endl;system("pause");}

6.二分图(双色问题)

将一幅图用两种颜色一分为二,也就是每一个条边的两个端点的颜色都不同。

在深度优先探索中,用两种颜色依次标记连通的结点,如果一个结点链表中有一个被标记元素且颜色与自己相同,则不是二分图。

代码:

#ifndef TWOCOLOR_H#define TWOCOLOR_H#include<vector>using std::vector;#include"Graph.h"class TwoColor{private:vector<bool> marked;vector<bool> color;//结点的颜色bool isTwoColor = true;//双色图(默认是)public:TwoColor(Graph G) :marked(G.getV()), color(G.getV()){for (int s = 0; s < G.getV(); s++)if (!marked[s])dfs(G, s);}void dfs(Graph G, int v){marked[v] = true;for (int w : G.getadj(v))if (!marked[w]){color[w] = !color[v];dfs(G, w);}else if (color[w] == color[v])isTwoColor = false;//子链接中一个元素被标记,且颜色与自己相同}bool isBipartite(){return isTwoColor;}};#endif


测试用例:

#include<string>#include<iostream>#include<vector>#include"Graph.h"#include"Bag.h"#include"TwoColor.h"using namespace std;int main()//TwoColor{cout << "输入顶点数和边数:" << endl;int n, m, s;cin >> n >> m;Graph G(n, m);TwoColor cy(G);if (cy.isBipartite())cout << "双色图" << endl;else cout << "不是双色图" << endl;system("pause");}


符号图

在典型应用中,图都是通过文件或者网页定义的,使用的是字符串而非整数来指代顶点。为了适应应用,我们定义了拥有以下性质的输入格式:

1)顶点名为字符串

2)用指定的分隔符来隔开顶点名

3)每一行都表示一组边的集合,每一条边都连接着这一晃的第一个名称表示的顶点和另一个顶点

4)结点总数V和边总数E,除了输入时都是隐式定义的

很显然符号图需要用到符号表,下面是带模板的符号表:

#ifndef ST_H#define ST_H#include<string>#include<vector>#include"queue.h"using std::string;using std::vector;template<typename key, typename Item>class ST{class Node{friend class ST;key key;Item data;Node* next;public:template<typename key, typename Item>Node(key key, Item item, Node* next) :key(key), data(item), next(next){}};private:Node* first = NULL;public:void put(key key, Item item);//插入Item get(key key);//顺序查找void delete_1(key key);//删除bool Empty(){ return first ? 0 : 1; }int size();//长度queue<key> keys();//键的集合bool contains(key key){ return get(key) != NULL; }//键是否存在};template<typename key, typename Item>void ST<key, Item>::put(key key, Item item)//头插法{for (Node* x = first; x; x = x->next)if (key == x->key){x->data = item;return;}first = new Node(key, item, first);}template<typename key, typename Item>Item ST<key, Item>::get(key key){for (Node* x = first; x; x = x->next)if (key == x->key)return x->data;return NULL;}template<typename key, typename Item>void ST<key, Item>::delete_1(key key){if (first->key == key){Node* x = first;first = x->next;delete x;return;}for (Node* x = first; x->next; x = x->next)if (key == x->next->key){Node* t = x->next;x->next = t->next;delete(t);return;}}template<typename key, typename Item>int ST<key, Item>::size(){int n = 0;for (Node* x = first; x; x = x->next)n++;return n;}template<typename key, typename Item>queue<key> ST<key,Item>::keys(){queue<key> a;for (Node* x = first; x; x = x->next)a.enqueue(x->key);return a;}#endif


符号图代码:

#ifndef SYMBOLGRAPH_H#define SYMBOLGRAPH_H#include<iostream>using std::cout;using std::endl;using std::cin;#include<string>using std::string;#include<vector>using std::vector;#include"Graph.h"#include"ST.h"class SymbolGraph{private:ST<string, int> st;//符号名->索引vector<string> keys;//索引->符号名Graph G;//图string sy = "-";//间隔符号public:SymbolGraph(int V, int E) :G(V){string sp;//读取字符串for(int i=0;i<V;i++)//第一遍,构建索引{cout << "输入第"<<i<<"个符号名:" << endl;cin >> sp;if (!st.contains(sp))//符号名唯一{st.put(sp, i);//添加符号名->索引keys.push_back(sp);//添加索引->符号名}else i--;}string sp1,sp2;for (int i = 0; i < E; i++)//第二遍,构造图{cout << "输入第" << i << "条边的两个顶点:" << endl;cin >> sp1 >> sp2;G.addEdge(st.get(sp1), st.get(sp2));}}bool contains(string s){ return st.contains(s); }//检索符号名是否存在int index(string s){ return st.get(s); }//检索符号名是第几个结点string name(int v){ return keys[v]; }//检索第v个索引的名称Graph getG(){ return G; }//输出图string Sy(){ return sy; }//输出分隔符};#endif


测试用例:

#include<string>#include<iostream>#include<vector>#include"Graph.h"#include"BreadthFirstPaths.h"#include"SymbolGraph.h"using namespace std;int main()//SymbolGraph{cout << "输入图的结点数和边数:" << endl;int V, E;cin >> V >> E;SymbolGraph sym(V, E);Graph G = sym.getG();cout << "输入要查找的结点的符号名:" << endl;string source;cin >> source;int s = sym.index(source);BreadthFirstPaths bfs(G,s);cout << "输入你要到达的结点名:" << endl;string sink;cin >> sink;if (sym.contains(sink)){int t = sym.index(sink);cout << source << "->"<<sink << "的最短路径为:" << endl;if (bfs.hasPathTo(t))for (int v : bfs.pathTo(t))if (v == s)cout << sym.name(v);else cout << sym.Sy() + sym.name(v);else cout << "Not connected" << endl;}else cout << "Not in datebase" ;cout << endl;system("pause");}
















原创粉丝点击