数据结构------图(一)

关于图的简单介绍

  • 图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。在图中的数据元素,我们称之为顶点(Vertex),顶点集合有穷非空。在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

  • 图按照边的有无方向分为无向图和有向图。无向图由顶点和边组成,有向图由顶点和弧构成。弧有弧尾和弧头之分,带箭头一端为弧头。

  • 图按照边或弧的多少分稀疏图稠密图。如果图中的任意两个顶点之间都存在边叫做完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。

  • 图中顶点之间有邻接点、依附的概念。无向图顶点的边数叫做度。有向图顶点分为入度和出度。

  • 图上的边或弧带有权则称为网。

  • 图中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环,当中不重复的叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向则称为强连通图。图中有子图,若子图极大连通则就是连通分量,有向的则称为强连通分量。

  • 无向图中连通且n个顶点n-1条边称为生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向树。一个有向图由若干棵有向树构成生成森林。

使用邻接矩阵来实现图

  • 本质上就是一个二维矩阵。给这个二维矩阵的元素保存bool类型的值。
    在这里插入图片描述- 对于一个无向图来说,如上图所示,节点i和节点j之间如果是向连接的,那么在其对应的邻接矩阵中的元素G[i][j] = true。如果节点i和节点j之间如果没有连接,那么在其对应的邻接矩阵中的元素G[i][j] = false
  • 我们来举一个例子。无向图和有向图。
    在这里插入图片描述在这里插入图片描述
  • 我们先不过多讨论利用临接矩阵所表示的图的性质。先来看一下代码实现
//利用邻接矩阵实现稠密图
class DenseGraph
{
private:
	int node, side; //node是图的结点  side是图上的边
	bool directed; //true代表有向图,false代表无向图
	vector<vector<bool>> g;  //二维数组代表邻接矩阵

public:
	DenseGraph(int node, bool directed)  
	{
		this->node = node;
		this->side = 0;
		this->directed = directed;
		for (int i = 0; i < node; i++)
		{
			g.push_back(vector<bool>(node, false));
		}
	}

	~DenseGraph()  //由于没有开辟新的空间,因此虚构函数可不写
	{
		
	}

	int NodeSize()
	{
		return this->node;
	}

	int SideSize()
	{
		return this->side;
	}

	bool HasSide(int v, int m)
	{
		assert(v >= 0 && v < node);
		assert(m >= 0 && m < node);
		return g[v][m];
	}

	void AddSide(int v,int m)
	{
		assert(v >= 0 && v < node);
		assert(m >= 0 && m < node);

		if (HasSide(v, m) == true)
			return;

		g[v][m] = true;
		if (this->directed == false) // 如果为无向图
			g[m][v] = true;
		this->side += 1; //边的数量加1
	}
};

利用临接表来实现图

在这里插入图片描述

  • 我们可以看清楚的看出,相对于邻接矩阵来说。他只需要维护一个数组就好了,对于上图的无向图。我们可以看出,对于节点0,与他连接的只有节点1.对于节点1,与他连接的有节点0、2、3.下面的都是类似的。
    在这里插入图片描述 - 对于上图的有向图来说,我们可以看出,节点0和节点1之间的关系使节点0指向节点1的,但是节点1不指向节点0的。因此在节点1指向的相邻元素只有节点2。
  • 那么我们用邻接表实现的图的代码如下
//使用临接表实现稀疏图
class SparseGraph {

private:
	int node, side; //node表示结点,side表示边
	bool directed;  //true为有向图,false为无向图
	vector<vector<int>> g;

public:
	SparseGraph(int node, bool directed)
	{
		this->node = node;
		this->side = 0;
		this->directed = directed;
		for (int i = 0; i < node; i++)
		{
			g.push_back(vector<int>());
		}
	}
	~SparseGraph()
	{

	}

	int NodeSize()
	{
		return this->node;
	}

	int SideSize()
	{
		return this->side;
	}

	bool HasSide(int v, int m)
	{
		assert(v >= 0 && v < node);
		assert(m >= 0 && m < node);

		for (int i = 0; i < g[v].size(); i++)
		{
			if (g[v][i] == m)
				return true;
		}
		return false;
	}

	void AddSide(int v, int m)
	{
		assert(v >= 0 && v < node);
		assert(m >= 0 && m < node);

		if (HasSide(v, m) == true)
			return;

		g[v].push_back(m);
		if (v != m && this->directed == false)
			g[m].push_back(v);
		this->side += 1;
	}
};

图的遍历

  • 对于图的遍历,我心在说两个方法。深度优先遍历和广度优先遍历。
  • 深度优先遍历:就是顺着一个节点一直往下找,一直找到头为止。找到头后再回溯。广度优先遍历:把这个节点相连的所有节点都拿到,然后在将所拿到的每一个节点的相邻节点都拿到,使用队列进行操作。
  • 同时呢,为了方便起见,我封装了一个类,用来遍历二维数组或者一维数组中一行的所有元素。
