图论——读书笔记 (深度优先搜索)

深度优先搜索是这样的,对最近才发现的结点v的所有出发边进行探索,

直到该结点的所有出发边都被发现为止。

例如上面的图,如果是按照以S点为源结点进行深度优先 遍历的话,

相应的访问顺序是:

(根据具体的应用不同,假定是以左访问优先,

而所谓的左优先指的具体是:如上图,当访问到Z结点的时候,

左优先,继续深入访问Y,而把W结点进行暂存起来)

S->Z(W相对于Z靠右,Z被压栈)

Z->Y

Y->X

X结点没有出发边,(所谓出发边指的是从当前结点出发指向其他结点的有向边)

故回溯到Y,Y除了访问过的X同样没有出发边;

回溯到Z,Z还有除了Y没被访问过的出发边指向被压进栈的W

故W从栈中弹出,访问W。

W没有出发边,故回溯到Z,Z再回溯到S。

以上描述就是对图从结点S深度优先遍历一次的大致过程。

在树中为了更形象的对这个算法进行描述,免不了多加了一些属性在其中。

其中u是图中的一个节点:

u.color:这个是用来描述当前结点u是什么颜色的,WHITE代表的是,u结点没有被访问。

BLACK代表的是,以结点u为前驱的所有结点都已经被访问到了。

而GRAY代表的是,位于二者{WHITE,BLACK}之间,即u被访问过了,但u的子结点并未被全部访问到。

u.father: 这个其实在本篇文章中意义不大,是用于标记结点u的前驱结点在图G 的Adj[]数组中的位序的,

同时也是用于标识G在跑完DFS算法的时候生成的深度优先树的。

u.startTime:结点的这个属性是用来给每个结点盖上时间戳的,

这个属性记载的是u结点第一次被发现的时间(涂上灰色的时候)

u.endTime:同上,不过这个属性记载的是在搜索完成对u结点的邻接链表扫描结束之后的时间,

该时间同样也是,访问从u出发的所有的结点之后,也是u结点被涂成黑色之后的时间。

u.name: 这个是用来记录结点的名称的;

u.firstedge:这个是邻接链表用于指向该结点后面的第一个邻接结点的指针,

通过它可以找到图中与u结点相连接的其他结点。

下面的深度优先搜索算法的伪代码将其发现结点u的时刻记录在属性u.d中,

将其完成对结点u的处理的时刻记录在u.f这一属性中。

因为|V|个结点中只能有一个发现事件和一个完成事件,

所以这些时间戳都是位于[1, 2*|V|]之间的整数。

很显然对于每个结点都有u.d < u.f

结点u在时间u.d之前为WHITE,在介于[u.d, u.f]之间为GRAY

在u.f之后为BLACK。

下面的伪代码给出的是基本的深度优先搜索算法。输入图G既可以是有向图

当然也可以是无向图,而变量为一个全局变量,用它来计算对每一个访问结点的时间戳。

 1 DFS(G)
 2     for each vertex u attibute G.V
 3            u.color = WHITE
 4            u.father = NIL
 5      time = 0
 6      
 7     for each vertec u attribute to G.V
 8             if u.color  ==  WHITE
 9                      DFS-VISIT(G,u)
10 
11 //code above is the main DFS that the cycle will check
12 //each node in Graph G, if the node is unvisited whose color is white
13 // DFS will call method:DFS-VISIT and take that node as the source node
14 
15 DFS-VISIT(G,u)
16 time = time+1
17 
18 u.d = time
19 
20 u.color = GRAY
21 
22 for each v attribute to G:Adj[u]
23   if v.color == WHITE
24       v.f = u
25       DFS-VISIT(G, v)  //continue to run
26 
27 u.color = BLACK
28 //blacken the node u after its subnodes all visited
29 
30 time = time +1
31 
32 u.f = time

 下面是使用C++代码实现上述的伪代码:

 在这里说明一下,LZ将color属性单独提出来作为Color数组,这个Color数组

其实质上与平时进行图遍历的visited数组所起到的作用是一样的,只不过,在《算法导论 第三版》

中将结点划分为访问与未访问之间,又多出了一个状态:GRAY,就是一个结点虽然被访问,

