数据结构 : 图 (待续)

数据结构: 图

分类

  • 有向图
  • 无向图
  • 无权图
  • 有权图

图的连通性

图的表示

邻接矩阵

  • 使用一个 二维矩阵NxN 来表示图
  • 邻接矩阵适合 表示 稠密图
#pragma once
#ifndef GRAPH_DENSEGRAPH_H
#define GRAPH_DENSEGRAPH_H
#include "MyInclude.h"

class DenseGraph {
private:
	int n, m; // 顶点和边数
	bool directed; // 有向图还是无向图
	vector<vector<bool>> g;

public:
	DenseGraph(int n,int directed) {

		this->n = n;
		this->m = 0;
		this->directed = directed;

		// NxN
		for (int i = 0; i < n; i++) {
			g.push_back(vector<bool>(n, false));
		}
	}

	~DenseGraph() {

	}

	// 顶点个数
	int V() { return n; }
	// 边个数
	int E() { return m; }


	void addEdge(int v, int w) {
		assert(v >= 0 && v < n);
		assert(w >= 0 && w < n);

		// 如果已经有边了
		if (hasEdge(v, w))
			return;

		g[v][w] = true;
		if (!directed) { // 无向图
			g[w][v] = true;
		}
		m++;
	}

	// 判断是否有没有边
	bool hasEdge(int v, int w) {
		assert(v >= 0 && v < n);
		assert(w >= 0 && w < n);

		return g[v][w];

	}

	void show() {
		for (int i = 0; i < n; i++) {
			cout << "vertex " << i << ":\t";
			for (int j = 0; j < n; j++) {
				//if (g[i][j]) 
					cout << g[i][j] << "\t";
			}
			cout << endl;
		}
		cout << endl;
	}

	class adjIterator {
	private:
		DenseGraph &G;
		int v;
		int index;
	public:
		adjIterator(DenseGraph &graph, int v) :G(graph) {
			this->v = v;
			this->index = 0;
		}

		int begin() {
			// 返回第一个相邻的节点
			index = -1;
			return next();
		}

		int next() {
			// 返回下一个相邻的节点
			for (index += 1; index < G.V(); index++) {
				if (G.g[v][index] == true) {
					return index;
				}
			}
			return -1;
		}

		bool end() {
			// 返回是否已经达到末尾
			return index >= G.V();
		}
	};

};
#endif // !GRAPH_DENSEGRAPH_H

邻接表

  • 用的是 链表 方式
  • 连接表适合表示 稀疏图
#pragma once
#ifndef GRAPH_SPARSEGRAPH_H
#define GRAPH_SPARSEGRAPH_H

#include "MyInclude.h"

class SparseGraph {
private:
	int n, m; // 顶点和边数
	bool directed; // 有向图还是无向图

	// 使用链表实现,删除的话更有效
	vector<vector<int>> g; // 存储是int类型,表示顶点编号[0,n-1]


public:

	SparseGraph(int n, int directed) {
		this->n = n;
		this->m = 0;
		this->directed = directed;
		
		for (int i = 0; i < n; i++) {
			g.push_back(vector<int>());
		}
	} 

	~SparseGraph() {
	}

	// 顶点个数
	int V() { return n; }
	// 边个数
	int E() { return m; }


	void addEdge(int v, int w) {
		
		assert(v >= 0 && v < n);
		assert(w >= 0 && w < n);

		if (hasEdge(v, w))
			return;

		g[v].push_back(w);
		if (v!=w && !directed) { // v!=w 处理自环边
			g[w].push_back(v);
		}
		m++;
	}

	// 判断是否有没有边
	bool hasEdge(int v, int w) {
		assert(v >= 0 && v < n); 
		assert(w >= 0 && w < n);

		// 时间复杂度 O(n)
		for (int i = 0; i < g[v].size(); i++) {
			if (g[v][i] == w)
				return true;
		}
		return false;
	}

	void show() {
		for (int i = 0; i < n; i++) {
			cout << "vertex " << i << ":\t";
			for (int j = 0; j < g[i].size(); j++) {
				cout << g[i][j] << "\t";
			}
			cout << endl;
		}
		cout << endl;
	}

	class adjIterator {
	private:
		SparseGraph &G;
		int v;
		int index;
	public:
		adjIterator(SparseGraph &graph, int v):G(graph) {
			this->v = v;
			this->index = 0;
		}
		int begin() {
			// 返回第一个相邻的节点
			index = 0;
			if (G.g[v].size() != 0) {
				return G.g[v][index];
			}
			return -1;
		}

