dfs时间复杂度_深度优先搜索算法(递归、栈实现、复杂度分析)

本文探讨了深度优先搜索(DFS)算法,包括递归和栈实现,以及在矩阵中最长递增路径的应用。通过示例展示了DFS如何遍历图,并强调了非连通图的处理方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

预计阅读15min

《小巷》

小巷

又弯又长

没有门

没有窗

我拿把旧钥匙

敲着厚厚的墙

----------------------顾城


深度优先搜索(Depth-First Search, DFS)是一种图遍历的方法。因发明「深度优先搜索算法」,约翰 · 霍普克洛夫特与罗伯特 · 塔扬在1986年共同获得计算机领域的最高奖:图灵奖。2dda7a95e052b011fe4bbcaf5df22371.png来看看深度优先搜索干了一件什么事吧。  
 图简介   给定一幅由节点和边组成的图G(V,E),V是顶点的集合,E表示边的集合。顶点是对现实对象的一种抽象,而边表示的是两个对象之间一定的关系。b03380265f2dbaf17b729a777baceae6.png图在计算机中的存储方式有:矩阵存储(适用于稠密的图),邻接表存储(适合稀疏图)。例:无向图的矩阵
v1v2v3v4
v11101
v21101
v30010
v41101
 图又分有向图和无向图,无向图的矩阵表示是一个对称矩阵(M(i,j)=M(j,i)),有向图的矩阵则不一定对称。图还有无权图和有权图 之分。无权图的矩阵里的数值只表示是否连通,而有权图矩阵里的数值大小可表示价值、距离、代价等现实意义。
图的遍历当我们需要从图中获取信息时,我们就需要访问图的节点,用dfs的目的不唯一,可能是为了查找是否存在某个节点,可能是要数图中有多少个环,或者判断图能否一笔画等等。重点是避免重复访问也不能漏掉一些节点。图的遍历方式有深度优先遍历(DFS)广度优先遍历(BFS)两种。本文只介绍深度优先遍历。所谓DFS,通俗的理解就是,当我访问完一个节点(v1)后,我从与当前节点相邻且未访问过的节点集合(S)中拿出一个来,对选出来的这个节点(v2)我们重复与前一个节点相同的过程,直到没有满足条件的节点可以被访问,于是我们退回到v1处,从S中再选出一个未被访问过的节点进行上述过程,直到整张图的节点都被访问过。以上的描述很明显具有递归结构。我们也知道,递归算法都可以用栈来实现。DFS算法递归实现的框架如下:     
1 procedure DFS(G, V) is 
2 将顶点V标记为 “已遍历”
3 对于 V 的所有邻接点 W
4 如果 w 未被遍历过 则
5 深度优先遍历 (G,W)
我们需要一个数组来记录某个节点是否访问过。用栈来实现DFS算法栈的特点是先进后出(Fisrt in last out), 利用这 个特点,我们可以在访问当前节点之后,把它所有当前未被访问的邻点入栈,然后弹出栈顶元素,即最后一个入栈的节点。对弹出的节点标记为已访问,然后同样把他的所有当前未被访问的邻点入栈(后入栈的节点会比先入栈的节点先出来,以此达到了深度优先遍历的目的)。这样一直进行下去,直到栈空。
  1. procedure DFS-iterative(Gv) islet S be a stack          //声明s为一个栈S.push(v)                    //根节点(起始节点)入栈while S is not empty do    //栈不空时一直循环       v = S.pop()   //弹出栈顶元素赋值给v       label v as visited   //标记v为已访问

            //v的所有未被访问的邻点中入栈        for all edges from v to w in G.adjacentEdges(v) do

                     if w is unvisited S.push(w)

  2. 注意以上程序只能访问一个连通枝中的节点,若要访问一个不连通的图中的中的所有节点,只需要在该程序以外加一个大循环,从未被访问过的节点开始访问其他的连通枝即可。

