深度优先遍历(Depth First Search, 简称 DFS)

正文开始:

深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First Search)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在 leetcode,高频面试题中。

前言

深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First Search)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在 leetcode,高频面试题中。

本文将会从以下几个方面来讲述深度优先遍历,广度优先遍历,相信大家看了肯定会有收获。

深度优先遍历

  • 深度优先遍历简介
  • 习题演练
  • DFS,BFS 在搜索引擎中的应用
深度优先遍历简历

主要思路是从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底…,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。

树是图的一种特例(连通无环的图就是树),接下来我们来看看树用深度优先遍历该怎么遍历。

在这里插入图片描述

1、我们从根节点 1 开始遍历,它相邻的节点有 2,3,4,先遍历节点 2,再遍历 2 的子节点 5,然后再遍历 5 的子节点 9。

在这里插入图片描述

2、上图中一条路已经走到底了(9是叶子节点,再无可遍历的节点),此时就从 9 回退到上一个节点 5,看下节点 5 是否还有除 9 以外的节点,没有继续回退到 2,2 也没有除 5 以外的节点,回退到 1,1 有除 2 以外的节点 3,所以从节点 3 开始进行深度优先遍历,如下:
在这里插入图片描述

3、同理从 10 开始往上回溯到 6, 6 没有除 10 以外的子节点,再往上回溯,发现 3 有除 6 以外的子点 7,所以此时会遍历 7。

在这里插入图片描述

4、从 7 往上回溯到 3, 1,发现 1 还有节点 4 未遍历,所以此时沿着 4, 8 进行遍历,这样就遍历完成了。

完整的节点的遍历顺序如下(节点上的的蓝色数字代表):

在这里插入图片描述

相信大家看到以上的遍历不难发现这就是树的前序遍历,实际上不管是前序遍历,还是中序遍历,亦或是后序遍历,都属于深度优先遍历。

那么深度优先遍历该怎么实现呢,有递归和非递归两种表现形式,接下来我们以二叉树为例来看下如何分别用递归和非递归来实现深度优先遍历。

1、递归实现

递归实现比较简单,由于是前序遍历,所以我们依次遍历当前节点,左节点,右节点即可,对于左右节点来说,依次遍历它们的左右节点即可,依此不断递归下去,直到叶节点(递归终止条件),代码如下:

public class Solution { 
    private static class Node { 
        /** 
         * 节点值 
         */ 
        public int value; 
        /** 
         * 左节点 
         */ 
        public Node left; 
        /** 
         * 右节点 
         */ 
        public Node right; 
 
        public Node(int value, Node left, Node right) { 
            this.value = value; 
            this.left = left; 
            this.right = right; 
        } 
    } 
 
    public static void dfs(Node treeNode) { 
        if (treeNode == null) { 
            return; 
        } 
        // 遍历节点 
        process(treeNode) 
        // 遍历左节点 
        dfs(treeNode.left); 
        // 遍历右节点 
        dfs(treeNode.right); 
    } 
} 

递归的表达性很好,也很容易理解,不过如果层级过深,很容易导致栈溢出。所以我们重点看下非递归实现。

2、非递归实现

仔细观察深度优先遍历的特点,对二叉树来说,由于是先序遍历(先遍历当前节点,再遍历左节点,再遍历右节点),所以我们有如下思路:

对于每个节点来说,先遍历当前节点,然后把右节点压栈,再压左节点(这样弹栈的时候会先拿到左节点遍历,符合深度优先遍历要求)。

弹栈,拿到栈顶的节点,如果节点不为空,重复步骤 1, 如果为空,结束遍历。

我们以以下二叉树为例来看下如何用栈来实现 DFS。
在这里插入图片描述
整体动图如下:
在这里插入图片描述

整体思路还是比较清晰的,使用栈来将要遍历的节点压栈,然后出栈后检查此节点是否还有未遍历的节点,有的话压栈,没有的话不断回溯(出栈),有了思路,不难写出如下用栈实现的二叉树的深度优先遍历代码:

/** 
 * 使用栈来实现 dfs 
 * @param root 
 */ 
public static void dfsWithStack(Node root) { 
    if (root == null) { 
        return; 
    } 
 
    Stack<Node> stack = new Stack<>(); 
    // 先把根节点压栈 
    stack.push(root); 
    while (!stack.isEmpty()) { 
        Node treeNode = stack.pop(); 
        // 遍历节点 
        process(treeNode) 
 
        // 先压右节点 
        if (treeNode.right != null) { 
            stack.push(treeNode.right); 
        } 
 
        // 再压左节点 
        if (treeNode.left != null) { 
            stack.push(treeNode.left); 
        } 
    } 
} 


可以看到用栈实现深度优先遍历其实代码也不复杂,而且也不用担心递归那样层级过深导致的栈溢出问题。

结尾