		int next() {
			// 返回下一个相邻的节点
			index++;
			if (index < G.g[v].size()) {
				return G.g[v][index];
			}
			return -1;
		}

		bool end() {
			// 返回是否已经达到末尾
			return index >= G.g[v].size();
		}
	};
};
#endif // !GRAPH_SPARSEGRAPH_H

图的读取与创建

  • 读取
template< typename Graph>
class ReadGraph {
public:
	// 注意:filename文件格式如下:
	// 第一行表示图的 顶点数和边数
	/* 
        6 8
        0 1
        0 2
        0 5
        1 2
        1 3
        1 4
        3 4
        3 5
	*/
	ReadGraph(Graph &graph, const string filename) {

		ifstream file(filename);
		string line;
		int V, E;
		assert(file.is_open());

		assert(getline(file, line));
		stringstream ss(line);
		ss >> V >> E;
		assert(V == graph.V());

		for (int i = 0; i < E; i++) {
			getline(file, line);
			stringstream ss(line);
			int v, w;
			ss >> v >> w;
			assert(v >= 0 && v < V);
			assert(w >= 0 && w < V);
			graph.addEdge(v, w);
		}
	}
};
  • 创建,示例如下:
#include "SparseGraph.h"
#include "DenseGraph.h"
#include "ReadGraph.h"
int main() {
	string filename = "testG1.txt";
	SparseGraph g1(13, false);
	ReadGraph<SparseGraph> readGraph1(g1, filename);

	g1.show();

	DenseGraph g2(13, false);
	ReadGraph<DenseGraph> readGraph2(g2, filename);
	g2.show();

	system("pause");
	return 0;
}

图的遍历

迭代器

  • begin
  • next
  • end

图的深度优先遍历

  • DFS类似于树的先根遍历,尽可能深的去访问节点。
  • 步骤1:访问节点,将这个节点标志为 已访问.
  • 步骤2:如果节点还有未标记的邻接点,继续步骤1,否则返回。\
  • 深度优先遍历求连通分量 代码如下:
#ifndef GRAPH_COMPONENT_H
#define  GRAPH_COMPONENT_H

#include "MyInclude.h"

// 求图的连通分量!
#include "SparseGraph.h"
#include "DenseGraph.h"

template< typename Graph>
class Component {

private:
	Graph &G;
	bool *visited;
	int ccount; // 计算有多少连通分量
	int *id; // id表示每个顶点属于哪个组的,类似于并查集,每一个连通分量是一个组的

	void dfs(int v) {
		// 访问v
		visited[v] = true;
		id[v] = ccount; // 属于哪个连通分量的

		// 对于v的每一个相邻点,进行深度优先遍历
		typename Graph::adjIterator adj(G, v);
		for (int i = adj.begin(); !adj.end(); i = adj.next()) {
			if (!visited[i]) {
				id[i] = v;
				dfs(i);
			}
		}
	}

public:
	Component(Graph &graph) :G(graph){

		visited = new bool[G.V()];
		id = new bool[G.V()];
		ccount = 0;
		for (int i = 0; i < G.V(); i++) {
			visited[i] = false; // 初始化为false
			id[i] = -1; // 初始化为-1
		}

		for (int i = 0; i < G.V(); i++) {
			// 若这个节点没有访问过,就访问
			if (!visited[i]) {	
				dfs(i);// 深度优先遍历
				ccount++;
			}
		}
	}

	int count() {
		return ccount;
	}
	
	// 判断两个节点是否相连
	bool isConnected(int v, int w) {
		assert(v >= 0 && v < G.V());
		assert(w >= 0 && w < G.V());
		return id[v] == id[w];
	}

	~Component() {
		delete[]visited;
	}
};

#endif // !GRAPH_COMPONENT_H
  • 深度优先遍历 打印路径:
#ifndef GRAPH_PATH_H
#define  GRAPH_PATH_H

#include "MyInclude.h"

// 求图的路径
#include "SparseGraph.h"
#include "DenseGraph.h"

template< typename Graph>
class Path {
private:
	Graph &G;
	int s; // source
	bool *visited; // 是否访问过
	int *from; // 记录当前访问的节点是哪个节点那过来的