但是与它相连接的结点还没有全部被访问,这种情况下,将该结点的颜色属性置为GRAY。

 31 #include<stdio.h>
 32 #include<stdlib.h>
 33 
 34 #define MaxVertexNum 50
 35 
 36 
 37 typedef struct node
 38 {
 39     int adjvex;
 40     struct node* next;
 41 }EdgeNode;
 42 
 43 
 44 typedef struct vnode
 45 {
 46     char name;
 47     int father;
 48     int startTime;
 49     int endTime;
 50         
 51     EdgeNode *firstedge;
 52 
 53 }VertexNode, AdjList[MaxVertexNum];
 54 
 55 
 56 typedef struct
 57 {
 58     AdjList Adj;
 59     int edgeNum, vertexNum;
 60 }Graph;
 61 
 62 
 63 typedef enum {WHITE, GRAY, BLACK} color;
 64 
 65 
 66 color  Color[MaxVertexNum];
 67 
 68 int time;
 69 
 70 
 71 void InitGraph(Graph *G)
 72 {
 73     int i, j, k;
 74     
 75     EdgeNode *s;
 76         
 77     printf("Input VertexNum  and EdgeNum :  ");
 78     scanf("%d %d", &G->vertexNum, &G->edgeNum);
 79     getchar();
 80 
 81     for(i = 0 ; i < G->vertexNum; i++)
 82     {
 83             
 84 
 85             scanf("%c", &G->Adj[i].name);
 86             getchar();
 87             
88 89 G->Adj[i].firstedge = NULL; 90 91 G->Adj[i].father = -1; 92 93 Color[i] = WHITE; 94 95 } 96 97 printf("input edges, Create Adjacency List : "); 98 99 for(k = 0 ; k < G->edgeNum; k++) 100 { 101 scanf("%d %d", &i, &j); 102 103 s = (EdgeNode*)malloc(sizeof(EdgeNode)); 104 105 s->adjvex = j; 106 107 s->next = G->Adj[i].firstedge; 108 109 G->Adj[i].firstedge = s; 110 111 112 113 /* if the graph is undirected graph, 114 release the following codes 115 116 s = (EdgeNode *) malloc(sizeof(EdgeNode)); 117 118 s->adjvex = i; 119 120 s->next = G->Adj[j].firstedge; 121 122 G->Adj[j].firstedge = s; 123 */ 124 } 125 } 126 127 128 129 130 131 void DFS_VISIT(Graph *G, int i) 132 { 133 EdgeNode *p; 134 135 time = time+1; 136 137 G->Adj[i].startTime = time; 138 139 Color[i] = GRAY; 140 141 p = G->Adj[i].firstedge; 142 143 while(p) 144 { 145 if(Color[p->adjvex]==WHITE) 146 { 147 G->Adj[p->adjvex].father = i; 148 DFS_VISIT(G, p->adjvex); 149 } 150 p = p->next; 151 } 152 153 Color[i] = BLACK; 154 time++; 155 156 G->Adj[i].endTime = time; 157 } 158 159 void DFS(Graph *G) 160 { 161 int i ; 162 163 time = 0; 164 for(i = 0; i < G->vertexNum; i++) 165 if(Color[i] == WHITE) 166 DFS_VISIT(G,i); 167 } 168 169 170 void PrintVisit(Graph *G, int source) 171 { 172 EdgeNode *p; 173 174 p = G->Adj[source].firstedge; 175 176 printf("current node name : %c ", G->Adj[source].name); 177 printf("current node connect to : "); 178 179 while(p) 180 { 181 printf("node:%c ", G->Adj[p->adjvex].name); 182 p = p->next; 183 } 184 185 printf(" "); 186 187 } 188 189 190 void PrintGraph(Graph *G) 191 { 192 int i; 193 for(i = 0 ; i < G->vertexNum ; i++) 194 PrintVisit(G , i ); 195 196 } 197 198 199 200 201 int main() 202 { 203 Graph G; 204 205 InitGraph(&G); 206 207 PrintGraph(&G); 208 209 DFS(&G); 210 211 return 0; 212 213 }

根据上面所化的图进行数据的输入是这样的

边:

S Z Y W X T V U 

 点:(0,1) (0,3)  (1,2)  (1,3)  (2,4) (3,4)  (4,1)

(6,0)  (6,3) (5,6) (5,7) (7,6) (7,5)

对于点的输入是这样的,

对应的是相应的点在G->Adj数组中的序号。

在控制台输入的时候是不需要添加括号和中间逗号的,

一组两个数,以空格分隔,回车换行接着输入下一条边。

例如第一个输入的是S符号,则说明S在Adj中的位序为0

而W是第四个输入的,在Adj中的位序3

如果想表示S->W 这条有向边的话,则在边输入的时候,输入0(空格)3

所以边的输入是要根据具体的点在Adj数组中的位序而定的。 

深度搜索在本文中是以递归实现的,

如果不适用递归的话,同样亦可以通过不断地压栈,

然后不断地弹栈来实现针对某一个结点的不断深入地搜索。

当 当前结点有三个子结点需要访问的时候,

如果默认是左优先,则把靠右边的两个结点全部压栈。

然后继续访问左结点的子结点,如此下去。

一旦当某一个结点再也找不到子结点的时候,

则弹栈,继续从弹出栈的结点进行深入访问,如此继续下去。

直到所有的结点都被访问后结束。

原文地址:https://www.cnblogs.com/inuyasha1027/p/algorithm_DFS_Graph.html