图的遍历:DFS和BFS算法

DFS(深度优先算法)

1. 原理概述

最基本的DFS是用来解决无向图的遍历问题的。无向图用沿主对角线对称的邻接矩阵存储。DFS算法最基本的代码实现如下:

void dfs(int cur)//cur是当前所在的顶点编号
{
    sum++;//每访问一个顶点,sum+1
    if(sum==n) return;
    for(i=1;i<=n;i++)//从1号顶点到n号顶点依次尝试,看哪些顶点与当前顶点cur有边相连
    {
        if(e[cur][i]==1 && book[i]==0)
        {
            book[i]=1;//标记顶点i已经访问过
            dfs(i);//从顶点i再出发继续遍历
        }
    }
}

上面,cur存储当前正在遍历的顶点,二维数组e存储的是图的边(邻接矩阵),book用来记录哪些顶点已经访问过,sum用来记录已经访问过的顶点个数,n存储图中顶点的总个数。

2. 用DFS求所有可能的排列组合

以上是最基本的dfs算法实现,事实上,代码结构绝不是一成不变的。
例如,应用dfs求一串元素的所有可能的排列,抽象出来的图的分支就十分庞大。我们不关心对整张图的全部遍历,而是希望分别输出每种可能的完整排列顺序。这就需要将dfs的步骤改成:

  1. 定义全局的用于存放所有解的对象和存放一次解的对象。
  2. 在递归函数中,确定递归终止条件(一般为sum==n),并在终止时将一次完整的解存档。
  3. 在递归函数中,若没有终止,代码结构应该是:则先执行动作,调用递归,再撤销动作。

    一个完整的实现代码为:

void dfs(int s, vector<int> &nums){
        if(s == nums.size()) result.push_back(oneResult);

        for(int i = 0; i < nums.size(); i++){
            if(used.count(nums[i]) != 0)   continue;//如果元素已经存在,就pass
            //执行动作
            used.insert(nums[i]);
            oneResult.push_back(nums[i]);
            dfs(s + 1, nums);
            //撤销动作
            oneResult.pop_back();
            used.erase(nums[i]);
        }
    }

其中,名为used的set对象用于存储已经使用过的元素,类似book数组。result存放所有可能的结果,而oneResult存放一次完整的排列结果。

3. 用DFS求城市最短路程问题

首先用一个二维矩阵表示任意两个城市之间的路程。
核心代码如下:

void dfs(int cur,int dis)
{
    int j;
    //如果当前走过的路程已经大于之前找到的最短路,就没有必要往下尝试了
    if(dis>min) return;
    if(cur==n)//如果到达了目标城市
    {
        if(dis<min) min=dis;//更新最小值
        return;
    }
    for(j=1;j<=n;j++)
    {
        //判断当前城市cur到城市j是否有路,并判断城市j是否已经走过
        if(e[cur][j]!=INT_MAX && book[j]==0)
        {
            book[j]=1;//标记城市j已经走过
            dfs(j,dis+e[cur][j]);
            book[j]=0;//撤销对城市j的标记
        }
    }
}

4 先序遍历和DFS

对于二叉树而言,先序遍历和深度优先遍历的效果是一样的。

BFS(广度优先遍历)

1. 原理概述

如果需要分层遍历图,就需要用到deque数据结构(可以用数组加两个辅助指针模拟队列)。BFS的主要思想是:首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点,然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过,遍历结束。
代码实现为:

int book[1001],que[1001],head,tail;//head和end模拟队列的队首和队尾位置
head=1;
tail=1;
//从1号顶点出发,将1号顶点加入队列
que[tail]=1;
tail++;
book[1]=1;//标记1号顶点已访问

//BFS核心部分
while(head<tail)//当队列不为空的时候循环
{
    cur=que[head];
    for(i=1;i<=n;i++)
    {
        //判断从顶点cur到顶点i是否有边,并判断顶点i是否已经被访问过
        if(e[cur][i]!=INT_MAX && book[i]==0)
        {
        //将顶点i加入队列
            que[tail]=i;
            tail++;
            book[i]=1;
        }
        //如果tail大于n,说明所有顶点都被访问过
        if(tail>n)
        {
            break;
        }
    }
    //当一个顶点拓展结束后,要将head++,相当于从队首删除该顶点
    head++;
}

2. 用BFS解最少转机问题

假设有当前城市和目标城市,两者没有直航,但有和其他城市的航班往来。希望找到一种最少转机的方法。
这个问题中,我们不关心两个城市间的往来时间(边的权值),如果可以往来,一律视为有权值为1的边。我们关心从城市A到B经过的边的数量最少。这也是DFS和BFS的不同应用之处。
核心代码如下:

while(head<tail)
{
    cur=que[head].x;//que是一个结构体数组,每个元素是一个结构体,x表示城市编号,s表示转机次数
    for(j=1;j<=n;j++)
    {
        if(e[cur][j]!=INT_MAX && book[j]==0)//从cur到j是否有航班,并且判断j是否已经经过
        {
            que[tail].x=j;
            que[tail].s=que[head].s+1;//转机次数+1
            tail++;
            book[j]=1;
        }
        //如果已经到达目标城市
        if(que[tail].x==end)
        {
            flag=1;
            break;
        }
    }
    //当一个点拓展完后,head++
    head++;
}

DFS和BFS算法的区别

从前面分别举的几个例子可以看出,DFS和BFS应用场景并不相同。

当我们关心一条路径的权重合时,适合用DFS算法。

当我们关心一条路径的边的数量时,适合用BFS算法。

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/jinzhao1993/article/details/51166850
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