//深度优先
template<typename Graph>
class Path {
private:
	Graph &graph;
	int s;  //某一个点
	bool* visited;
	int* from;  //他是从那个点来的

	void dfs(int v)
	{
		visited[v] = true;

		typename Graph::adjIterator adj(graph, v);
		for(int i = adj.begin();!adj.end();i = adj.next())
			if (!visited[i])
			{
				from[i] = v;
				dfs(i);
			}
	}

public:
	Path(Graph &graph, int s) :graph(graph) {

		assert(s >= 0 && s < graph.NodeSize());

		visited = new bool[graph.NodeSize()];
		from = new int[graph.NodeSize()];
		for (int i = 0; i < graph.NodeSize(); ++i)
		{
			visited[i] = false;
			from[i] = -1;
		}
		this->s = s;

		//寻找路由
		dfs(s);
	}

	~Path()
	{
		delete[] visited;
		delete[] from;
	}

	bool hasPath(int w)
	{
		assert(w >= 0 && w < graph.NodeSize());
		return visited[w];
	}

	void path(int w, vector<int> &vec)
	{
		stack<int> s;

		int p = w;
		while (p != -1)
		{
			s.push(p);
			p = from[p];
		}
		vec.clear();
		while (!s.empty())
		{
			vec.push_back(s.top());
			s.pop();
		}
	}

	void showPath(int w)
	{
		vector<int> vec;
		path(w, vec);
		for (int i = 0; i < vec.size(); ++i)
		{
			cout << vec[i];
			if (i == vec.size() - 1)
				cout << endl;
			else
				cout << "→";
		}
	}
};
//广度优先
template<typename Graph>
class ShortPath {
private:
	Graph &graph;
	int s;
	bool* visited;
	int* from;
	int* ord;  
public:
	ShortPath(Graph &graph, int s) :graph(graph) {
		assert(s >= 0 && s < graph.NodeSize());

		visited = new bool[graph.NodeSize()];
		from = new int[graph.NodeSize()];
		ord = new int[graph.NodeSize()];
		for (int i = 0; i < graph.NodeSize(); ++i)
		{
			visited[i] = false;
			from[i] = -1;
			ord[i] = -1;
		}
		this->s = s;

		queue<int> q;
		q.push(s);
		visited[s] = true;
		ord[s] = 0;
		while (!q.empty())
		{
			int w = q.front();
			q.pop();

			typename Graph::adjIterator adj(graph, w);
			for (int i = adj.begin(); !adj.end(); i = adj.next())
			{
				if (!visited[i])
				{
					q.push(i);
					visited[i] = true;
					from[i] = w;
					ord[i] = ord[w] + 1;
				}
			}
		}
	}


	~ShortPath()
	{
		delete[] visited;
		delete[] from;
		delete[] ord;
	}

	bool hasPath(int w)
	{
		assert(w >= 0 && w < graph.NodeSize());
		return visited[w];
	}

	void path(int w, vector<int> &vec)
	{
		stack<int> s;

		int p = w;
		while (p != -1)
		{
			s.push(p);
			p = from[p];
		}
		vec.clear();
		while (!s.empty())
		{
			vec.push_back(s.top());
			s.pop();
		}
	}

	void showPath(int w)
	{
		vector<int> vec;
		path(w, vec);
		for (int i = 0; i < vec.size(); ++i)
		{
			cout << vec[i];
			if (i == vec.size() - 1)
				cout << endl;
			else
				cout << "→";
		}
	}

	int length(int w)
	{
		assert(w >= 0 && w < graph.NodeSize());
		return ord[w];
	}
};

图的连通分量

  • 连通分量就是用来表示一个图中有有多少个分组。
    在这里插入图片描述- 比如上图中,我们说这个图的连通分量就是3。就是有三个分组,他们没有连在一起。
template<typename Graph>
class Comment {

private:
	Graph &graph;
	bool* visited;
	int* id; // 检测两个结点之间是否连接
	int ccount;

	void dfs(int i)
	{
		visited[i] = true;
		id[i] = ccount;
		typename Graph::adjIterator adj(graph, i);
		for (int w = adj.begin(); !adj.end(); w = adj.next())
		{
			if (visited[w] == false)
				dfs(w);
		}
	}

public:
	Comment(Graph graph) :graph(graph) {
		visited = new bool[graph.NodeSize()];
		id = new int[graph.NodeSize()];
		ccount = 0;
		for (int i = 0; i < graph.NodeSize(); ++i)
		{
			visited[i] = false;
			id[i] = -1;
		}
		for (int i = 0; i < graph.NodeSize(); i++)
		{
			if (visited[i] == false)
			{
				dfs(i);
				ccount++;
			}
		}
	}

	~Comment()
	{
		delete[] visited;
	}

	int Count()
	{
		return ccount;
	}

	bool isConnection(int v, int m)
	{
		assert(v >= 0 && v < graph.NodeSize());
		assert(m >= 0 && m < graph.NodeSize());
		return id[v] == id[m];
	}
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值