图上的BFS/DFS方法——孤立点的处理:
参考:
1. 邻接矩阵
直接开一个N×N的二维数组E,然后 E[i][j] 为1的时候表示 i 和 j 之间有一条边,0的时候就没有。
这样很方便简单,但是也有缺点,
1 首先是效率问题,超过1000个点一般不管是空间还是时间都不允许了。
2 然后就是如果两个顶点之间有两条边的话,就没法表示了。
所以现在一般很少用这种方法,当然不排除某些时候还是可以使用的。
2. 邻接表
使用链表的方式保存一个顶点的所有相连接的边,每个顶点表示为这个链表的起始的节点。
当然,每个顶点都要维护一个链表,这个时候可以使用 vector 数组比较方便。
3 使用邻接表建立无向图的一个例子:
// 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;
}
DFS
需要一个栈,因为每次都是搜到之后不停的往下搜,符合先进先出。但是一般来说不用栈,而是直接通过函数的递归就行。
由于可能存在环,所以需要设置 bool 类型的数组,防止有的节点会重复遍历,导致进入无休止的循环遍历状态。
// 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;
}
下面也是一种写法:
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
广度优先搜索,顾名思义,就是先搜索完同一层次的所有节点,然后到下一层,继续搜索下一层的所有节点。以此类推,搜索完所有的节点。
这种遍历的方式类似于遍历一个队列,如同我把每层的节点从左到右放入队列中,所有的层从上到下遍历完。所以,我们可以借助辅助队列,来进行广度优先搜索。
#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中的访问图中存在孤立点的写法
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]);
}
}
}