复杂度分析:    DFS算法需要借助一个递归工作栈和一个标记是否访问的数组,故它的空间复杂度为O(N)。N为节点个数。遍历图的过程实质上是对每个顶点查找其邻接点的过程,其耗费的时间取决于所采用结构。邻接表表示时,查找所有顶点的邻接点所需时间为O(E),访问顶点的邻接点所花时间为O(N),此时,总的时间复杂度为O(N+E) 。邻接矩阵表示时,查找每个顶点的邻接点所需时间为O(N) ,要查找整个矩阵,故总的时间度为 O(N^2) 。
例:矩阵中的最长递增路径
难度:困难
给定一个整数矩阵,找出最长递增路径的长度。对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。 示例 1: 输入: nums =  [   [9,9,4],                            [6,6,8],                             [2,1,1] ] 输出: 4 解释: 最长递增路径为 [1, 2, 6, 9]。
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/longest-increasing-path-in-a-matrix 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。分析:   求最长路径肯定会想到DFS,但是本题若直接对每个节点进行一下DFS然后取最长的长度, 会超时。可以借助动态规划的思想--即记忆化数组。由于我们的题目要求是最长的递增路径,而从每个节点开始的最长的递增路径是一定的。我们可以在深度优先遍历的过程中记下已经搜索过的节点的最长递增路径。下一次在经过该节点时直接用就行了,不需要再搜索。计算关系式是:   在数组索引不越界的情况下:    lenth(x,y)=1+max(lenth(x-1,y),lenth(x+1,y),lenth(x,y-1),lenth(x,y+1))我们设置一个seen数组,其形状和给的矩阵形状相同,用来记录每个节点是否访问过以及其最长的递增路径长度,若seen[x][y]==0,则(x,y)没访问过,否则seen[x][y]至少为1。 程序如下:
class Solution {public:    //递归实现,在下一个程序被调用    int dfs(vector >& matrix,vector >&seen, int x, int y,int&m,int&n){          int maxv=1;          if(seen[x][y])return seen[x][y];          if((x-1)>=0&&matrix[x][y]          if((y-1)>=0&&matrix[x][y]          if((x+1)          if((y+1)          seen[x][y]=maxv;          return maxv;    }    int longestIncreasingPath(vector >& matrix) {     if(matrix.size()==0)return 0;      int m=matrix.size(), n=matrix[0].size(),maxv=1;           vector >seen(m,vector(n,0));      for(int i=0;i      {          for(int j=0;j          {   if(seen[i][j])maxv=max(maxv,seen[i][j]);//(i,j)位置已搜索过              else maxv=max(maxv,dfs(matrix,seen,i,j,m,n));//否则搜索(i,j)          }      }      return maxv;    }};

c0410ae5226c4dcf6939053b4631885f.png

79577f6a11795839461a1aa2bf44db8c.png

今天分享《人工智能的未来》

回复futur获取链接755116c41de964e43a9110924449dd70.png755116c41de964e43a9110924449dd70.png755116c41de964e43a9110924449dd70.png


69cce3691a628b2446ea1a064ba9b6f9.png

DFS算法是一种用于遍历或树遍历算法。其核心思想是从起点开始递归地深入每一个可能的分支,直到无法继续为止,然后回溯到上一个节点,继续尝试其他分支。 DFS算法有两种实现方式:递归实现非递归实现(使用)。 递归实现DFS算法伪代码: ``` DFS(node): if node is None: return visit(node) for child in node.children: DFS(child) ``` 非递归实现DFS算法伪代码: ``` DFS(node): stack = [node] while stack: node = stack.pop() if node.visited: continue visit(node) node.visited = True for child in node.children: stack.append(child) ``` 其中,visit(node)表示对节点node进行操作,例如打印节点值、记录路径等。 DFS算法时间复杂度为O(V+E),其中V为节点数,E为边数。因为每个节点和每条边都只会被访问一次。 下面是一个例题,用DFS算法求解从起点到终点的路径(leetcode 79题): 给定一个二维网格和一个单词,找出该单词是否存在于网格中。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 示例: ``` board = [ ['A','B','C','E'], ['S','F','C','S'], ['A','D','E','E'] ] 给定 word = "ABCCED", 返回 true. 给定 word = "SEE", 返回 true. 给定 word = "ABCB", 返回 false. ``` 实现代码: ```python class Solution: def exist(self, board: List[List[str]], word: str) -> bool: if not board or not board[0]: return False m, n = len(board), len(board[0]) visited = [[False] * n for _ in range(m)] directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] def dfs(i, j, k): if k == len(word): return True if i < 0 or i >= m or j < 0 or j >= n or visited[i][j] or board[i][j] != word[k]: return False visited[i][j] = True for dx, dy in directions: if dfs(i+dx, j+dy, k+1): return True visited[i][j] = False return False for i in range(m): for j in range(n): if dfs(i, j, 0): return True return False ``` 时间复杂度为O(m*n*3^k),其中m、n为网格大小,k为单词长度。因为每个格子都有可能走,所以是O(m*n),而每次调用dfs函数会向四个方向递归,每个方向都不能重复走,所以是3^k。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值