图的遍历(搜索):从图的某一顶点出发,对图中所有顶点访问一次且仅访问一次。
但是图结构具有复杂性,不像线性表和树结构的顶点是有先后次序的,在图结构中任何两个顶点之间都可能存在边,顶点是没有先后次序的,所以顶点编号不唯一。
图中可能存在回路,且图的任一顶点都可能与其他顶点“相通”,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点,那么如何避免某些顶点被重复访问?
visited[n]
。在图中,一个顶点可以和其他多个顶点相连,当这样的顶点访问过后,如何选取下一个要访问的顶点?
类似于树结构的先序遍历。
设图 G
的初态是所有顶点都 “未访问过(False)”,在 G
中任选一个顶点 v
为初始出发点(源点),则深度优先搜索可定义为:
v
,并将其标记为“访问过 (True) ”;v
出发依次考察与 v
相邻的顶点 w
:若 w
“未访问过(False)”,则以 w
为新的出发点递归地进行深度优先搜索,直到图中所有与源点 v
有路径相通的顶点(亦称从源点可到达的顶点)均被访问为止,这个过程称为从源点出发的一次先深搜索;注意到进行深度优先搜索的过程中,访问与当前节点邻接的节点的前提是该顶点没有被访问过。
深度优先搜索时间复杂度:
深度优先遍历的特点:
生成森林(树):由原图的所有顶点和搜索过程中所经过的边构成的子图。
由于深度优先搜索的结果不唯一,所以图的 DFS 序列、先深编号和生成森林不唯一。
深度优先需要无路可走时按照来路往回退,恰好是后进先出,需要用到栈(递归写法(系统栈),非递归写法(显式定义栈))。
递归写法(邻接表形式、邻接矩阵形式):
bool visited[NumVertices]; // 访问标记数组是全局变量
int dfn[NumVertices]; // 顶点的先深编号
int count = 1; // 当前访问节点个数
void DFSTraverse(AdjGraph G) // 主函数
/* 先深搜索——邻接表表示的图G;而以邻接矩阵表示G时,算法完全相同 */
{
for(int i = 0; i < G.n; i++)
visited[i] = FALSE; // 标志数组初始化
for(int i = 0; i < G.n; i++)
if(!visited[i])
DFSX(G, i);
}
void DFS1(AdjGraph* G, int i)
// 以 vi 为出发点时对邻接表表示的图 G 进行先深搜索
{
EdgeNode *p;
cout << G→vexlist[i].vertex; // 访问顶点 vi
visited[i] = TRUE; // 标记 vi 已访问
dfn[i] = count++; // 对 vi 进行编号
p = G→vexlist[i].firstedge; // 取 vi 边表的头指针
while(p) { //依次搜索 vi 的邻接点 vj, 这里j=p->adjvex
if(!visited[p→adjvex]) // 若 vj 尚未访问 *
DFS1(G, p→adjvex); // 则以 vj 为出发点先深搜索
p=p→next;
}
} //DFS1
void DFS2(MTGraph *G, int i)
// 以 vi 为出发点对邻接矩阵表示的图 G 进行深度优先搜索
{
int j;
cout << G→vexlist[i]; // 访问定点vi
visited[i] = TRUE; // 标记 vi 已访问
dfn[i] = count++; // 对 vi 进行编号
for(j=0; j
深度优先遍历的非递归写法也非常容易实现,需要自己定义栈。编写过程中应注意的问题:
visited[i]
置 True
,这样可以防止同一个节点多次入栈,重复入栈;实现框架与广度优先搜索很像,只不过将队列换成了栈,再注意何时访问节点即可。
类似于树的层序遍历。
设图 G
的初态是所有顶点都“未访问过(False)”,在 G
中任选一个顶点 v
为源点,则广度优先搜索可定义为:
v
,并将其标记为“访问过 (True)”;v
相邻的顶点 w1, w2 ...wt
;w1, w2 ...wt
相邻的所有未访问的顶点;v
有路相通的顶点都已访问过为止,该过程称为从源点出发的一次先广搜索(广度优先搜索)。v
开始的搜索结束,若 G
是连通的,则遍历完成;否则在G中另选一个尚未访问的顶点作为新源点继续上述搜索过程,直到 G
中的所有顶点均已访问为止。广度优先遍历特点:
生成森林(树):由原图的所有顶点和搜索过程中所经过的边构成的子图。
由于先广搜索结果不唯一,图的BFS序列、先广编号和生成森林不唯一。
广度优先搜索需要保证先访问的顶点的未访问邻接点要先被访问,恰好就是先进先出,需要用到队列。
时间复杂度:
bool visited[NumVertices]; // 访问标记数组是全局变量
int bfn[NumVertices]; // 顶点的先广编号
void BFSTraverse(AdjGraph G) // 主函数
/* 先深搜索——邻接表表示的图G;而以邻接矩阵表示G时,算法完全相同 */
{
int count = 1;
for(int i = 0; i < G.n; i++)
visited[i] = FALSE; // 标志数组初始化
for(int i = 0; i < G.n; i++)
if(!visited[i])
BFSX(G, i);
}
void BFS1(AdjGraph *G, int i)//这里没有进行先广编号
{
int j;
EdgeNode *p; QUEUE Q; MAKENULL(Q);
cout << G→vexlist[i].vertex; // 访问顶点 vi
visited[i] = TRUE; // 标记 vi 已访问
bfn[i] = count++;
ENQUEUE(i, Q); // 进队列
while(!Empty(Q)) { // 队空搜索结束
j = DEQUEUE(Q); // vj 出队,在这之前 vj 已经被访问过了
p =G→vexlist[j].firstedge; // 取vj 的边表头指针
while(p) { // 若 vj 的邻接点 vk(k=p→adjvex) 存在,依次搜索
if (!visited[p→adjvex]) { // 若 vk 未访问过
cout << G→vexlist[p→adjvex].vertex; // 访问 vk
visited[p→adjvex] = TRUE; // 给 vk 作访问过标记
bfn[p->adjvex] = count++;
ENQUEUE(p→adjvex , Q ); //访问过的 vk 入队
}
p = p→next; // 找 vj 的下一个邻接点
} // 重复检测 vj 的所有邻接顶点
} // 外层循环,判队列空否
} // 以 vi 为出发点时对用邻接表表示的图 G 进行先广搜索
void BFS2(MTGraph *G, int i)
{
int j, k;
QUEUE Q; MAKENULL(Q);
cout << G→vexlist[i]; // 访问 vi
visited[i] = TRUE; // 给 vi 作访问过标记
ENQUEUE(i, Q); // vi 进队列
while(!Empty(Q) ) { // 队空时搜索结束
i=j = DEQUEUE(Q); // vj 出队
for(k=0; k
广度优先搜索实现时:
True
,防止队列中进入重复节点,这点与深度优先搜索的非递归实现相同;