C++实现图的存储和遍历

前言

        许多新手友友在初学算法和数据结构时,会被图论支配过。我这里整理了一下图论常见的存储和遍历方式,仅供参考。如有问题,欢迎大佬们批评指正。

        存储我将提到四种方式:邻接矩阵、vector实现邻接表、数组模拟单链表实现的前向星实现邻接表、结构体数组直接存储边。遍历我将提到三种方式:dfs、bfs、按照边的权重值大小遍历输出。

一、邻接矩阵

#include <iostream>
#include <cstring>
#include <queue>
#include <functional>
constexpr int MAX_N = 1e3+5;
int g[MAX_N][MAX_N];
bool vis[MAX_N];
int main(){
	//取消同步流,加快输入输出 
	std::cin.tie(nullptr)->sync_with_stdio(false);
	
	//图的存储方式 1:邻接矩阵
	int n;std::cin >> n;
	for(int i = 1;i<=n;++i){
		for(int j = 1;j<=n;++j){
			std::cin >> g[i][j];
		}
	}
	
	//邻接矩阵的 dfs遍历
	memset(vis,false,sizeof vis);
	vis[1] = true;
	using Dfs = std::function<void(int)>;//目的是为了让 dfs函数可以进行递归 
	Dfs dfs = [&](int u)->void{//lambda表达式(函数) 
		std::cout << u << ' ';
		for(int i = 1;i<=n;++i){
			if(!vis[i]&&g[u][i]){
				vis[i]=true;
				dfs(i);
			}
		}
	};
	dfs(1);
	std::cout << '\n';
	
	//邻接矩阵的 bfs遍历
	auto bfs = [&]()->void{
		memset(vis,false,sizeof vis);
		std::queue<int> q;
		q.emplace(1);//emplace取代 push,可以加快运行速度 
		vis[1] = true;
		while(!q.empty()){
			auto t = q.front();
			q.pop();
			std::cout << t << ' ';
			for(int i = 1;i<=n;++i){
				if(!vis[i]&&g[t][i]){
					vis[i] = true;
					q.emplace(i);
				}
			}
		}
	};
	bfs();
	std::cout << '\n';
	
	return 0;
}

        测试

二、 vector实现邻接表

#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
#include <functional>
constexpr int MAX_N = 1e3+5,MAX_M = 1e6+5;
std::vector<std::pair<int,int>> edges[MAX_N];
bool vis[MAX_N];
int main(){
	std::cin.tie(nullptr)->sync_with_stdio(false);
	
	//图的存储方式 2:vector实现邻接表 
	int n,m;std::cin >> n >> m;
	while(m--){
		int x,y,w;std::cin >> x >> y >> w;
		//假设没有重边,无向边需要加两条边
		edges[x].emplace_back(y,w);
		edges[y].emplace_back(x,w);
	}
	
	//vector实现邻接表的 dfs遍历
	memset(vis,false,sizeof vis);
	vis[1] = true;
	using Dfs = std::function<void(int)>;
	Dfs dfs = [&](int u)->void{
		std::cout << u << ' ';
		for(int i = 0;i<edges[u].size();++i){
			if(!vis[edges[u][i].first]){
				vis[edges[u][i].first]=true;
				dfs(edges[u][i].first);
			}
		}
	};
	dfs(1);
	std::cout << '\n';
	
	//vector实现邻接表的 bfs遍历
	auto bfs = [&]()->void{
		memset(vis,false,sizeof vis);
		std::queue<int> q;
		q.emplace(1);
		vis[1] = true;
		while(!q.empty()){
			auto t = q.front();
			q.pop();
			std::cout << t << ' ';
			for(int i = 0;i<edges[t].size();++i){
				if(!vis[edges[t][i].first]){
					vis[edges[t][i].first] = true;
					q.emplace(edges[t][i].first);
				}
			}
		}
	};
	bfs();
	std::cout << '\n';
	
	return 0;
}

        测试

三、 数组模拟单链表实现的前向星实现邻接表

