图模板、BFS、DFS的C++实现

图的存储方式有两种:邻接矩阵和邻接表。

邻接矩阵

用二维数组来表示顶点之间是否存在边,边的权重为多少。对于无向图来说,邻接矩阵是一个对称矩阵。缺点是邻接矩阵虽然比较好写但是需要开辟一个二维数组,如果顶点数量太多,容易超过题目的内存限制。

在这里插入图片描述

邻接表

把同一个顶点的所有出边都放在一个列表中,那么N个顶点就会有N个列表,这N个列表就是图G的邻接表,记为Adj[N]每个结点会存放一条边的信息(括号外的数字是边的终点编号,括号内的数字是边权。

在这里插入图片描述

对于初学者来说,使用变长数组vector来表示邻接表更为简便。

​ 如果邻接表只存放每条边的终点序号而不存放权重,则vector中的元素类型可以直接定义为int类型vector<int>Adj[N];如果想添加一条3号结点到1号结点的有向边,那么就需要Adj[3].push_back(1);

如果邻接表中还需要存放边权,则vector的元素类型使用结点Node结构体,代码如下:

struct Node{
    int w;//边权
    int v;//边的终点序号
};

此时要添加一条3号结点到1号结点的权重为2的有向边,代码如下:

Node temp;
temp.w=2;
temp.v=1;
Adj[3].push_back(temp);

最快速的写法是给结构体提供构造函数,在添加过程中直接加入临时结点,代码如下:

struct Node{
    int w,v;
    Node(int _w,int _v):w(_w),v(_v){}
};
Adj[3].push_back(Node(2,1));

图的遍历

遍历方法一般有两种:深度优先搜索DFS和广度优先搜索BFS。

DFS

思想:沿着一条路径直到无法继续前进,才退回路径上离当前顶点最近的还存在未被访问的结点的岔路口,并继续访问那些分支顶点,直到完成遍历整个图。

实现:将经过的顶点设置为已访问,在下次递归遇到这个顶点的时候就不再处理,直到整个图的顶点都被标记为已访问。伪代码如下:

DFS(u){
    vis[u]=true;//设置u已被访问
    	for(从u出发能到达的所有顶点v)
            if vis[v]==false //如果v未被访问
                DFS(v);//递归访问v
}
DFSTrave(G){ //遍历图G	
    for(G的所有顶点u){ //对图G的所有顶点
        if vis[u]=false //如果u未被访问
            DFS(u); //访问u所在的连通块
    }
}

邻接矩阵DFS

const int MAXV=1000;
const int INF=1000000000;
int n,G[MAXV][MAXV];//n为顶点数,MAXV为最大顶点数
bool vis[MAXV]={false};//将所有结点的访问状态初始化为未访问

void DFS(int n,int depth){//u为当前访问的结点符号,depth为深度
    vis[u]=true;
    //遍历从u出发可以到达的所有结点
    for(int v=0;v<n;v++){
        if(vis[v]==false&&G[u][v]!=INF){//如果v未被访问,且u可以到达v
            DFS(v,depth+1);
        }
    }
}
void DFSTrave(){
    for(int u=0;u<n;u++){//对每个顶点u
        if(vis[u]==false){//如果当前顶点u未被访问
            DFS(u,1);//访问每个顶点u所在的连通块,表示第一层
        }
    }
}

邻接表版本DFS

const int MAXV=1000;
const int INF=1000000000;
vector<int>Adj[MAXV];
int n;
bool vis[MAXV]={false};

void DFS(int u,int depth){
    vis[u]=true;
    for(int i=0;i<Adj[u].size();i++){//遍历当前结点u后连接的所有出度点
        int v=Adj[u][i];
        if(vis[v]==false){
            DFS(v,depth+1);
        }
    }
}
void DFSTrave(){
    for(int u=0;u<n;u++){
        if(vis[u]==false){
            DFS(u,1);
        }
    }
}

两个结构的DFS区别主要是由于图的表示方法不同,所以在找某个结点下一层的其他结点时,使用邻接矩阵需要遍历所有的位置,查找未被访问的且与该点连接的点。而邻接表只需要查找当前结点后面连接的表即可,不必遍历所有结点。

BFS

思想:每次以扩散的方式访问顶点。从搜索的起点开始,不断地优先访问当前结点的邻居,也就是说,首先访问起点,然后依次访问起点尚未访问的邻居结点,然后根据访问起点邻居结点的先后顺序依次访问他们的邻居,直到找到解或者搜遍整个空间。

步骤:需要建立一个队列,将初始顶点加入队列,通过反复取出队首顶点,将该顶点可到达的未曾加入过队列的顶点全部入队,直到队列为空时遍历结束。伪代码如下:

BFS(u){
    queue q;
    inq[u]=true;
    while(q非空){
        for(从u出发可达到的所有顶点v){
            if(inq[v]==false){
                将v入队
                inq[v]=true;
            }
        }
    }
}
BFSTrave(G){
    for(G的所有顶点u){
        if(inq[u]==false){
            BFS(u);
        }
    }
}

邻接矩阵BFS

int n,G[MAXN][MAXN];
bool inq[MAXN]={false};
void BFS(int u){//遍历u所在的连通块
    queue<int> q;//定义队列
    q.push(u);//将初始点u入队
    inq[u]=true;//设置u已加入过队列
    while(!q.empty()){//只要队列非空
        int u=q.front();//取出队首元素
        q.pop();//将队首元素出队
        for(int v=0;v<n;v++){
            //如果u的邻接点v未曾加入过队列
            if(inq[v]==false&&G[u][v]!=INF){
                q.push(v);//将v入队
                inq[v]=true;//标记v为已加入过队列
            }
        }
    }
}
void BFSTrave(){
    for(int u=0;u<n;u++){
        if(inq[u]==false){
            BFS(u);
        }
    }
}

邻接表版本

vector<int> Adj[MAXV];
int n;
bool inq[MAXV]={false};
void BFS(int u){//遍历单个连通块
    queue<int> q;//定义队列
    q.push(u);//将初始结点入队
    inq[u]=true;//设置u已经加入过队列
    while(!q.empty()){//只要队列非空
        int u=q.front();//获取队首元素
        q.pop();//弹出队列
        for(int i=0;i<Adj[u].size();i++){//枚举从u出发可以到达的顶点
            int v=Adj[u][i];
            if(inq[v]==false){//如果v未曾入队
                q.push(v);//将v入队
                inq[v]=true;//设置为已加入过队列
            }
        }
    }
}
void BFSTrave(){//遍历图G
    for(int u=0;u<n;u++){//枚举所有顶点
        if(inq[u]==false){//如果顶点u未曾加入过队列
            BFS(q);//遍历u所在的连通块
        }
    }
}

图模板

Graph类使用map存放结点信息,使用set存放边的信息

class Graph{
public:
    unorder_map<int,Node*>nodes;
    unorder_set<Edge*>edges;
    Graph(){};
};

Node类由五部分组成,分别是节点的值value、入度in、出度out、当前结点的下一个节点nexts、以及从当前结点出发的边edges

class Node{
    public:
    int value;
    int out;
    int in;
    vector<Node*>nexts;
    vector<Edge*>edges;
    Node(int val,int inn=0,int outt=0):value(val),in(inn),out(outt){}
};

Edge类由三部分组成,分别是权重weight、当前边的from和to结点

class Edge{
public:
    int weight;
    Node* from;
    Node* to;
    Edge(int w,Node* f,Node* t):weight(w),from(f),to(t){}
};

将产生图函数封装成类,采用的是邻接矩阵的方式。图的构建过程如下所示:

  1. 实例化一个Graph对象,对数组按行遍历,取出每行数据对应的权重、from和to结点
  2. 判断当前图中的from和to结点是否存在,不存在则需要创建节点
  3. 用form和to结点创建边
  4. 丰富form结点的指向结点to、边、出度,丰富to结点的入度。
  5. 把建好的边添加到图里的边
class GraphGenerator{
public:
	Graph graph;
    for(int i=0;i<matrix.size();i++){
        //循环读取二维数组中的信息
        int weight=matrix[i][0];
        int from=matrix[i][1];
        int to=matrix[i][2];
        //如果这两个点没在图中出现过,就初始化这个点
        if(graph.nodes.find(from)==graph.nodes.end()){
            graph.nodes[from]=new Node(from);
        }
        if(graph.nodes.find(to)==graph.nodes.end()){
            graph.nodes[to]=new Node(to);
        }
        //从图中取出这两个点,丰富点的信息
        Node* fromNode=graph.nodes[from];
        Node* toNode=graph.nodes[to];
        //将读取到边权重和两端信息初始化一个边对象
        Edge* newEdge=new Edge(weight,fromNode,toNode);
        //丰富fromnode的信息,包括指向的结点、边、出度增加
        fromNode->nexts.push_back(toNode);
        fromNode->edges.push_back(newEdge);
        fromNode->out++;
        //丰富toNode的信息,入度增加
        toNode->in++;
        //将生成的这条边添加到图对象的边集合成员对象中
        graph.edges.insert(newEdge);
    }
    return graph;
};

图搜索

void BFS(Node* head) {
	if (head == NULL)return;
	queue<Node*>q;
	unordered_set<Node*>s;//使用set来判断节点是否加入过队列
	q.push(head);//将首结点加入队列
	s.insert(head);
	while (!q.empty()) {//只要队列不为空,就一直循环
		Node* cur = q.front();//取当前队首元素,并弹出
		cout << cur->value << " ";
		q.pop();
		for (Node* n : cur->nexts) {//遍历该节点的所有相邻(下一层)节点
			if (s.find(n) == s.end()) {//如果这个结点没有加入过队列
				s.insert(n);//就将节点标记为已访问过,并入队
				q.push(n);
			}
		}
	}
}

void DFS(Node* head) {
	if (head == NULL)return;
	stack<Node*>st;
	unordered_set<Node*>se;
    //将头结点入栈并记录
	st.push(head);
	se.insert(head);
	cout << head->value << " ";
    //只要栈不为空,就一直循环
	while (!st.empty()) {
        //弹出栈顶元素作为当前结点
		Node* cur = st.top();
		st.pop();
        //遍历当前结点的所有下一层结点,若没有下一层结点或者下一层结点都被访问了,那么就不断执行上两句程序
        //栈中结点不断弹出,直到找到当前结点的下一层结点或者栈弹空。
		for (Node* n : cur->nexts) {
			if (se.find(n) == se.end()) {//如果这个下一层结点未被访问过
				st.push(cur);//则继续深入,就需要将当前结点和下一层结点都压入栈中
				st.push(n);
				se.insert(n);//标记访问了这个下一层结点
				cout << n->value << " ";
				break;//找到了一个下一层结点,那么就退出当前循环,继续深入查找
			}
		}
	}
}
### 回答1: DFS深度优先搜索)和BFS(广度优先搜索)算法图论中常见的两种算法,用于遍历图或树的节点。以下是C++实现DFS算法实现: ```c++ #include <iostream> #include <vector> #include <stack> using namespace std; void dfs(vector<vector<int>>& graph, vector<bool>& visited, int start) { stack<int> s; s.push(start); while (!s.empty()) { int node = s.top(); s.pop(); if (!visited[node]) { visited[node] = true; cout << node << " "; for (int i = graph[node].size() - 1; i >= 0; --i) { int next_node = graph[node][i]; if (!visited[next_node]) { s.push(next_node); } } } } } int main() { int n = 5; vector<vector<int>> graph(n); graph[0].push_back(1); graph[0].push_back(2); graph[1].push_back(3); graph[1].push_back(4); vector<bool> visited(n, false); dfs(graph, visited, 0); return 0; } ``` BFS算法实现: ```c++ #include <iostream> #include <vector> #include <queue> using namespace std; void bfs(vector<vector<int>>& graph, vector<bool>& visited, int start) { queue<int> q; q.push(start); while (!q.empty()) { int node = q.front(); q.pop(); if (!visited[node]) { visited[node] = true; cout << node << " "; for (int i = 0; i < graph[node].size(); ++i) { int next_node = graph[node][i]; if (!visited[next_node]) { q.push(next_node); } } } } } int main() { int n = 5; vector<vector<int>> graph(n); graph[0].push_back(1); graph[0].push_back(2); graph[1].push_back(3); graph[1].push_back(4); vector<bool> visited(n, false); bfs(graph, visited, 0); return 0; } ``` 这里我们以一个简单的无向图为例,节点从0到4编号,图的邻接表表示为: ``` 0: 1, 2 1: 0, 3, 4 2: 0 3: 1 4: 1 ``` DFS算法BFS算法的输出结果都是:0 2 1 4 3。 ### 回答2: DFS深度优先搜索)和BFS(广度优先搜索)是两种常用的图遍历算法,都可以用来实现C语言。 DFS算法通过递归或者栈的方式实现,可以从图的某个顶点开始,沿着一条路径一直到达没有未探索的邻居节点为止,然后返回到前一个顶点继续探索其他未探索的邻居节点。可以用以下C语言代码实现DFS算法: ```c #include <stdio.h> #define SIZE 100 int visited[SIZE]; //用来标记节点是否访问过 int graph[SIZE][SIZE]; //图的邻接矩阵表示 void dfs(int node) { printf("%d ", node); visited[node] = 1; for(int i = 0; i < SIZE; i++) { if(graph[node][i] && !visited[i]) { dfs(i); } } } int main() { //初始化visited和graph //调用dfs函数 dfs(0); //从节点0开始深度优先搜索 return 0; } ``` BFS算法通过队列的方式实现,可以从图的某个顶点开始,将其加入队列,然后依次将队列中的节点访问并将其邻居节点加入队列,直到队列为空。可以用以下C语言代码实现BFS算法: ```c #include <stdio.h> #define SIZE 100 int visited[SIZE]; //用来标记节点是否访问过 int graph[SIZE][SIZE]; //图的邻接矩阵表示 void bfs(int node) { int queue[SIZE]; int front = 0, rear = 0; queue[rear++] = node; visited[node] = 1; while(front < rear) { int curNode = queue[front++]; printf("%d ", curNode); for(int i = 0; i < SIZE; i++) { if(graph[curNode][i] && !visited[i]) { queue[rear++] = i; visited[i] = 1; } } } } int main() { //初始化visited和graph //调用bfs函数 bfs(0); //从节点0开始广度优先搜索 return 0; } ``` 以上就是用C语言实现DFSBFS算法的代码。在实际应用中,可以根据具体场景选择使用DFS还是BFS来进行图的遍历。 ### 回答3: DFS深度优先搜索)和BFS(广度优先搜索)算法都是用于图的遍历的常见算法。它们在图遍历的顺序、搜索方式和空间复杂度上有所差异。 DFS是一种先深入后回溯的搜索方法。它从起点开始,沿着图的一条路径一直遍历到尽头,然后回溯到上一个节点,继续探索其他未遍历的路径,直到整个图都被遍历完。DFS常用递归或栈的方式实现BFS是一种逐层扩展的搜索方法。它从起点开始,首先遍历起点的所有邻接节点,然后依次遍历邻接节点的邻接节点,以此类推,直到整个图都被遍历完。BFS常用队列的方式实现,每次将待遍历节点加入队列,并在从队列中取出节点时,将其邻接节点加入队列。 在C语言中,实现DFSBFS算法可以借助图的表示方式和遍历的数据结构。一种常见的图的表示方式是邻接矩阵或邻接表,用于存储图的顶点和边的关系。而在遍历过程中,可以借助一个访问标记数组,用于标记节点是否被访问过。 对于DFS算法实现,可以通过递归函数实现,递归函数的参数包括当前遍历的节点、访问标记数组等。递归函数的主体部分可以按照DFS的逻辑进行实现。 而对于BFS算法实现,可以通过队列来实现,首先将起点加入队列,然后循环取出队列中的节点,并将其邻接节点依次加入队列,直到队列为空。在每次取出节点时,可以将其标记为已经访问过。 总之,DFSBFS算法在C语言中的实现需要借助图的表示方式,以及递归函数或队列等数据结构。具体实现的细节还可以根据具体问题的需求进行调整和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值