文中的代码和部分图参考了 Graph and its representations
目录
DFS,参考:Depth First Search or DFS for a Graph
BFS(广度优先搜索)参考:Breadth First Search or BFS for a Graph
-
图的表示的方法:
- 邻接矩阵
直接开一个N×N的二维数组E,然后 E[i][j] 为1的时候表示 i 和 j 之间有一条边,0的时候就没有。
这样很方便简单,但是也有缺点,
1、首先是效率问题,超过1000个点一般不管是空间还是时间都不允许了。
2、然后就是如果两个顶点之间有两条边的话,就没法表示了。。。
所以现在一般很少用这种方法,当然不排除某些时候还是可以使用的。
int E[5][5]; E[1][2]=1; E[4][2]=0;
- 邻接表
使用链表的方式保存一个顶点的所有相连接的边,每个顶点表示为这个链表的起始的根节点。
当然,每个顶点都要维护一个链表,这个时候可以使用 vector 数组比较方便。
vector <int> E[5]; E[4].push_back(1) // 有一条从4到1的边。
- 使用邻接表建立无向图的一个例子:
// A simple representation of graph using STL #include<bits/stdc++.h> using namespace std; // A utility function to add an edge in an // undirected graph. void addEdge(vector<int> adj[], int u, int v) { adj[u].push_back(v); adj[v].push_back(u); } // A utility function to print the adjacency list // representation of graph void printGraph(vector<int> adj[], int V) { for (int v = 0; v < V; ++v) { cout << "\n Adjacency list of vertex " << v << "\n head "; for (auto x : adj[v]) cout << "-> " << x; printf("\n"); } } // Driver code int main() { int V = 5; vector<int> adj[V]; addEdge(adj, 0, 1); addEdge(adj, 0, 4); addEdge(adj, 1, 2); addEdge(adj, 1, 3); addEdge(adj, 1, 4); addEdge(adj, 2, 3); addEdge(adj, 3, 4); printGraph(adj, V); return 0; }
先上图:需要一个栈,因为每次都是搜到之后不停的往下搜,符合先进先出。但是一般来说不用栈,而是直接通过函数的递归就行了。
如图所示,由于存在环,所以需要设置 bool 类型的数组,防止有的节点会重复遍历,导致进入无休止的循环遍历状态。
先从2开始搜索,然后0与2连接,所以搜索0,再从0开始搜索,由于有一条指向2的路线,但是2一开始就已经访问过了,所以选择与0相连接的另一条没有被访问过节点1。此时从1开始搜索,1只有一条指向2的路线,但是2已经遍历过,所以回退到上一个连接点0,与0向连接的节点都已经被访问过了,所以再回退到2,此时还有与2连接的节点3没有访问过,此时访问3,至此,DFS遍历结束。
// C++ program to print DFS traversal from // a given vertex in a given graph #include<iostream> #include<list> using namespace std; // Graph class represents a directed graph // using adjacency list representation class Graph { int V; // No. of vertices // Pointer to an array containing // adjacency lists list<int> *adj; // A recursive function used by DFS void DFSUtil(int v, bool visited[]); public: Graph(int V); // Constructor // function to add an edge to graph void addEdge(int v, int w); // DFS traversal of the vertices // reachable from v void DFS(int v); }; Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; } void Graph::addEdge(int v, int w) { adj[v].push_back(w); // Add w to v’s list. } void Graph::DFSUtil(int v, bool visited[]) { // Mark the current node as visited and // print it visited[v] = true; cout << v << " "; // Recur for all the vertices adjacent // to this vertex list<int>::iterator i; for (i = adj[v].begin(); i != adj[v].end(); ++i) if (!visited[*i]) DFSUtil(*i, visited); } // DFS traversal of the vertices reachable from v. // It uses recursive DFSUtil() void Graph::DFS(int v) { // Mark all the vertices as not visited bool *visited = new bool[V]; for (int i = 0; i < V; i++) visited[i] = false; // Call the recursive helper function // to print DFS traversal DFSUtil(v, visited); } int main() { // Create a graph given in the above diagram Graph g(4); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(2, 3); g.addEdge(3, 3); cout << "Following is Depth First Traversal" " (starting from vertex 2) \n"; g.DFS(2); return 0; }
访问存在孤立点的图
上面是从一个指定的源点DFS访问节点,但是如果图中有孤立的点,则不能保证图中所有的点都能被访问到。所以,改变遍历的方式,改为不指定源点,将每一个节点都设置为源点,只要在从源点开始DFS遍历前,判断一下是否已经访问过就可以了。代码改动如下:
#include <iostream> #include <cstdio> #include <cstring> #include <list> #include <bits/stdc++.h> using namespace std; // C++ program to print DFS traversal from // a given vertex in a given graph // Graph class represents a directed graph // using adjacency list representation class Graph { int V; // No. of vertices // Pointer to an array containing // adjacency lists list<int> *adj; // A recursive function used by DFS void DFSUtil(int v, bool visited[]); public: Graph(int V); // Constructor // function to add an edge to graph void addEdge(int v, int w); // DFS traversal of the vertices // reachable from v void DFS(); // --------------------------- }; Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; } void Graph::addEdge(int v, int w) { adj[v].push_back(w); // Add w to v’s list. } void Graph::DFSUtil(int v, bool visited[]) { // Mark the current node as visited and // print it visited[v] = true; cout << v << " "; // Recur for all the vertices adjacent // to this vertex list<int>::iterator i; for (i = adj[v].begin(); i != adj[v].end(); ++i) if (!visited[*i]) DFSUtil(*i, visited); } // DFS traversal of the vertices reachable from v. // It uses recursive DFSUtil() void Graph::DFS() // ------------------------ { // Mark all the vertices as not visited bool *visited = new bool[V]; for (int i = 0; i < V; i++) visited[i] = false; // Call the recursive helper function // to print DFS traversal for(int i=0; i<V; i++) // ------------------ { if(!visited[i]) DFSUtil(i, visited); // -------------------- } } int main() { // Create a graph given in the above diagram Graph g(7); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(2, 3); g.addEdge(3, 3); cout << "Following is Depth First Traversal" " (starting from vertex 2) \n"; g.DFS(); // --------------------- return 0; }
下面也是一种写法:参考 算法录 之 BFS和DFS
bool vis[110]; int N; void DFS(int u) { int len; vis[u]=1; len=E[u].size(); for(int i=0;i<len;++i) if(vis[E[u][i]]==0) DFS(E[u][i]); }
-
DFS的非递归写法
- 比如建立如下的一个图,
- 代码如下:
#include <iostream> #include <cstdio> #include <cstring> #include <list> #include <bits/stdc++.h> using namespace std; // C++ program to print DFS traversal from // a given vertex in a given graph // Graph class represents a directed graph // using adjacency list representation class Graph { int V; // No. of vertices // Pointer to an array containing // adjacency lists list<int> *adj; stack<int>st; // A recursive function used by DFS void DFSUtil(int v, bool visited[], stack<int>st); public: Graph(int V); // Constructor // function to add an edge to graph void addEdge(int v, int w); // DFS traversal of the vertices // reachable from v void DFS(); }; Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; } void Graph::addEdge(int v, int w) { adj[v].push_back(w); // Add w to v¡¯s list. } void Graph::DFSUtil(int v, bool visited[], stack<int> st) { // Mark the current node as visited and // print it visited[v] = true; // 遍历每个源点,如果没有访问,就打印输出,并入栈 cout << v << " "; st.push(v); while(!st.empty()) // 如果栈不为空 { int head = st.top(); list<int>::iterator it = adj[head].begin(); bool flag = false; // 设置标记,如果当前节点没有邻接点或者邻接点都已经被访问过,则将当前栈中的一个元素出队 for(it = adj[head].begin(); it!=adj[head].end(); it++) // 开始遍历当前节点的邻接点 { if(!visited[*it]) // 如果邻接点没有访问过 { cout<< *it <<" "; st.push(*it); visited[*it] = true; //设置邻接点已经访问过 flag = true; // 设置标记为 true,代表当前节点存在一个没有被访问过的邻接点 break; } } if(flag == false) // 如果当前节点没有邻接点或者邻接点都已经被访问过,则栈不为空时出栈一个元素。 { if(!st.empty()) st.pop(); } } } void Graph::DFS() { bool *visited = new bool[V]; for(int i=0; i<V; i++) visited[i] = false; for(int i=0; i<V; i++) //依次以每个结点为源点 { if(!visited[i]) DFSUtil(i, visited, st); } } int main() { // Create a graph given in the above diagram Graph g(10); g.addEdge(0, 1); g.addEdge(3, 3); g.addEdge(2, 4); g.addEdge(2, 5); cout << "Following is Depth First Traversal" " (starting from vertex 2) \n"; g.DFS(); return 0; }
-
BFS(广度优先搜索)参考:Breadth First Search or BFS for a Graph
广度优先搜索,顾名思义,就是先搜索完同一层次的所有节点,然后到下一层,继续搜索下一层的所有节点。以此类推,搜索完所有的节点。
这种遍历的方式类似于遍历一个队列,如同我把每层的节点从左到右放入队列中,所有的层从上到下遍历完。所以,我们可以借助辅助队列,来进行广度优先搜索。
对上面的图进行BFS遍历的话:结果为 2,0,3,1。
#include <iostream> #include <cstdio> #include <cstring> #include <list> #include <bits/stdc++.h> using namespace std; // 新建一个 Graph 类,使用邻接表表示一个有向图, class Graph { private: int V; // 表示有向图中节点的数量 list<int> *adj; // 使用STL中的 list 容器,定义链表数组,数组中的每一个元素表示一个链表 public: Graph(int V); //构造函数初始化 void addEdge(int v, int w); // 向图添加一条边 void BFS(int s); //从节点 s 开始,BFS 遍历图。 }; Graph::Graph(int V) { this->V = V; adj = new list<int>[V]; } void Graph::addEdge(int v, int w) { adj[v].push_back(w);// 把 w 添加到v的链表中。 } void Graph::BFS(int s) { bool *visit = new bool[V]; for(int i=0; i<V; i++) visit[i] = false; list<int>queue;// list 是一个双向链表,所以在两头都可以执行插入删除操作, // 所以直接使用 list 来模拟队列 visit[s] = true; //标记当前顶点已经被访问标记过,并入队列 queue.push_back(s); list<int>::iterator it; // 初始化迭代器,用于遍历容器,这里是用作遍历当前节点的邻接点 while(!queue.empty()) { s = queue.front(); cout << s << " "; queue.pop_front(); for(it = adj[s].begin(); it!=adj[s].end(); it++) { if(!visit[*it]) // 如果已经访问过,则继续遍历下一个没有访问过得邻接点 { visit[*it] = true; //没有访问过,则先标记已经访问过了,防止重复遍历 queue.push_back(*it); } } } } int main() { Graph g(4); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(2, 3); g.addEdge(3, 3); cout << "Following is Breadth First Traversal " << "(starting from vertex 2) \n"; g.BFS(2);//从 2 开始进行BFS遍历 return 0; }
访问存在孤立点的图
上面的写法如果是在图中存在孤立点的时候,也会不能遍历到,所以,应该以每个结点为源点,进行BFS遍历,具体的写法可以参看上面 DFS中的访问图中存在孤立点的写法
下面的写法类似:参考 算法录 之 BFS和DFS
bool vis[110]; // 记录已经走过的点,防止重复访问。 void BFS(int root,int N) // N个点的图,从root点开始搜索。 { queue <int> que; memset(vis,0,sizeof(vis)); // 初始化。 vis[root]=1; que.push(root); int u,len; while(!que.empty()) { u=que.front(); que.pop(); len=E[u].size(); for(int i=0;i<len;++i) // 找到和u相连的所有点,存在一个vector里面。 if(vis[E[u][i]]==0) { vis[E[u][i]]=1; que.push(E[u][i]); } } }
-
应用:
- BFS算法求解单源最短路劲问题
-
参考文献