这就是我理解的深度优先遍历算法啦,如果有不太对的地方,欢迎指出,谢谢。

  • 39
    点赞
  • 121
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
#include<stdio.h> #include<stdlib.h> #include<conio.h> #include<math.h> #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define OVERFLOW -2 #define NULL 0 #define MAX 20 typedef int Status; typedef struct Node {int elem; struct Node *next; }Node,*QNode; typedef struct {QNode front; QNode rear; }Queue; typedef struct ArcNode /*头节点*/ {int adjvex; /*该边所指向的顶点的位置*/ struct ArcNode *nextarc; /*指向下一条边*/ }ArcNode; typedef struct VNode /*表节点*/ {int data; /*顶点信息*/ ArcNode *firstarc; /*指向第一条依附该节点的边的指针*/ }VNode,AdjList[MAX]; typedef struct {AdjList vertices; /*表节点*/ int vexnum; /*节点的个数*/ int arcnum; /*边的条数*/ }Graph; Status InitQueue(Queue *Q) {Q->front=Q->rear=(QNode)malloc(sizeof(Node)); if(!Q->front) exit(OVERFLOW); Q->front->next=NULL; return OK; } Status EnQueue(Queue *Q,int e) {QNode p=(QNode)malloc(sizeof(Node)); if(!p) exit(OVERFLOW); p->elem=e; p->next=NULL; Q->rear->next=p; Q->rear=p; return OK; } Status DeQueue(Queue *Q,int *e) {QNode p; p=Q->front->next; Q->front->next=p->next; if(Q->rear==p) Q->rear=Q->front; *e=p->elem; free(p); return OK; } Status QueueEmpty(Queue Q) {if(Q.rear==Q.front) return TRUE; else return FALSE; } int LocateVex(Graph *G,int v) /*返回节点v在图中的位置*/ {int i; for(i=0;i<G->vexnum;++i) if(G->vertices[i].data==v) break; else continue; if(i<G->vexnum) return i; else return -1; } Status CreateGraph(Graph *G)/*以邻接表形式创建无向连通图G*/ {int m,n,i,j,k,v1,v2,flag=0; ArcNode *p1,*q1,*p,*q; printf("Please input the number of VNode: "); scanf("%d",&m); printf("Please input the number of ArcNode: "); scanf("%d",&n); G->vexnum=m; /*顶点数目*/ G->arcnum=n; /*边的数目*/ for(i=0;i<G->vexnum;++i) {G->vertices[i].data=i+1; /*顶点信息*/ G->vertices[i].firstarc=NULL; } printf("Output the message of VNode:\n"); for(i=0;i<G->vexnum;++i) printf("v%d\n",G->vertices[i].data); for(k=0;k<G->arcnum;++k) {printf("Please input the %d edge beginpoint and endpoint: ",k+1); scanf("%d%d",&v1;,&v2;); i=LocateVex(G,v1); j=LocateVex(G,v2); if(i>=0&&j>=0) {++flag; p=(ArcNode *)malloc(sizeof(ArcNode)); p->adjvex=j; p->nextarc=NULL; if(!G->vertices[i].firstarc) G->vertices[i].firstarc=p; else{for(p1=G->vertices[i].firstarc;p1->nextarc;p1=p1->nextarc); p1->nextarc=p; } q=(ArcNode *)malloc(sizeof(ArcNode)); q->adjvex=i; q->nextarc=NULL; if(!G->vertices[j].firstarc) G->vertices[j].firstarc=q; else{for(q1=G->vertices[j].firstarc;q1->nextarc;q1=q1->nextarc); q1->nextarc=q; } } else{printf("Not hava this edge!\n"); k=flag; } } printf("The Adjacency List is:\n"); /*输出邻接表*/ for(i=0;i<G->vexnum;++i) {printf("\t%d v%d->",i,G->vertices[i].data); p=G->vertices[i].firstarc; while(p->nextarc) {printf("%d->",p->adjvex); p=p->nextarc; } printf("%d\n",p->adjvex); } return OK; } int FirstAdjVex(Graph G,int v)/*返回v的第一个邻接顶点*/ {if(G.vertices[v].firstarc) return G.vertices[v].firstarc->adjvex; else return -1; }/*FirstAdjVex*/ int NextAdjVex(Graph G,int v,int w)/*返回v中相对于w的下一个邻接顶点*/ {int flag=0; ArcNode *p; p=G.vertices[v].firstarc; while(p) { if(p->adjvex==w) { flag=1; break; } p=p->nextarc; } if(flag && p->nextarc) return p->nextarc->adjvex; else return -1; }/*NextAdjVex*/ int Visited[MAX]; void DFS(Graph G,int v)/*深度优先遍历*/ {int w; Visited[v]=TRUE; printf("v%d ",G.vertices[v].data); for(w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w)) if(!Visited[w]) DFS(G,w); } void DFSTraverse(Graph G) {int v; for(v=0;v<G.vexnum;++v) Visited[v]=FALSE; for(v=0;v<G.vexnum;++v) if(!Visited[v]) DFS(G,v); } void BFSTraverse(Graph G)/*广度优先遍历*/ {int v,v1,w; Queue q; for(v=0;v<G.vexnum;++v) Visited[v]=FALSE; InitQueue(&q); for(v=0;v<G.vexnum;++v) if(!Visited[v]) {Visited[v]=TRUE; printf("v%d ",G.vertices[v].data); EnQueue(&q,v); /*第一个顶点入队*/ while(!QueueEmpty(q)) {DeQueue(&q,&v1;); /*第一个顶点出对*/ for(w=FirstAdjVex(G,v1);w>=0;w=NextAdjVex(G,v1,w)) if(!Visited[w]){Visited[w]=TRUE; printf("v%d ",G.vertices[w].data); EnQueue(&q,w); }/*if*/ } } } main() {Graph G; char ch; system("cls"); CreateGraph(&G); while(1) {printf("which one choice you want DFS(1) BFS(2) orQuit(0)?\n"); scanf("%d",&ch;); switch(ch) {case 1:printf("Depth First Search:\n"); DFSTraverse(G); break; case 2:printf("\nBreadth First Search:\n"); BFSTraverse(G); break; case 0:exit(0); } } getch(); }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值