	void dfs(int v) {

		visited[v] = true;

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

public:
	Path(Graph &graph,int s) :G(graph) {
		// s表示source,源点
		assert(s >= 0 && s < G.V());
		// 算法初始化
		visited = new bool[G.V()];
		from = new int[G.V()];
		for (int i = 0; i < G.V(); i++) {
			visited[i] = false;
			from[i] = -1;
		}
		this->s = s;

		// 寻路算法
		dfs(s);

	}

	// 从源点s,到w是否有路径
	bool hasPath(int w) {
		assert(w >= 0 && w < G.V());
		return visited[w];
	}

	// 从源点s到w的具体路径是怎么样的
	void path(int w, vector<int> &vec) {
		if (hasPath(w)) {
			stack<int> m_stack;
			int p = w;
			while (p != -1) {
				m_stack.push(p);
				p = from[p];
			}
			vec.clear();
			while (!m_stack.empty()) {
				vec.push_back(m_stack.top());
				m_stack.pop();
			}
		}
	}

	// 打印路径,从源点s到w
	void showPath(int w) {
		vector<int> vec;
		path(w, vec);
		for (int i = 0; i < vec.size(); i++) {
			if(i!=vec.size()-1){
				cout << vec[i] << " --> ";
			}
			else {
				cout << vec[i] << endl;
			}
		}
	}

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

#endif // !GRAPH_PATH_H

图的广度优先遍历

  • BFS我们用队列来实现。
  • 步骤1:如果队列为空,则返回,否则,取出队列头节点,将该节点标记为已访问。
  • 步骤2:同时,将该节点所有未标记的邻接点,加入队列尾,继续步骤1。

利用队列遍历实现图广度优先遍历

  • 记录图的路径和长度
#ifndef GRAPH_SHORTESTPATH_H
#define  GRAPH_SHORTESTPATH_H

#include "MyInclude.h"

// 求最短路径,用的广度优先遍历
#include "SparseGraph.h"
#include "DenseGraph.h"

template< typename Graph>
class ShortestPath {
private:
	Graph &G;
	int s;
	bool *visited; // 是否访问过
	int *from; // 记录当前访问的节点是哪个节点那过来的
	int *ord; // ord表示,s到每一个节点的最短距离

	void bfs() {
		queue<int> q;
		q.push(s);
		ord[s] = 0;
		visited[s] = true;

		while (!q.empty()) {
			int v = q.front();
			q.pop();
			typename Graph::adjIterator adj(G, v);
			for (int j = adj.begin(); !adj.end(); j = adj.next()) {
				if (!visited[j]) {
					q.push(j);
					visited[j] = true;
					from[j] = v;
					ord[j] = ord[v] + 1; // 更新长度
				}
			}
		}
	}

public:
	ShortestPath(Graph &graph, int s) :G(graph) {
		// s表示source,源点
		assert(s >= 0 && s < G.V());
		// 算法初始化
		visited = new bool[G.V()];
		from = new int[G.V()];
		ord = new int[G.V()];
		for (int i = 0; i < G.V(); i++) {
			visited[i] = false;
			from[i] = -1;
			ord[i] = -1;
		}
		this->s = s;

		// 寻路算法
		bfs();
	}
	bool hasPath(int w) {
		assert(w >= 0 && w < G.V());
		return visited[w];
	}

	// 返回最短路径是什么
	int length(int w) {
		assert(w >= 0 && w < G.V());
		return ord[w];
	}
	// 从源点s到w的具体路径是怎么样的
	void path(int w, vector<int> &vec) {
		if (hasPath(w)) {
			stack<int> m_stack;
			int p = w;
			while (p != -1) {
				m_stack.push(p);
				p = from[p];
			}
			vec.clear();
			while (!m_stack.empty()) {
				vec.push_back(m_stack.top());
				m_stack.pop();
			}

		}
	}

	// 打印路径,从源点s到w
	void showPath(int w) {
		vector<int> vec;
		path(w, vec);
		for (int i = 0; i < vec.size(); i++) {
			if (i != vec.size() - 1) {
				cout << vec[i] << " --> ";
			} else {
				cout << vec[i] << endl;
			}
		}
	}

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

#endif // !GRAPH_SHORTESTPATH_H

参考文献

图的遍历

猜你喜欢

转载自blog.csdn.net/qjh5606/article/details/85019129