[转载]算法王帖:精选经典的24个算法 [3.BFS和DFS优先搜索]
2010-12-16 00:29 Rollen Holt 阅读(1004) 评论(1) 编辑 收藏 举报
-------------------------------------------------
翻遍网上,关于此类BFS和DFS算法的文章,很多。但全都是抄来抄去,最后,都说不出个所以然来。
此文,我自作主张,加精。读完此文,我想,
你对图的广度优先搜索和深度优先搜索定会有个通通透透,彻彻底底的认识。
---------------------
首先,看下算法导论一书关于 此DFS 深度优先搜索算法和 BFS 广度优先搜索算法的概述。
算法导论第二版,中译本,第324页。
广度优先搜索(BFS)
在Prime最小生成树算法,和Dijkstra单源最短路径算法中,都采用了与BFS 算法类似的思想。
BFS(G, s)
1 for each vertex u ∈ V [G] - {s}
2 do color[u] ← WHITE
3 d[u] ← ∞
4 π[u] ← NIL//除了源顶点s之外,第1-4行置每个顶点为白色,置每个顶点u的d[u]为无穷大,
//置每个顶点的父母为NIL。
5 color[s] ← GRAY
//第5行,将源顶点s置为灰色,这是因为在过程开始时,源顶点已被发现。
6 d[s] ← 0 //第6行,将d[s]初始化为0。7 π[s] ← NIL //第7行,将源顶点的父顶点置为NIL。
8 Q ← Ø
9 ENQUEUE(Q, s)//第8、9行,初始化队列Q,使其仅含源顶点s。
10 while Q ≠ Ø
11 do u ← DEQUEUE(Q)//第11行,确定队列头部Q头部的灰色顶点u,并将其从Q中去掉。
12 for each v ∈ Adj[u] //for循环考察u的邻接表中的每个顶点v
13 do if color[v] = WHITE
14 then color[v] ← GRAY //置为灰色
15 d[v] ← d[u] + 1 //距离被置为d[u]+1
16 π[v] ← u //u记为该顶点的父母
17 ENQUEUE(Q, v) //插入队列中
18 color[u] ← BLACK //u 置为黑色
illustrates the progress of BFS on a sample graph.
Vertex u is WHITE before time d[u], GRAY between time d[u] and time f [u], and BLACK thereafter.The following pseudocode is the basic depth-first-search algorithm. The input graph G may be undirected or directed. The variable time is a global variable that we use for timestamping.DFS(G)
1 for each vertex u ∈ V [G]
2 do color[u] ← WHITE
3 π[u] ← NIL
4 time ← 0
5 for each vertex u ∈ V [G]
6 do if color[u] = WHITE
7 then DFS-VISIT(u)DFS-VISIT(u)
1 color[u] ← GRAY ▹White vertex u has just been discovered.
2 time ← time +1
3 d[u] time
4 for each v ∈ Adj[u] ▹Explore edge(u, v).
5 do if color[v] = WHITE
6 then π[v] ← u
7 DFS-VISIT(v)
8 color[u] BLACK ▹ Blacken u; it is finished.
9 f [u] ▹ time ← time +1illustrates the progress of DFS on the graph shown in//先后顺序 -> 横向
括号定理(时间先后顺序)
图的深度优先遍历演示系统:
http://sjjg.js.zwu.edu.cn/SFXX/sf1/sdyxbl.html
咱们来看深度优先搜索的递归实现与非递归实现,
最后,再给出俩算法实现的具体代码。
1图的深度优先搜索算法的回顾:
图的深度优先搜索用递归实相当的简洁好懂:
void dftR(PGraphMatrix inGraph)
{
PVexType v;
assertF(inGraph!=NULL,"in dftR, pass in inGraph is nulln");
printf("n===start of dft recursive version===n");
for(v=firstVertex(inGraph);v!=NULL;v=nextVertex(inGraph,v))
if(v->marked==0)
dfsR(inGraph,v);
printf("n===end of dft recursive version===n");
}
void dfsR(PGraphMatrix inGraph,PVexType inV)
{
PVexType v1;
assertF(inGraph!=NULL,"in dfsR,inGraph is nulln");
assertF(inV!=NULL,"in dfsR,inV is nulln");
inV->marked=1;
visit(inV);
for(v1=firstAdjacent(inGraph,inV);v1!=NULL;v1=nextAdjacent(inGraph,inV,v1))
//v1当为v的邻接点。
if(v1->marked==0)
dfsR(inGraph,v1);
}
2 DFS 非递归版本
2.1.1非递归版本1---借助结点类型为队列的栈实现
首先,将从某个顶点开始进行的深度优先搜索树画出,可以看到,
对从某个结点开始的深度优先遍历,实际上就是基于这个深度优先搜索树一个遍历,
不过,树中的的很多结点当marked值为1时将不必再访问.举例如下:
根据深度优先搜索规则,图1对应的两棵深度优先搜索树如右所示.
其中,用土黄色”\”标注的树枝表示在第一次试探的过程中便不需要访问的边,
而用蓝色”\”标注的树枝则表示在回访过程中不再需要访问的结点.
联系树的前序遍历的非递归实现:
可知,其中无非是分成“探左”和“访右”两大块访右需借助栈中弹出的结点进行.
在图的深度优先搜索中,同样可分成“深度探索”和“回访上层未访结点”两块:1图的深度探索这样一个过程和树的“探左”完全一致,只要对已访问过的结点作一个判定即可
2而图的回访上层未访结点和树的前序遍历中的“访右”也是一致的.
但是,对于树而言,是提供rightSibling这样的操作的,因而访右相当好实现。
在这里,若要实现相应的功能,我考虑是将每一个当前结点的下层结点中,如果有m个未访问结点,
则最左的一个需要访问,而将剩余的m-1个结点按从左到右的顺序推入一个队列中。
并将这个队列压入一个堆栈中。这样,当当前的结点的邻接点均已访问或无邻接点需要回访时,
则从栈顶的队列结点中弹出队列元素,将队列中的结点元素依次出队,
若已访问,则继续出队(当当前队列结点已空时,则继续出栈,弹出下一个栈顶的队列),
直至遇到有未访问结点(访问并置当前点为该点)或直到栈为空(则当前的深度优先搜索树停止搜索)
将算法通过精简过的C源程序的方式描述如下:
//dfsUR:功能从一个树的某个结点inV发,以深度优先的原则访问所有与它相邻的结点
void dfsUR(PGraphMatrix inGraph,PVexType inV)
{
PSingleRearSeqQueue tmpQ; //定义临时队列,用以接受栈顶队列及压栈时使用
PSeqStack testStack; //存放当前层中的m-1个未访问结点构成队列的堆栈.
//一些变量声明,初始化动作
//访问当前结点
inV->marked=1;
visit(inV);
do
{
flag2=0;
//flag2是一个重要的标志变量,用以、说明当前结点的所有未访问结点的个数,两个以上的用2代表*/
//flag2:0:current node has no adjacent which has not been visited.
//1:current node has only one adjacent node which has not been visited.
//2:current node has more than one adjacent node which have not been visited.
v1=firstAdjacent(inGraph,inV); //邻接点v1
while(v1!=NULL) //访问当前结点的所有邻接点
{
if(v1->marked==0)//find one adjacent node which has not been visited.
{
if(flag2==0)//visit the first unvisited adjacent node
{
//访问最左结点
visit(v1);
v1->marked=1;
flag2=1;
//记录最左儿子
lChildV=v1;
//save the current node's first unvisited(has been visited at this time)adjacent node
}
else if(flag2==1)//current node has second unvisited node
{
//新建一个队列,申请空间,并加入第一个结点
flag2=2;
}
else if(flag2==2)//current node has more unvisited node
{
enQueue(tmpQ,v1);
}
}
v1=nextAdjacent(inGraph,inV,v1);
}
if(flag2==2)//push adjacent nodes which are not visited.
{
//将存有当前结点的m-1个未访问邻接点的队列压栈
seqPush(testStack,tmpQ);
inV=lChildV;
}
else if(flag2==1)//only has one adjacent which has been visited.
{
//只有一个最左儿子,则置当前点为最左儿子
inV=lChildV;
}
else if(flag2==0)
//has no adjacent nodes or all adjacent nodes has been visited
{
//当当前的结点的邻接点均已访问或无邻接点需要回访时,则从栈顶的队列结点中弹出队列元素,
//将队列中的结点元素依次出队,若已访问,则继续出队(当当前队列结点已空时,
//则继续出栈,弹出下一个栈顶的队列),直至遇到有未访问结点(访问并置当前点为该点)或直到栈为空
flag=0;
while(!isNullSeqStack(testStack)&&!flag)
{
v1=frontQueueInSt(testStack);
deQueueInSt(testStack);
if(v1->marked==0)
{
visit(v1);
v1->marked=1;
inV=v1;
flag=1;
}
}
}
}while(!isNullSeqStack(testStack));//the algorithm ends when the stack is null
}
所以,这里应使用的数据结构的构成方式应该采用下面这种形式:
1)队列的实现中,每个队列结点均为图中的结点指针类型.
定义一个以队列尾部下标加队列长度的环形队列如下:struct SingleRearSeqQueue;
typedef PVexType QElemType;
typedef struct SingleRearSeqQueue* PSingleRearSeqQueue;
struct SingleRearSeqQueue
{
int rear;
int quelen;
QElemType dataPool[MAXNUM];
};
其余基本操作不再赘述.
2)堆栈的实现中,每个堆栈中的结点元素均为一个指向队列的指针,定义如下:
#define SEQ_STACK_LEN 1000
#define StackElemType PSingleRearSeqQueue
struct SeqStack;
typedef struct SeqStack* PSeqStack;
struct SeqStack
{
StackElemType dataArea[SEQ_STACK_LEN];
int slot;
};
为了提供更好的封装性,对这个堆栈实现两种特殊的操作2.1) deQueueInSt操作用于将栈顶结点的队列中的队首元素弹出.
void deQueueInSt(PSeqStack inStack)
{
if(isEmptyQueue(seqTop(inStack))||isNullSeqStack(inStack))
{
printf("in deQueueInSt,under flow!n");
return;
}
deQueue(seqTop(inStack));
if(isEmptyQueue(seqTop(inStack)))
inStack->slot--;
}2.2) frontQueueInSt操作用以返回栈顶结点的队列中的队首元素.
QElemType frontQueueInSt(PSeqStack inStack)
{
if(isEmptyQueue(seqTop(inStack))||isNullSeqStack(inStack))
{
printf("in frontQueueInSt,under flow!n");
return 'r';
}
return getHeadData(seqTop(inStack));
}
3)DFS 非递归主要算法的实现:
//外层的周游层和递归版一致
void dftUR(PGraphMatrix inGraph)
{
PVexType v;
assertF(inGraph!=NULL,"in dftR, pass in inGraph is nulln");
printf("n===start of dft recursive version===n");
for(v=firstVertex(inGraph);v!=NULL;v=nextVertex(inGraph,v))
if(v->marked==0)
dfsUR(inGraph,v);
printf("n===end of dft recursive version===n");
}
void dfsUR(PGraphMatrix inGraph,PVexType inV)
{
PSingleRearSeqQueue tmpQ;
PSeqStack testStack;
PVexType v1,lChildV;
int flag,flag2;
int i;
assertF(inGraph!=NULL,"in dfsR,inGraph is nulln");
assertF(inV!=NULL,"in dfsR,inV is nulln");
testStack=(PSeqStack)malloc(sizeof(struct SeqStack));
assertF(testStack!=NULL,"in main,testStack is nulln");
testStack=createNullSeqStack();
for(i=0;i<inGraph->n;i++)
testStack->dataArea[i]=(PSingleRearSeqQueue)malloc(sizeof(struct SingleRearSeqQueue));
inV->marked=1;
visit(inV);
do
{
flag2=0;
//
//flag2:
//0:current node has no adjacent which has not been visited.
//1:current node has only one adjacent node which has not been visited.
//2:current node has more than one adjacent node which have not been visited.
v1=firstAdjacent(inGraph,inV);
while(v1!=NULL)
{
if(v1->marked==0)//find one adjacent node which has not been visited.
{
if(flag2==0)//visit the first unvisited adjacent node
{
visit(v1);
v1->marked=1;
flag2=1;
lChildV=v1;
//save the current node's first unvisited(has been visited at this time)adjacent node
}
else if(flag2==1)//current node has second unvisited node
{
tmpQ=(PSingleRearSeqQueue)malloc(sizeof(struct SingleRearSeqQueue));
assertF(tmpQ!=NULL,"tmpQ is nulln");
tmpQ=createNullSingleRearSeqQueue();
for(i=0;i<inGraph->n;i++)
tmpQ->dataPool[i]=(PVexType)malloc(sizeof(VexType));
enQueue(tmpQ,v1);
flag2=2;
}
else if(flag2==2)//current node has more unvisited node
{
enQueue(tmpQ,v1);
}
}
v1=nextAdjacent(inGraph,inV,v1);
}
if(flag2==2)//push adjacent nodes which are not visited.
{
seqPush(testStack,tmpQ);
inV=lChildV;
}
else if(flag2==1)//only has one adjacent which has been visited.
{
inV=lChildV;
}
else if(flag2==0)//has no adjacent nodes or all adjacent nodes has been visited
{
flag=0;
while(!isNullSeqStack(testStack)&&!flag)
{
v1=frontQueueInSt(testStack);
deQueueInSt(testStack);
if(v1->marked==0)
{
visit(v1);
v1->marked=1;
inV=v1;
flag=1;
}
}
}
}while(!isNullSeqStack(testStack));//the algorithm ends when the stack is null
}--------------------
//、完。 July、2010/10/29。
//以下内容不纳入本文范围。仅供 参考。
深度优先搜索与广度优先搜索的具体实现源码:
-------------------------------
- //算法功能:
- //DFS
- #include <stdio.h>
- #include <stdlib.h>
- #define OK 1
- #define NULL 0
- #define MAX_VERTEX_NUM 20 // 最大顶点数
- typedef int Status; //函数的类型,其值是函数结果状态代码
- typedef char VertexType;
- typedef int VRType;
- typedef int InfoType;
- typedef struct ArcNode
- {
- int adjvex; //该边所指的顶点的位置
- struct ArcNode *nextarc; //指向下一条边的指针
- InfoType *info;
- }ArcNode; //表的结点
- typedef struct VNode
- {
- VertexType data; //顶点信息(如数据等)
- ArcNode *firstarc; //指向第一条依附该顶点的边的弧指针
- }VNode, AdjList[MAX_VERTEX_NUM]; //头结点
- typedef struct ALGraph
- {
- AdjList vertices;
- int vexnum, arcnum; //图的当前顶点数和弧数
- int kind;
- }ALGraph;
- //返回顶点v在顶点向量中的位置
- int LocateVex(ALGraph G, char v)
- {
- int i;
- for(i = 0; v != G.vertices[i].data && i < G.vexnum; i++)
- ;
- if(i >= G.vexnum)
- return -1;
- return i;
- }
- //构造邻接链表
- Status CreateDN(ALGraph &G)
- {
- int i, j;
- ArcNode *s;
- printf("输入有向图顶点数: ");
- scanf("%d", &G.vexnum);
- printf("输入有向图边数: ");
- scanf("%d", &G.arcnum);
- getchar();
- for(int i = 0; i < G.vexnum; i++)
- {
- printf("输入第%d个顶点信息:", i+1);
- scanf("%c", &G.vertices[i]); //构造顶点向量
- G.vertices[i].firstarc = NULL;
- getchar();
- }
- char v1, v2;
- for(int k = 0; k < G.arcnum; k++)
- {
- printf("输入第 %d 条边依附的顶点v1: ", k+1);
- scanf("%c", &v1);
- getchar();
- printf("输入第 %d 条边依附的顶点v2: ", k+1);
- scanf("%c", &v2);
- getchar();
- int i = LocateVex(G, v1);
- int j = LocateVex(G, v2); //确定v1 , v2在G中的位置
- s = (ArcNode*) malloc (sizeof(ArcNode));
- s->adjvex = j; //该边所指向的顶点的位置为j
- s->nextarc = G.vertices[i].firstarc;
- G.vertices[i].firstarc =s;
- }
- return OK;
- }
- Status PrintAdjList(ALGraph &G)
- {
- int i;
- ArcNode *p;
- printf("%4s%6ssn", "编号", "顶点", "相邻边编号");
- for(int i = 0; i < G.vexnum; i++)
- {
- printf("Ml", i, G.vertices[i].data);
- for(p = G.vertices[i].firstarc; p; p = p->nextarc)
- printf("M", p->adjvex);
- printf("n");
- }
- return OK;
- }
- void DFS(ALGraph G, int v, int *visited)
- {
- int w;
- ArcNode *s;
- visited[v] = 1;
- printf("%c ->", G.vertices[v].data);
- s = G.vertices[v].firstarc;
- while(s != NULL)
- {
- w = s->adjvex;
- if(visited[w] == 0)
- DFS(G, w, visited);
- s = s->nextarc; //访问下一个邻接点。
- }
- }
- //对图G做深度优先遍历
- Status DFSTraverse(ALGraph G)
- {
- int v;
- int visited[MAX_VERTEX_NUM];
- for(v = 0; v < G.vexnum; ++v)
- visited[v] = 0; //初始化visited[v]
- for(v = 0; v < G.vexnum; ++v)
- if(visited[v] == 0)
- DFS(G, v, visited); //对未访问的顶点调用上述函数DFS()
- printf("完成n");
- return OK;
- }
- int main()
- {
- printf("==========================n");
- ALGraph G;
- CreateDN(G);
- printf("有向图的邻接表为:n");
- PrintAdjList(G);
- printf("深度优先遍历该有向图:n");
- DFSTraverse(G); //调用图的深度优先函数DFSTraverse()
- return 0;
- }
----------------------------------------------------
- //算法功能:
- //BFS。
- #include <stdio.h>
- #include <stdlib.h>
- #define OK 1
- #define NULL 0
- #define MAX_VERTEX_NUM 20 // 最大顶点数
- #define MAXQSIZE 100
- typedef int Status; //函数的类型,其值是函数结果状态代码
- typedef char VertexType;
- typedef char QElemType;
- typedef int InfoType;
- typedef struct ArcNode
- {
- int adjvex; //该边所指的顶点的位置
- struct ArcNode *nextarc; //指向下一条边的指针
- InfoType *info;
- }ArcNode; //表的结点
- typedef struct VNode
- {
- VertexType data; //顶点信息(如数据等)
- ArcNode *firstarc; //指向第一条依附该顶点的边的弧指针
- }VNode, AdjList[MAX_VERTEX_NUM]; //头结点
- typedef struct ALGraph
- {
- AdjList vertices;
- int vexnum, arcnum; //图的当前顶点数和弧数
- int kind;
- }ALGraph;
- typedef struct SqQueue
- {
- QElemType *base;
- int front;
- int rear;
- }SqQueue;
- //返回顶点v在顶点向量中的位置
- int LocateVex(ALGraph G, char v)
- {
- int i;
- for(i = 0; v != G.vertices[i].data && i < G.vexnum; i++)
- ;
- if(i >= G.vexnum)
- return -1;
- return i;
- }
- //构造无向图邻接链表
- Status CreateUDN(ALGraph &G)
- {
- int i, j;
- ArcNode *s, *t;
- printf("输入有向图顶点数: ");
- scanf("%d", &G.vexnum);
- printf("输入有向图边数: ");
- scanf("%d", &G.arcnum);
- getchar();
- for(int i = 0; i < G.vexnum; i++)
- {
- printf("输入第%d个顶点信息:", i+1);
- scanf("%c", &G.vertices[i]); //构造顶点向量
- G.vertices[i].firstarc = NULL;
- getchar();
- }
- char v1, v2;
- for(int k = 0; k < G.arcnum; k++)
- {
- printf("输入第 %d 条边依附的顶点v1: ", k+1);
- scanf("%c", &v1);
- getchar();
- printf("输入第 %d 条边依附的顶点v2: ", k+1);
- scanf("%c", &v2);
- getchar();
- int i = LocateVex(G, v1);
- int j = LocateVex(G, v2); //确定v1 , v2在G中的位置
- s = (ArcNode*) malloc (sizeof(ArcNode));
- t = (ArcNode*) malloc (sizeof(ArcNode));
- s->adjvex = j; //该边所指向的顶点的位置为j
- s->nextarc = G.vertices[i].firstarc;
- G.vertices[i].firstarc =s;
- t->adjvex = i; //该边所指向的顶点的位置为j
- t->nextarc = G.vertices[j].firstarc;
- G.vertices[j].firstarc =t;
- }
- return OK;
- }
- Status InitQueue(SqQueue &Q)
- {
- Q.base = (QElemType *) malloc (MAXQSIZE * sizeof(QElemType));
- if(!Q.base)
- {
- printf("分配地址失败!");
- return 0;
- }
- Q.front = Q.rear = 0;
- return OK;
- }
- //已访问图顶点入队
- Status EnQueue(SqQueue &Q, QElemType e)
- {
- if((Q.rear+1) % MAXQSIZE == Q.front) //队列已满
- {
- printf("队列已满!");
- return 0;
- }
- Q.base[Q.rear] = e;
- Q.rear = (Q.rear+1) % MAXQSIZE;
- return OK;
- }
- //判断队列是否为空
- Status QueueEmpty(SqQueue Q)
- {
- if(Q.front == Q.rear)
- return OK;
- else
- return 0;
- }
- //辅助队列队头顶点出队
- char DeQueue(SqQueue &Q)
- {
- QElemType e;
- if(Q.front == Q.rear) //队列为空
- {
- printf("队列为空!");
- return 0;
- }
- e = Q.base[Q.front];
- Q.front = (Q.front+1) % MAXQSIZE;
- return e;
- }
- Status PrintAdjList(ALGraph &G)
- {
- int i;
- ArcNode *p;
- printf("%4s%6ssn", "编号", "顶点", "相邻边编号");
- for(int i = 0; i < G.vexnum; i++)
- {
- printf("Ml", i, G.vertices[i].data);
- for(p = G.vertices[i].firstarc; p; p = p->nextarc)
- printf("M", p->adjvex);
- printf("n");
- }
- return OK;
- }
- void DFS(ALGraph G, int v, int *visited)
- {
- int w;
- ArcNode *s;
- visited[v] = 1;
- printf("%c ->", G.vertices[v].data);
- s = G.vertices[v].firstarc;
- while(s != NULL)
- {
- w = s->adjvex;
- if(visited[w] == 0)
- DFS(G, w, visited);
- s = s->nextarc;
- }
- }
- //对图G做广度优先遍历
- Status BFSTraverse(ALGraph G)
- {
- int v, u, w;
- int visited[MAX_VERTEX_NUM];
- ArcNode *s;
- char e;
- SqQueue Q; //辅助队列
- for(v = 0; v < G.vexnum; ++v)
- visited[v] = 0; //初始化visited[v]
- InitQueue(Q);
- for(v = 0; v < G.vexnum; ++v)
- if(visited[v] == 0) //尚未访问的顶点
- {
- visited[v] = 1;
- printf("%c ->", G.vertices[v].data);
- EnQueue(Q, G.vertices[v].data); //已访问顶点入队
- while(!QueueEmpty(Q)) //辅助队列非空
- {
- e = DeQueue(Q); //返回辅助队列中的头结点
- u = LocateVex(G, e);
- s = (ArcNode *) malloc (sizeof(ArcNode));
- s = G.vertices[u].firstarc;
- while(s != NULL) //*顶点e还有邻接顶点
- {
- w = s->adjvex;
- if(visited[w] == 0)
- {
- visited[w] = 1;
- printf("%c ->", G.vertices[w].data);
- EnQueue(Q, G.vertices[w].data);
- }
- s = s->nextarc;
- }
- }
- }
- printf("完成n");
- return OK;
- }
- int main()
- {
- printf("==========================n");
- printf("=n");
- ALGraph G;
- CreateUDN(G);
- printf("无向图的邻接表为:n");
- PrintAdjList(G);
- printf("广度优先遍历该无向图:n");
- BFSTraverse(G); //调用图的深度优先函数DFSTraverse()
- return 0;
- }
搜索引擎算法的额外研究:
==============================================================================
本博客已经废弃,不在维护。新博客地址:http://wenchao.ren
我喜欢程序员,他们单纯、固执、容易体会到成就感;面对压力,能够挑灯夜战不眠不休;面对困难,能够迎难而上挑战自我。他
们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想“用智慧开创属于自己的事业”。我想说的是,其
实我是一个程序员
==============================================================================
![](http://service.t.sina.com.cn/widget/qmd/1882500857/8c17d4b5/1.png)