前言:图的广度优先搜索和深度优先搜索对于获取图的结构信息具有重要的作用,很多图相关的算法都是建立在对图的结构进行搜索的基础上。如最小生成树算法、Dijkstra最短路径算法都采用了和BFS相似的思想。广度优先搜索和二叉树的层序遍历较为相似,需要借助于队列实现;深度优先搜索和二叉树的前序遍历较为相似,需要借助于栈实现;无论是BFS还是DFS,都适用于有向图和无向图。
图的广度优先搜索算法:
基本概念:
对于给定的图G=(V,E),广度优先搜索从一个源顶点出发,通过对其邻接表进行遍历(搜索),可以发现所有与源顶点邻接的顶点。依次类推,不断地向外扩展,进而发现与源顶点的邻接点相邻接的顶点。故得名广度优先搜索(BFS);
算法思想:
为了标记一个顶点是否被发现,其邻接表是否已经被扫描,广度优先搜索采用三种颜色进行标识,即白色、灰色和黑色。
1. 如果一个顶点没有被发现,则其颜色为白色;(未入队列)
2. 如果一个顶点已经被发现而邻接表未被搜索,则其为灰色;(已入队列)
3. 如果一个顶点的邻接表已经被扫描完,则其为黑色;(已出队列)
在扫描一个顶点u的邻接表的过程中,如果发现了一个白色顶点v,则称u为v的父亲节点。由于一个顶点的邻接点一般有多个,为了表示这些邻接点被当前节点(其父节点)发现的顺序,需要借助于队列;对于先发现的节点先入队列(同时意味着先出队列);
应用:在广度优先搜索的过程中,可以记录一些重要的额外信息,如当前节点的父亲节点(用于回溯路径)、源节点到当前节点的距离(用于求最短路径)等。
BFS源码剖析(算法导论版)
广度优先搜索的代码主要包括两个部分,第一部分为初始化,主要是对所有的节点进行着色、父亲节点置空,距离置零等,如下所示:
广度优先搜索的代码第二部分是主循环部分,循环终止条件是队列为空,代码核心是对当前出队的节点(灰色节点)的邻接表进行扫描以发现白色节点。如下图所示:
广度优先搜索的时间复杂度分析:
由于每个节点仅被发现一次,因此每个节点入栈和出栈各一次,时间均为O(1),故入栈和出栈总时间为O(V);由于需要对每个节点的邻接表进行扫描,时间为O(Adj[u]),总时间为O(E);综上所示,广度优先搜索的时间复杂度为O(V+E).即是图邻接表大小的线性函数。
广度优先树和最短路径(Shortest Path):
广度优先搜索的过程会产生一个广度优先树(BST),从源顶点s到当前节点v的最短路径是从s到v所有路径中边数最少的那条。如果两个顶点没有路径,即不可达,则最短路径为无穷大。
在此不加证明地给出:广度优先搜索所计算出的距离即为源顶点到当前节点的最短距离。通过从当前节点的父亲域进行回溯即可获得最短路径。代码如下:
图的深度优先搜索算法
深度优先搜索的操作过程恰如其名,从源顶点s出发,扫描其邻接表以找到“一个”白色邻接点,而后以该白色邻接点为顶点,扫描其邻接表找到它的一个白色邻接点,依次类推,总是尽可能深的发现白色顶点。如果一个节点的所有顶点均不是白色顶点,则回退(出栈)到该节点的父亲节点,找其父亲节点的白色邻接点,直到s可达的所有节点都已经被发现为止。如果图中还有没被发现的白色顶点,则以该白色顶点作为新的源顶点,重复上述过程。
关于深度优先搜索算法的几点说明:
- 以一个顶点为源进行DFS得到一棵深度优先树,以多个顶点为源进行DFS得到一棵深度优先森林。
- 由于需要回溯,所以要借助于栈保存已经发现的父亲节点,以便回溯时找其白色邻接点;
- 除了和BFS一样的颜色、父亲域外,还要为每个节点加盖时间戳,即发现时间d和结束时间f;
- 深度优先搜索所得得路径不是最短路径;但适用于与图的连通性相关的应用。
DFS源码剖析(算法导论版)
深度优先搜索的代码也分为两部分,第一部分是一个外循环,因为可能有多个源顶点,故对于每个源顶点都要执行一次深度优先搜索;代码如下:
深度优先搜索代码的第二部分是对当前源节点进行DFS_Visit,是一个递归过程,代码如下:
DFS的时间复杂度分析:
同广度优先搜索一样,深度优先搜索的时间复杂度也是O(V+E);
DFS非递归版本(用栈消除递归)
void DeepFirstSearch(Node *pScrNode)
{
InitStack(S);
for each vertex u
do u->color=White;
Push(S,pScrNode);
pScrNode->d=time++;
pScrNode->color=Gray;
while(!IsEmpty(S)) //主循环
{
p=GetTop(S);//获取栈顶元素,不出栈
q=p->next;//获取p的邻接顶点
while(q!=Null)
{
if(q->color==White)
{
push(S,q);
q->d=time++;
q->color=Gray;
break;
}
q=q->next;
}//找一个邻接的白色顶点
if(q==Null) //如果没有白色的邻接点,则出栈
{
p=pop(S);
p->f=time++;
p->color=Black;
}
}
}