图的深度优先搜索和广度优先搜索
深度优先搜索的主要思想:首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;当没有未访问过的顶点时,则回到上一个顶点,继续试探访问别的顶点,直到所有的顶点都被访问过。
深度优先遍历是沿着图的某一条分支遍历直到末端,然后回溯,再沿着另一条进行同样的遍历,直到所有的顶点都被访问过为止。
深度优先搜索(使用邻接矩阵)
#include<iostream>
#include<vector>
#include<climits>
#include<algorithm>
using namespace std;
void dfs(vector<vector<int>> & graph, vector<bool> & flags, int point)
{
cout << point << ' ';
if (find(flags.begin(), flags.end(), false) == flags.end())
{
//所有顶点遍历完
cout << endl;
return;
}
for (int i = 0; i < graph[point].size(); ++i)
//依次尝试所有顶点,判断哪些顶点与当前边相连
{
if (graph[point][i] == 1 && flags[i] == false)
{
//当前点到i有边,并且i没被访问过
flags[i] = true; //标记i为访问过
dfs(graph, flags, i); //从i出发继续遍历
}
}
}
int main(void)
{
int v = 5; //顶点数
int e = 5; //边数
vector<vector<int>> graph(v, vector<int>(v, INT_MAX));
vector<bool> flags(v, false);
for (int i = 0; i < v; ++i)
graph[i][i] = 0;
graph[0][1] = graph[1][0] = 1;
graph[0][2] = graph[2][0] = 1;
graph[0][4] = graph[4][0] = 1;
graph[1][3] = graph[3][1] = 1;
graph[2][4] = graph[4][2] = 1;
/*
0 1 1 ∞ 1
1 0 ∞ 1 ∞
1 ∞ 0 ∞ 1
∞ 1 ∞ 0 ∞
1 ∞ 1 ∞ 0
*/
flags[0] = true;
dfs(graph, flags, 0); //从顶点0开始深度搜索
return 0;
}
广度优先遍历的主要思想:首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点,然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过,遍历结束。
广度优先搜索(使用邻接矩阵)
#include<iostream>
#include<vector>
#include<queue>
#include<climits>
using namespace std;
int main(void)
{
int v = 5; //顶点数
int e = 5; //边数
vector<vector<int>> graph(v, vector<int>(v, INT_MAX));
vector<bool> flags(v, false);
for (int i = 0; i < v; ++i)
graph[i][i] = 0;
graph[0][1] = graph[1][0] = 1;
graph[0][2] = graph[2][0] = 1;
graph[0][4] = graph[4][0] = 1;
graph[1][3] = graph[3][1] = 1;
graph[2][4] = graph[4][2] = 1;
/*
0 1 1 ∞ 1
1 0 ∞ 1 ∞
1 ∞ 0 ∞ 1
∞ 1 ∞ 0 ∞
1 ∞ 1 ∞ 0
*/
queue<int> bfs;
bfs.push(0); //从顶点0开始广度搜索
flags[0] = true;
while (!bfs.empty())
{
cout << bfs.front() << ' ';
for (int i = 0; i < graph[bfs.front()].size(); ++i)
{
if (graph[bfs.front()][i] == 1 && flags[i] == false)
{
flags[i] = true;
bfs.push(i);
}
}
bfs.pop();
}
cout << endl;
return 0;
}
使用深度优先搜索和广度优先搜索来遍历图都将会得到这个图的生成树。
无向图的边没有方向,对应的邻接矩阵是沿主对角线对称的。
城市地图——图的深度优先遍历
如果给图的每条边规定一个方向,那么得到的图成为有向图,其边也称为有向边。在有向图中,与一个点相关联的边有出边和入边之分,而与一个有向边关联的两个点也有始点和终点之分。相反,边没有方向的图称为无向图。
存储图的方法除了邻接矩阵,还有很多种,比如邻接表等。
#include<iostream>
#include<vector>
#include<climits>
#include<algorithm>
using namespace std;
void dfs(vector<vector<int>> & graph, vector<bool> & flags,
int curr, vector<int> & path, int dis,
int & min_count, vector<int> & min_path)
{
//如果当前走过的距离已经大于之前找到的最短距离,没必要继续下去,剪枝
if (dis > min_count)
return;
if (curr == graph.size() - 1) //已到达目的地,顶点4
{
if (dis < min_count) //把最小值保存下来
{
min_count = dis;
min_path.resize(path.size());
copy(path.begin(), path.end(), min_path.begin());
}
return;
}
for (int i = 0; i < graph[curr].size(); ++i)
{
if (graph[curr][i] > 0 && graph[curr][i] < INT_MAX && flags[i] == false)
{
//当前点到i有边,并且i没被访问过
flags[i] = true; //尝试顶点i
path.push_back(i);
//从顶点i出发,继续寻找
dfs(graph, flags, i, path, dis + graph[curr][i], min_count, min_path);
//之前搜索完毕,取消标记
path.pop_back();
flags[i] = false;
}
}
}
int main(void)
{
int v = 5; //顶点数
int e = 8; //边数
vector<vector<int>> graph(v, vector<int>(v, INT_MAX));
vector<bool> flags(v, false);
for (int i = 0; i < v; ++i)
graph[i][i] = 0;
graph[0][1] = 2;
graph[0][4] = 10;
graph[1][2] = 3;
graph[1][4] = 7;
graph[2][0] = 4;
graph[2][3] = 4;
graph[3][4] = 5;
graph[4][2] = 3;
/*
0 2 ∞ ∞ 10
∞ 0 3 ∞ 7
4 ∞ 0 4 ∞
∞ ∞ ∞ 0 5
∞ ∞ 3 ∞ 0
*/
vector<int> path; //临时路径
vector<int> min_path; //保存最短路径
int min_count = INT_MAX;
//从顶点0出发,目前走过了0的距离,最短的距离和那条路径保存在最后两个参数
flags[0] = true;
path.push_back(0);
dfs(graph, flags, 0, path, 0, min_count, min_path);
cout << min_count << endl;
for (auto iter = min_path.cbegin(); iter != min_path.cend(); ++iter)
{
cout << *iter;
if (iter == min_path.cend() - 1)
break;
cout << " -> ";
}
cout << endl;
return 0;
}
求图上两点之间的最短路径,除了使用深度优先搜索之外,还可以使用广度优先搜索、Floyd、Bellman-Ford、Dijkstra等。
最少转机——图的广度优先遍历
要求转机次数最少,所以可以认为所有边的长度都是1。
#include<iostream>
#include<vector>
#include<climits>
#include<queue>
using namespace std;
struct Node_Trun
{
int node;
int turn;
};
int main(void)
{
int v = 5; //顶点数
int e = 8; //边数
vector<vector<int>> graph(v, vector<int>(v, INT_MAX));
vector<bool> flags(v, false);
for (int i = 0; i < v; ++i)
graph[i][i] = 0;
graph[0][1] = 1;graph[1][0] = 1;
graph[0][2] = 1;graph[2][0] = 1;
graph[1][2] = 1;graph[2][1] = 1;
graph[1][3] = 1;graph[3][1] = 1;
graph[2][3] = 1;graph[3][2] = 1;
graph[2][4] = 1;graph[4][2] = 1;
graph[3][4] = 1;graph[4][3] = 1;
/*
0 2 ∞ ∞ 10
∞ 0 3 ∞ 7
4 ∞ 0 4 ∞
∞ ∞ ∞ 0 5
∞ ∞ 3 ∞ 0
*/
queue<Node_Trun> bfs;
//从顶点0出发,转机次数为0
Node_Trun start;
start.node = 0;
start.turn = 0;
flags[0] = true;
bfs.push(start);
//目的顶点
int end = 4;
bool isfind = false;
while (!bfs.empty() && !isfind)
{
for (int i = 0; i < graph[bfs.front().node].size(); ++i)
{
if (graph[bfs.front().node][i] > 0 && graph[bfs.front().node][i] < INT_MAX
&& flags[i] == false)
{
flags[i] = true;
bfs.push({ i, bfs.front().turn + 1 });
}
if (bfs.back().node == end)
{
//如果到达目的,停止扩展,任务结束
isfind = true;
break;
}
}
bfs.pop();
}
cout << bfs.back().turn - 1 << endl;
return 0;
}
也可以用深度优先搜索解决,但是用广度优先搜索会更快。广度优先搜索更加适用于所有边的权值相同的情况。