#include <iostream>
#include <cstring>
#include <queue>
#include <functional>
constexpr int MAX_N = 1e3+5,MAX_M = 1e6+5;
//h数组为头节点,值表示下一个节点的下标,值为 -1则表示指向 NULL(nullptr)
//有 idx到 e[idx]的边,权值为 w[idx],ne[idx]是 idx的下一个节点的下标 
int h[MAX_N],e[MAX_M],w[MAX_M],ne[MAX_M],idx;
bool vis[MAX_N];
int main(){
	std::cin.tie(nullptr)->sync_with_stdio(false);
	
	//图的存储方式 3:前向星实现邻接表
	//采用数组模拟单链表来实现前向星
	auto init = [&]()->void{
		memset(h,-1,sizeof h);//头结点初始值为 -1,表示 next指向 NULL(nullptr) 
		idx = 0;//下标从 0开始 
	};
	//注意这里是头插法,遍历的时候顺序是反的
	//但是大多数时候求最短路或者最小生成树不关注这个顺序 
	auto add = [&](int x,int y,int z)->void{
		//先勾右链,再勾左链
		e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;
	};
	init();
	int n,m;std::cin >> n >> m;
	while(m--){
		int x,y,z;std::cin >> x >> y >> z;
		add(x,y,z),add(y,x,z);
	}
	
	//前向星实现邻接表的 dfs遍历
	memset(vis,false,sizeof vis);
	vis[1] = true;
	using Dfs = std::function<void(int)>;
	Dfs dfs = [&](int u)->void{
		std::cout << u << ' ';
		for(int i = h[u];~i;i=ne[i]){
			int j = e[i];
			if(!vis[j]){
				vis[j] = true;
				dfs(j);
			}
		} 
	};
	dfs(1);
	std::cout << '\n';
	
	//前向星实现邻接表的 bfs遍历
	auto bfs = [&]()->void{
		memset(vis,false,sizeof vis);
		std::queue<int> q;
		q.emplace(1);
		vis[1] = true;
		while(!q.empty()){
			auto t = q.front();
			q.pop();
			std::cout << t << ' ';
			for(int i = h[t];~i;i=ne[i]){
				int j = e[i];
				if(!vis[j]){
					vis[j] = true;
					q.emplace(j);
				}
			}
		}
	};
	bfs();
	std::cout << '\n';
	
	return 0;
}

        测试

四、 结构体数组直接存储边

