图(1)——无向图

无向图

术语表

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

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

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

多重图:含有平行边。

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

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

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

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

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

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

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

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

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

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

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

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

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

:一幅无环连通子图。

森林:换不相连的树。

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

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


当一幅含有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;//已经过v
		for (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;//标记v
		id[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");
}
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值