1、图与搜索
简单地说,图就是由一些小圆点(称为顶点)和连接这些小圆点的直线(称为边)组成的。例如上图是由五个顶点(编号为1、2、3、4、5)和5条边(1-2、1-3、1-5、2-4、3-5)组成。
现在我们从1号顶点开始遍历这个图,遍历就是指把图的每一个顶点都访问一次。使用深度优先搜索来遍历这个图将会得到如下的结果。
图中每个顶点右上方的数就表示这个顶点是第几个被访问到——时间戳。
深度优先遍历的主要思想就是:首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;当没有未访问过的顶点时,则回到上一个顶点,继续试探访问别的顶点,直到所有的顶点都被访问过。显然,深度优先遍历是沿着图的某一条分支遍历直到末端,然后回溯,再沿着另一条进行同样的遍历,直到所有的顶点都被访问过为止。
存储一个图最常用的方法是使用一个二维数组e来存储。
上图二维数组中第i行第j列表示的就是顶点i到顶点j是否有边。1表示有边,表示没有边,这里我们将自己到自己(即i等于j)设为0。我们将这种存储图的方法称为图的邻接矩阵存储法。
深度优先搜索的程序实现:
#include <bits/stdc++.h>
using namespace std;
int inf = 99999999;
//构建图
int e[6][6] = {
{0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, inf, 1},
{0, 1, 0, inf, 1, inf},
{0, 1, inf, 0, inf, 1},
{0, inf, 1, inf, 0, inf},
{0, 1, inf, 1, inf, 0}};
//记录访问标识,避免重复访问
int book[101] = {0};
int sum = 0;
int n = 5;
void dfs(int cur) {
cout << cur << " ";
sum++;
if (sum == n) return;
for (int i = 1; i <= n; i++) {
if (e[cur][i] == 1 && book[i] == 0) {
book[i] = 1;
dfs(i);
}
}
return;
}
int main() {
book[1]=1;
dfs(1);
return 0;
}
在上面的代码中变量cur存储的是当前正在遍历的顶点,二维数组 e存储的就是图的边(邻接矩阵),数组 book用来记录哪些顶点已经访问过,变量sum用来记录已经访问过多少个顶点,变量n存储的是图的顶点的总个数。
广度优先搜索的程序实现:
#include <bits/stdc++.h>
using namespace std;
int inf = 99999999;
//构建图
int e[6][6] = {
{0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, inf, 1},
{0, 1, 0, inf, 1, inf},
{0, 1, inf, 0, inf, 1},
{0, inf, 1, inf, 0, inf},
{0, 1, inf, 1, inf, 0}
};
//记录访问标识,避免重复访问
int _queue[10];
int head, tail;
int book[101] = {0};
int n = 5;
void bfs() {
while (head < tail) {
int cur=_queue[head];
for (int i = 1; i <= n; i++) {
if (e[cur][i] == 1 && book[i] == 0) {
book[i] = 1;
//将数据加入队列
_queue[tail] = i;
tail++;
}
}
head++;
}
return;
}
int main() {
//队列初始化
head = tail = 1;
_queue[tail] = 1;
book[1] = 1;
tail++;
bfs();
for(int i=1;i<tail;i++){
cout<<_queue[i]<<" ";
}
return 0;
}
广度优先遍历的主要思想就是:首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点,然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过,遍历结束。
2、图的深度遍历——城市地图
题目描述:(带权值的有向图)
试计算从1号城市到5号城市的最短路径。
实现程序:
#include <bits/stdc++.h>
using namespace std;
int inf = 99999999;
//构建图的邻接矩阵,从1开始编号,将0编号的内容赋值为0
int e[6][6] = {
{0, 0, 0, 0, 0, 0},
{0, 0, 2, inf, inf, 10},
{0, inf, 0, 3, inf, 7},
{0, 4, inf, 0, 4, inf},
{0, inf, inf, inf, 0, 5},
{0, inf, inf, 3, inf, 0}
};
//记录访问标识,避免重复访问
int book[101] = {0};
//记录走过的距离和最优距离
int sum_dis = 0, min_dis = inf, n = 5;
//记录走过的路径城市与最优路径城市
int _count = 0, _min_count = 0;
int city[20], min_city[20];
//深度优先搜索
void dfs(int cur) {
if (sum_dis > min_dis)return;
if (cur == 5) { //达到目的地后的路径存储和距离存储
if (min_dis > sum_dis) {
min_dis = sum_dis;
_min_count = _count;
//将路径放入到最优路径内
for (int i = 1; i <= _count; i++)
min_city[i] = city[i];
}
return;
}
//遍历所有的路径点
for (int i = 1; i <= n; i++) {
if (e[cur][i] < inf && book[i] == 0) {
book[i] = 1;
sum_dis += e[cur][i];
city[++_count] = i;
dfs(i);
book[i] = 0;
_count--;
sum_dis -= e[cur][i];
}
}
return;
}
int main() {
//初始化第一个点
_count++;
city[_count] = 1; //将第一个路径点装入
book[1] = 1;
dfs(1);
//打印输出
cout << min_dis << endl;
for (int i = 1; i <= _min_count; i++)
cout << min_city[i] << " ";
return 0;
}
3、图的广度变量——最小转机
题目描述:(不带权值的无向图)
试计算从1号城市到5号城市的最小经过点。 (广度优先搜索适合求权值为1的最优路径求解)
实现程序:
#include <bits/stdc++.h>
using namespace std;
//队列结构体,记录当前的城市和转机的城市数量
struct node {
int city;
int count_city;
};
//定义结构体变量
struct node _queue[30];
int head, tail;
//图的节点个数
int n = 5;
//构建图的邻接矩阵
int e[6][6] = {
{0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 0, 0},
{0, 1, 0, 1, 1, 0},
{0, 1, 1, 0, 1, 1},
{0, 0, 1, 1, 0, 1},
{0, 0, 0, 1, 1, 0},
};
//避免走重复的路径
int book[10] = {0};
//广度优先搜索算法
void bfs() {
int flag=0;
while (head < tail) { //队列不为空
int cur=_queue[head].city;
for (int i = 1; i <= n; i++) {
//如果该点可以拓展,进行队列拓展
if(e[cur][i]==1 && book[i]==0){
book[i]=1;
_queue[tail].city=i;
//此处比较重要,注意数值的递增
_queue[tail].count_city=_queue[head].count_city+1;
tail++;
if(i==n){
flag=1;
break;
}
}
}
if(flag==1)break;
head++;
}
}
int main() {
//初始化队列
head = tail = 1;
_queue[tail].city = 1;
_queue[tail].count_city = 0;
book[1] = 1;
tail++;
//深度优先的搜索
bfs();
//打印搜索结果
cout << _queue[tail - 1].count_city << endl;
return 0;
}