#include <iostream>
#include <vector> 
#include <cstring>
#include <queue>
#include <algorithm>
#include <functional>
constexpr int MAX_N = 1e3+5,MAX_M = 1e6+5;
struct edge{
	int x,y,w;
	//重载 <符号,方便后续的sort()排序 
	bool operator < (const edge& W) {
		return w<W.w;
	}
	//写构造函数,方便后续直接 emplace_back
	edge(int _x,int _y,int _w):x(_x),y(_y),w(_w){}
};
std::vector<edge> edges;
std::vector<std::pair<int,int>> new_edges[MAX_N];
int h[MAX_N],e[MAX_M],w[MAX_M],ne[MAX_M],idx;
bool vis[MAX_N];
int main(){
	std::cin.tie(nullptr)->sync_with_stdio(false);
	
	//图的存储方式 4:结构体数组直接存储边
	int n,m;std::cin >> n >> m;
	while(m--){
		int x,y,w;std::cin >> x >> y >> w;
		edges.emplace_back(x,y,w);
	}
	
	//如果要实现 dfs和 bfs,只需把这种存边方式转换成第2或第3种即可
	//若要转换成第二种: 
	for(auto &t:edges){
		new_edges[t.x].emplace_back(t.y,t.w);
	}
	//若要转换成第三种:
	auto init = [&]()->void{
		memset(h,-1,sizeof h);
		idx = 0;
	};
	auto add = [&](int x,int y,int z)->void{
		e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;
	};
	init();
	for(auto &t:edges){
		add(t.x,t.y,t.w),add(t.y,t.x,t.w);
	}
	//再使用任意一种方式进行 dfs或者 bfs遍历
	//这里以第三种存图方式为例,dfs遍历 
	memset(vis,false,sizeof vis);
	vis[1] = true;
	using Dfs = std::function<void(int)>;
	Dfs dfs = [&](int u)->void{
		std::cout << u << ' ';
		for(int i = h[u];~i;i=ne[i]){
			int j = e[i];
			if(!vis[j]){
				vis[j] = true;
				dfs(j);
			}
		} 
	};
	dfs(1);
	std::cout << '\n';
	
	//bfs遍历
	auto bfs = [&]()->void{
		memset(vis,false,sizeof vis);
		std::queue<int> q;
		q.emplace(1);
		vis[1] = true;
		while(!q.empty()){
			auto t = q.front();
			q.pop();
			std::cout << t << ' ';
			for(int i = h[t];~i;i=ne[i]){
				int j = e[i];
				if(!vis[j]){
					vis[j] = true;
					q.emplace(j);
				}
			}
		}
	};
	bfs();
	std::cout << '\n';
	
	//按照边的权重值大小遍历输出
	std::sort(edges.begin(),edges.end());
	for(auto &t:edges){
		std::cout << t.x << ' ' << t.y << ' ' << t.w << '\n';
	}
	
	return 0;
}

        测试

  • 13
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
的深度遍历和广度遍历图论中比较基础的算法,下面是C++实现的代码: ```cpp #include <iostream> #include <vector> #include <queue> #include <stack> using namespace std; // 定义一个无向类 class Graph { private: int V; // 中顶点的数量 vector<int> *adj; // 邻接表 public: Graph(int V); // 构造函数 void addEdge(int v, int w); // 添加边 void DFS(int v); // 深度优先遍历 void BFS(int v); // 广度优先遍历 }; Graph::Graph(int V) { this->V = V; adj = new vector<int>[V]; } void Graph::addEdge(int v, int w) { adj[v].push_back(w); adj[w].push_back(v); } void Graph::DFS(int v) { // 创建一个 bool 类型的数组,表示每个顶点是否被访问 bool *visited = new bool[V]; for (int i = 0; i < V; i++) { visited[i] = false; } // 创建一个栈,用于存储已经经过的顶点 stack<int> s; // 将起始顶点加入栈中 s.push(v); while (!s.empty()) { // 取出栈顶元素 v = s.top(); s.pop(); // 如果该顶点还没有被访问过,就将其标记为已访问 if (!visited[v]) { cout << v << " "; visited[v] = true; } // 遍历该顶点的所有邻接顶点 for (auto it = adj[v].begin(); it != adj[v].end(); ++it) { if (!visited[*it]) { // 如果邻接顶点还没有被访问过,就将其加入栈中 s.push(*it); } } } delete[] visited; } void Graph::BFS(int v) { // 创建一个 bool 类型的数组,表示每个顶点是否被访问 bool *visited = new bool[V]; for (int i = 0; i < V; i++) { visited[i] = false; } // 创建一个队列,用于存储已经经过的顶点 queue<int> q; // 将起始顶点加入队列中 q.push(v); while (!q.empty()) { // 取出队首元素 v = q.front(); q.pop(); // 如果该顶点还没有被访问过,就将其标记为已访问 if (!visited[v]) { cout << v << " "; visited[v] = true; } // 遍历该顶点的所有邻接顶点 for (auto it = adj[v].begin(); it != adj[v].end(); ++it) { if (!visited[*it]) { // 如果邻接顶点还没有被访问过,就将其加入队列中 q.push(*it); } } } delete[] visited; } int main() { Graph g(5); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 3); g.addEdge(3, 4); cout << "DFS: "; g.DFS(0); cout << endl; cout << "BFS: "; g.BFS(0); cout << endl; return 0; } ``` 上面的代码中,我们定义了一个 `Graph` 类来表示一个无向,其中包含了两个成员函数 `DFS()` 和 `BFS()` 分别用于实现深度优先遍历和广度优先遍历。在 `DFS()` 和 `BFS()` 函数中,我们都创建了一个 bool 类型的数组 `visited`,用于表示每个顶点是否被访问过。然后分别使用栈和队列来存储已经经过的顶点,从而实现遍历整个的过程。 最后我们在 `main()` 函数中创建了一个 `Graph` 对象 `g`,并添加了一些边。然后分别调用 `DFS()` 和 `BFS()` 函数来遍历整个

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Beau_Will

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值