图(1)——无向图

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

无向图

术语表

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

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

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

多重图:含有平行边。

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

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

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

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

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

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

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

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

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

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

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

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

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

:一幅无环连通子图。

森林:换不相连的树。

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

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


当一幅含有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");}
















原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 我的身份证注销了淘宝支付宝怎么办 买了空调坏了店家不管怎么办 支付宝充话费没到账也不退款怎么办 淘宝上店家关铺无法赔偿怎么办 未发货申请退款卖家拒绝怎么办 淘宝订单未发货买家恶意退款怎么办 淘宝被限制购买还有没退款的怎么办 复制粘贴了打字在复制下页怎么办? 得了抑郁症没办法还贷款了怎么办 淘宝东西退了商家不退钱怎么办 京东买东西付款后显示未付款怎么办 不能吃小龙虾的人吃了怎么办 闲鱼退货卖家收到货不退款怎么办 没收到货申请退款卖家不同意怎么办 咸鱼买家不确认收货老退款怎么办 买家买了确认收货然后退款怎么办 买家收确认收货后还申请退款怎么办 淘宝收到货后买家故意说少货怎么办 对面商铺放很大声音乐怎么办 在店面住隔壁商铺一直放音乐怎么办 淘宝卖家把买家评论删了怎么办 淘宝店铺上当顾客说再看看怎么办 车辆摇号申请说手机号注册怎么办 如果在微商手上买到假东西怎么办? 登陆微信显示版本过低该怎么办 苹果手机登陆微信版本过低怎么办 微信版本过低无法登怎么办录 登入微信显示版本过低登不上怎么办 微信版本过低无法使用小程序怎么办 小米4s微信反应很慢怎么办 手机网页缓存的视频播放不了怎么办 ios微信占用内存太大了怎么办 爱奇艺离线视频显示暂无缓存怎么办 酷狗音乐不小心删了歌怎么办 苹果手机里系统占的空间太大怎么办 酷狗音乐歌单里有不同步的歌怎么办 一插耳机手机自带音乐就响怎么办 清理空间时不小心把图片删了怎么办 手机中清理误把照片删了怎么办 清理手机文件把照片给删了怎么办 清理手机不小心把照片删了怎么办