前言
图的定义:图G由顶点集V和边集E组成,记为G=(V,E)。线性表可以是空表,树可以是空树,但图不能是空图,也就是说图的顶点集V一定非空。
对于图的存储,主要有邻接矩阵法、邻接表法、十字链表等。在本文中,给出的是基于图的邻接矩阵存储的DFS算法和BFS算法。
图的邻接矩阵存储
所谓的邻接矩阵存储,就是用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),这个二维数组称为邻接矩阵。
对于邻接矩阵A[][],若(vi,vj)∈E,则A[i][j] = 1,否则A[i][j] = 0。
#define MAX 100 //顶点数的最大值
int vex[MAX]; //顶点表
int edge[MAX][MAX]; //邻接矩阵,边表
int vexnum; //顶点数
深度优先搜索
深度优先搜索的基本思想
所谓深度优先搜索DFS(Depth-First-Search),就是从某个顶点出发, 尽可能“深”的去搜索一个图。
基本思想:
- 首先访问图中某一起始顶点v
- 然后从v出发,访问与v邻接并且未被访问的任一顶点w1
- 然后再访问与w1邻接且未被访问的任一顶点w2
- 重复上述过程,直到不能再继续向下访问时,依次退回到最近被访问的顶点,若它还有邻接顶点未被访问过,则从该点开始继续上述过程,直至图中所有顶点均被访问过为止。
DFS算法
由上面深度优先搜索的基本思想,如果要实现DFS算法,那么就需要一个一维数组来存储该顶点是否被访问,然后按照DFS的基本思想来编写代码。
从某个顶点v出发,深度优先遍历:
int visited[MAX]; //标记数组
void DFS(int v){
cout<<v;
visited[v] = 1;
for(int w=getFirstNeighbor(v); w!=-1; w=getNextNeighbor(v,w)){ //关于这两个函数,在下面完整代码中写出
if(!visited[w]){
DFS(w);
}
}
}
或者
int visited[MAX]; //标记数组
void DFS(int v){
cout<<v;
visited[v] = 1;
int w = getFirstNeighbor(v);
while(w != -1){
if(!visited[w]){
DFS(w);
}
w = getNextNeighbor(v,w);
}
}
两种方式都可以。
那么对图G进行深度优先遍历:
void DFSTraverse(){
for(int i=1; i<=vexnum; i++){
visited[i] = 0; //初始化访问标记数组
}
for(int i=1; i<=vexnum; i++){
if(!visited[i]){
DFS(i); //从1号顶点开始,进行深度优先遍历
}
}
}
广度优先搜索
广度优先搜索的基本思想
广度优先搜索BFS(Breadth-First-Search)类似于二叉树的层序遍历,会优先考虑最早被发现的顶点。
基本思想:
- 首先访问图中某一起始顶点v
- 然后从v出发,依次访问v的各个未访问过的邻接顶点w1, w2, … ,wi
- 然后再依次访问w1, w2, … ,wi的所有未被访问的邻接顶点
- 重复上述过程,直到图中多有顶点都被访问过为止
BFS算法
对于广度优先搜索,不仅要有一个一维数组来存储该结点是否被访问,还需要借助队列来存储所有邻接点。
从某个顶点v出发,广度优先遍历:
void BFS(int v){
queue<int> q;
cout<<v;
visited[v] = 1;
q.push(v);
while(!q.empty()){
int u = q.front(); //获取队首元素
q.pop(); //出队
for(int w=getFirstNeighbor(u); w!=-1; w=getNextNeighbor(u,w)){ //关于这两个函数,在下面完整代码中写出
if(!visited[w]){
cout<<w;
visited[w] = 1;
q.push(w); //入队
}
}
}
}
或者
void BFS(int v){
queue<int> q;
cout<<v;
visited[v] = 1;
q.push(v); //入队
while(!q.empty()){
int u = q.front();
q.pop();
int w = getFirstNeighbot(u);
while(w != -1){
if(!visited[w]){
cout<<w;
visited[w] = 1;
q.push(w);
}
w = getNextNeighbor(u, w);
}
}
}
两种方式都可以。
那么对图G进行广度优先遍历:
void BFSTraverse(){
for(int i=1; i<=vexnum; i++){
visited[i] = 0; //初始化访问标记数组
}
for(int i=1; i<=vexnum; i++){
if(!visited[i]){
BFS(i);
}
}
}
完整代码
#include<bits/stdc++.h>
using namespace std;
#define MAX 100
int edge[MAX][MAX]; //邻接矩阵
int vexnum; //顶点数
int visited[MAX]; //标记数组
//返回第一个邻接点
int getFirstNeighbor(int v){
if(v!=-1){
for(int i=0; i<vexnum; i++){
if(edge[v][i] > 0){ //找到第一个存在权值的边
return i;
}
}
}
return -1;
}
//返回下一个邻接点
int getNextNeighbor(int v, int w){
if(v!=-1 && w!=-1){
for(int i=w+1; i<vexnum; i++){ //从w+1开始
if(edge[v][i] > 0){
return i;
}
}
}
return -1;
}
//深度优先遍历
void DFS(int v){ //从某个顶点v开始
cout<<v;
visited[v] = 1;
for(int w=getFirstNeighbor(v); w!=-1; w=getNextNeighbor(v,w)){
if(!visited[w]){
DFS(w);
}
}
}
void DFSTraverse(){
for(int i=1; i<=vexnum; i++){
visited[i] = 0; //初始化访问标记数组
}
for(int i=1; i<=vexnum; i++){
if(!visited[i]){
DFS(i);
}
}
}
//广度优先遍历
void BFS(int v){ //从某个顶点v开始
queue<int> q; //申请一个队列
cout<<v;
visited[v] = 1;
q.push(v); //入队
while(!q.empty()){
int u = q.front(); //获取队首元素
q.pop(); //出队
for(int w=getFirstNeighbor(u); w!=-1; w=getNextNeighbor(u,w)){
if(!visited[w]){
cout<<w;
visited[w] = 1;
q.push(w);
}
}
}
}
void BFSTraverse(){
for(int i=1; i<=vexnum; i++){
visited[i] = 0; //初始化访问标记数组
}
for(int i=1; i<=vexnum; i++){
if(!visited[i]){
BFS(i);
}
}
}
int main(){
int m; //边数
cin>>vexnum>>m; //输入顶点数、边数
for(int i=0; i<m; i++){ //输入有向边的两个顶点以及权值
int x,y,cost;
cin>>x>>y>>cost;
edge[x][y] = cost;
}
DFSTraverse();
cout<<endl;
BFSTraverse();
return 0;
}
实例:
运行结果:
总结
对于图的深度优先遍历和广度优先搜索,只要模拟几遍就能掌握,关键是要理解掌握DFS和BFS算法。对图的深度优先遍历和广度优先遍历有了了解后,我们可以进一步的优化代码,当然,这两个算法不会改变,详情请参照文章图的深度优先遍历DFS和广度优先遍历BFS(邻接矩阵存储)超详细完整代码进阶版。