【数据结构与算法】深度优先搜索:树与图的路径探寻之道
一、引言
在计算机科学领域,树与图的路径搜索是一个基础且重要的问题,而深度优先搜索算法(Depth First Search,简称 DFS)则是解决此类问题的经典算法之一。深度优先搜索算法通过从起始节点开始,沿着一条路径尽可能深地探索,直到无法继续或达到目标节点,然后回溯到前一步,继续探索其他路径,这种策略在处理树与图的结构时具有独特的优势。它能够遍历树或图中的所有节点,帮助我们寻找特定的节点、路径,判断连通性,进行拓扑排序等,在众多实际应用场景中发挥着关键作用,例如网络路由规划、社交网络分析、游戏地图搜索等。因此,深入理解和掌握深度优先搜索算法对于算法学习和实际编程应用都具有极高的价值。本文将详细探讨深度优先搜索算法在树与图路径搜索中的原理、实现方式以及应用实例,旨在帮助读者更好地理解和运用这一重要算法。
二、深度优先搜索算法概述
深度优先搜索算法(Depth First Search,简称 DFS)是一种沿着树或图的深度遍历节点的算法。其核心思想是从起始节点开始,沿着一条路径尽可能深地探索,直到无法继续或者达到目标节点,若遇到阻碍则进行回溯,然后再尝试其他路径继续探索。它可以通过递归或者迭代(借助栈)的方式来实现,其中回溯与剪枝是该算法的重要核心思想,回溯能够让算法在遇到死胡同后回到之前的节点继续探索其他分支,剪枝则可根据特定条件去除不必要的搜索分支,提高算法效率。
2.1 树的结构
树是一种非线性的数据结构,它由节点(Node)和边(Edge)组成,具有层级关系。树有一个根节点(Root Node),其余节点通过边与根节点相连,每个节点可以有零个或多个子节点,但除根节点外每个节点有且仅有一个父节点,且不存在环。在 Python 中,我们可以通过类来实现树的结构,例如下面是一个简单的树节点类的示例:
class TreeNode:def __init__(self, x):self.val = xself.left = Noneself.right = Noneclass Tree:def __init__(self):self.root = Nonedef add(self, item):node = TreeNode(item)if self.root == None:# tree为空的情况,直接赋值给rootself.root = nodereturnqueue = [self.root]# tree不为空的情况,根据root逐步去找插入位置while queue:cur_node = queue.pop(0)if cur_node.left == None:# 左孩子为空则直接赋值,否则存储左节点,下一层再弹出cur_node.left = nodereturnelse:queue.append(cur_node.left)if cur_node.right == None:cur_node.right = nodereturnelse:queue.append(cur_node.right)
通过上述代码,我们可以构建出树的数据结构,方便后续进行深度优先搜索等相关操作。
2.2 图的结构
图同样是一种非线性的数据结构,用于描述对象之间的关系,由顶点(Vertex)和边(Edge)组成。在图中,顶点之间通过边相连,边可以是有向的(有方向,从一个顶点指向另一个顶点)也可以是无向的(两个顶点之间双向可达),并且边可以有权重(表示连接的代价等信息)。
在 Python 中,常见的图结构实现方式有邻接矩阵和邻接表两种:
邻接矩阵实现:
class Graph:def __init__(self, mat, unconn=0):vnum = len(mat)for x in mat:if len(x)!= vnum:raise ValueError("参数错误")self._mat = [mat[i][:] for i in range(vnum)] # 做拷贝self._unconn = unconnself._vnum = vnum # 顶点个数def vertex_num(self):return self._vnumdef _invalid(self, v):return v < 0 or v >= self._vnumdef add_edge(self, vi, vj, val=1):if self._invalid(vi) or self._invalid(vj):raise ValueError(str(vi) + " or " + str(vj) + "不是有效的顶点")self._mat[vi][vj] = valdef get_edge(self, vi, vj):if self._invalid(vi) or self._invalid(vj):raise ValueError(str(vi) + " or " + str(vj) + "不是有效的顶点")return self._mat[vi][vj]def out_edges(self, vi):if self._invalid(vi):raise ValueError(str(vi) + "不是有效的顶点")return self._out_edges(self._mat[vi], self._unconn)@staticmethoddef _out_edges(row, unconn):edges = []for i in range(len(row)):if row[i]!= unconn:edges.append((i, row[i]))return edgesdef __str__(self):return "[\n" + ",\n".join(map(str, self._mat)) + "\n]" + "\nUnconnected: " + str(self._unconn)
邻接表实现:
class GraphAL(Graph):def __init__(self, mat=[], unconn=0):vnum = len(mat)for x in mat:if len(x)!= vnum:raise ValueError("参数错误")self._mat = [Graph._out_edges(mat[i], unconn) for i in range(vnum)]self._unconn = unconnself._vnum = vnumdef add_vertex(self):self._mat.append([])self._vnum += 1return self._vnum - 1def add_edge(self, vi, vj, val=1):if self._vnum == 0:raise ValueError("不能向空图添加边")if self._invalid(vi) or self._invalid(vj):raise ValueError(str(vi) + " or " + str(vj) + "不是有效的顶点")row = self._mat[vi]i = 0while i < len(row):if row[i][0] == vj:self._mat[vi][i] = (vj, val) # 如果原来有到vj的边,修改mat[vi][vj]的值returnif row[i][0] > vj: # 原来没有到vj的边,退出循环后加入边breaki += 1self._mat[vi].insert(i, (vj, val))def get_edge(self, vi, vj):if self._invalid(vi) or self._invalid(vj):raise ValueError(str(vi) + " or " + str(vj) + "不是有效的顶点")for i, val in self._mat[vi]:if i == vj:return valreturn self._unconndef out_edges(self, vi):if self._invalid(vi):raise ValueError(str(vi) + "不是有效的顶点")return self._mat[vi]
这两种实现方式各有优劣,邻接矩阵适用于顶点数量相对较少且边比较密集的图,而邻接表在顶点较多、边相对稀疏的情况下更节省空间且插入顶点操作比较简单。
2.3 图转化为树
将图转化为树的过程通常可以基于图的连通性和生成树的概念来实现。比如,对于一个连通图,我们可以通过深度优先搜索或者广度优先搜索来生成其生成树。以深度优先搜索为例,基本的算法过程如下:
- 选择图中的一个起始顶点作为根节点。
- 从根节点开始进行深度优先搜索,每次访问到一个新的顶点时,记录下从根节点到该顶点的边,这些边构成了生成树的边集。
- 继续深度优先搜索,直到图中的所有顶点都被访问过,最终得到的由这些边和所有顶点组成的子图就是一棵生成树。
以下是一个简单的 Python 示例代码(这里假设图的存储结构为邻接表,并且已经有了图类 GraphAL 以及深度优先搜索的相关函数 dfs,下面重点展示生成树构建部分):
def graph_to_tree(graph, start_vertex):tree_edges = []visited = set()def dfs_construct(vertex):visited.add(vertex)for neighbor, _ in graph.out_edges(vertex):if neighbor not in visited:tree_edges.append((vertex, neighbor))dfs_construct(neighbor)dfs_construct(start_vertex)return tree_edges
在上述代码中,graph_to_tree 函数接受一个图对象和起始顶点,通过深度优先搜索的方式记录下构成生成树的边,最终返回这些边的列表,这些边就可以用来表示转化后的树结构(当然,实际应用中可能还需要进一步处理来构建完整的树类等结构)。
2.4 树的深度遍历
树的深度遍历遵循深度优先搜索的原则,从根节点开始,沿着一条路径尽可能深地访问节点,直到无法继续(即到达叶节点),然后回溯到上一个未完全探索的节点,继续探索其他分支。其原理可以这样理解:
例如有一棵二叉树,首先访问根节点,然后递归地对左子树进行深度遍历,当左子树遍历完(也就是到达最左下方的叶节点)后,再递归地对右子树进行深度遍历。整个过程就像沿着树的深度一直往下钻,钻到底后再回头找其他路径继续钻。
以下用图形示例展示二叉树的深度遍历过程(假设二叉树结构如下,数字表示节点值):
1/ \2 3/ \ / \4 5 6 7
从根节点 1 开始,首先访问 1,然后沿着左子树深入,访问 2,接着访问 2 的左子节点 4,此时 4 是叶节点,无法继续深入,于是回溯到 2,再访问 2 的右子节点 5,同样 5 遍历完后,左子树遍历结束,回溯到根节点 1,接着开始遍历右子树,访问 3,然后 3 的左子节点 6,再是 6 的右子节点 7。整个遍历顺序就是 1 -> 2 -> 4 -> 5 -> 3 -> 6 -> 7(这里以先序遍历为例,中序、后序遍历只是访问节点的时机稍有不同,但都是深度优先遍历的不同形式)。
代码实现(以递归方式为例):
def dfs_tree(root):if root is None:returnprint(root.val) # 访问节点,可以根据实际需求修改这里的操作,比如存储节点值等dfs_tree(root.left)dfs_tree(root.right)
通过上述代码,可以实现对树的深度遍历,递归函数会自动处理回溯的过程,当一个子树的所有节点都遍历完后,就会回到上一层继续其他分支的遍历。
2.5 图的深度遍历
图的深度遍历同样基于深度优先搜索的思想,从图中的某个起始顶点出发,沿着一条路径尽可能深地访问顶点,当遇到没有未访问的邻接顶点时就进行回溯,然后再尝试其他路径继续探索,直到图中的所有顶点都被访问过。
例如有如下一个无向图(顶点用字母表示,边连接表示相邻关系):
A -- B| |C -- D
假设从顶点 A 开始进行深度遍历,首先访问 A,然后选择 A 的一个邻接顶点,比如 B,接着访问 B,再从 B 出发选择其未访问的邻接顶点(假设先选 D),访问 D,此时 D 的邻接顶点中只有 C 未访问,访问 C,C 的邻接顶点都已访问过了,就回溯到 D,发现 D 也没有其他未访问邻接顶点了,继续回溯到 B,再看 B 是否还有其他未访问邻接顶点(这里没有了),最后回溯到 A,此时发现所有顶点都已访问,遍历结束。整个遍历顺序就是 A -> B -> D -> C(遍历顺序可能因选择邻接顶点的顺序不同而有所变化)。
在 Python 中,使用递归方式实现图的深度遍历代码示例如下(假设图的存储结构为邻接表,并且图类为 GraphAL):
def dfs_graph(graph, start_vertex, visited=set()):visited.add(start_vertex)print(start_vertex) # 访问顶点,可按需修改操作for neighbor, _ in graph.out_edges(start_vertex):if neighbor not in visited:dfs_graph(graph, neighbor, visited)
通过上述代码,传入图对象和起始顶点,就可以实现对图的深度遍历,其中 visited 集合用于记录已经访问过的顶点,避免重复访问。 非递归方式(借助栈实现)示例如下:
def dfs_graph_stack(graph, start_vertex):visited = set()stack = [start_vertex]while stack:vertex = stack.pop()if vertex not in visited:visited.add(vertex)print(vertex) # 访问顶点,可按需修改操作neighbors = graph.out_edges(vertex)for neighbor, _ in reversed(neighbors): # 逆序入栈,保证先访问先入栈的邻接顶点,符合深度优先顺序if neighbor not in visited:stack.append(neighbor)
这两种实现方式都能完成图的深度遍历操作,递归方式代码简洁但可能存在栈溢出风险(对于深度很大的图),而借助栈的非递归方式可以更好地控制栈空间的使用情况。
三、算法流程解析
深度优先搜索算法的具体流程如下:
1.选择起始节点:首先确定从哪个节点开始进行搜索,将其作为起始节点。
2.标记起始节点为已访问:使用一个数据结构(如数组、集合或哈希表)来记录节点的访问状态,将起始节点标记为已访问,以避免重复访问。
3.探索相邻节点:从起始节点开始,检查其所有相邻的节点。对于每个未被访问的相邻节点,执行以下操作:
标记为已访问:将该相邻节点标记为已访问。
递归探索或入栈:如果采用递归方式,则递归地对该相邻节点调用深度优先搜索算法;如果使用迭代方式(借助栈),则将该相邻节点入栈,以便后续继续探索。
4.回溯:当当前节点的所有相邻节点都已被访问完毕,或者没有未访问的相邻节点时,就需要进行回溯。在递归实现中,函数会自动返回到上一层调用;在迭代实现中,将当前节点从栈中弹出,回到上一个节点,继续探索其其他未被访问的相邻节点。
5.继续搜索或结束:重复步骤 3 和4,直到遍历完整个图或树(即所有节点都已被访问),或者找到了目标节点(如果有特定的搜索目标)。如果找到了目标节点,可以根据需要进行相应的处理,如记录路径、返回结果等。
以下是一个简单的示例来说明深度优先搜索算法在图中的应用流程:
假设有一个无向图,由以下节点和边组成:
A —— B —— C| | |D —— E —— F
我们从节点 A 开始进行深度优先搜索:
从A开始进行深度优先搜索(DFS)遍历,我们可以按照以下步骤进行:
- 从A开始。
- 选择一个与A相邻且未访问过的节点进行访问。这里可以选择B或D。我们先选择B。
- 从B出发,选择一个与B相邻且未访问过的节点。这里有C和E两个选择。我们先选择C。
- 从C出发,因为C只与B和F相邻,而B已经访问过,所以接下来访问F。
- 从F出发,F与C和E相邻,但是C已经访问过,因此访问E。
- 从E出发,E与B、D、F相邻,但此时只有D还未被访问(假设我们是从B到C再到F到E的顺序走过来的),所以接下来访问D。
- 在D附近的节点只有A和E,但是A和E都已经被访问过,则回溯到E。
- 回溯到E之后,E相邻的节点B、D、F均已访问过,则回溯到F。
- 回溯到F之后,F相邻的节点C、E均已访问过,则回溯到C。
- 回溯到C之后,C相邻的节点B、F均已访问过,则回溯到B。
- 回溯到B之后,B相邻的节点A、C、E均已访问过,则回溯到A。
- 最后回溯到A,但是A周围的所有节点B、D都已经访问过了,且A为根节点。此时图中所有的节点都已被访问,搜索结束。
因此,一个可能的DFS遍历路径为:A -> B -> C -> F -> E -> D。如果在搜索过程中有特定的目标节点,比如要找到节点 F,则在搜索到 F 时就可以停止搜索并进行相应的处理。
假设有另外一个无向图,由以下节点和边组成:
A —— B —— C| | |D —— E F
依旧从A为起点进行深度优先探索:
- 从A开始。
- 访问与A相邻且未访问过的节点。可以选择B或D。我们先选择B。
- 从B出发,访问与B相邻且未访问过的节点,可以选择C或E,我们先选择C。
- 从C出发,访问与C相邻且未访问过的节点F。
- 因为F周围没有未访问的邻居,所以回溯到C。
- 同理,C附近也没有相邻且未访问过的节点,再回溯到B。
- 此B出发,访问与B相邻且未访问过的节点E。
- 此E出发,访问与E相邻且未访问过的节点D。
- D周围没有未被访问过的邻居,则回溯到E。
- E周围没有未被访问过的邻居,则回溯到B。
- B周围没有未被访问过的邻居,则回溯到A。但是A周围的所有节点B、D都已经访问过了,且A为根节点。此时图中所有的节点都已被访问,搜索结束。
因此,一个可能的DFS遍历路径为:A -> B -> C -> F -> E -> D。如果在搜索过程中有特定的目标节点,比如要找到节点 F,则在搜索到 F 时就可以停止搜索并进行相应的处理。
小结: 虽然两次DFS搜索的路径相同,同时需要注意的是,他们产生的拓扑结构是不一样的。
四、代码实现示例
4.1 递归实现
以下是使用递归方式实现深度优先搜索算法对树进行遍历的 Python 代码示例:
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightdef dfs_recursive(root):if root is None:returnprint(root.val) # 访问节点,可以根据实际需求修改这里的操作,比如存储节点值等dfs_recursive(root.left)dfs_recursive(root.right)# 构建一个简单的树示例
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)dfs_recursive(root)
在上述代码中,dfs_recursive函数接受树的根节点作为参数。如果根节点为空,则直接返回。否则,先访问根节点(这里是打印节点值),然后递归地对左子树和右子树分别调用dfs_recursive函数,从而实现深度优先搜索遍历。
对于图的递归深度优先搜索,代码示例如下:
def dfs_graph_recursive(graph, node, visited):if node not in visited:print(node) # 访问节点visited.add(node)for neighbor in graph[node]:dfs_graph_recursive(graph, neighbor, visited)# 示例图,使用邻接表表示
graph = {'A': ['B', 'C'],'B': ['D', 'E'],'C': ['F'],'D': [],'E': ['F'],'F': []}
visited = set()
dfs_graph_recursive(graph, 'A', visited)
这里dfs_graph_recursive函数接受图(用邻接表表示)、起始节点和已访问节点集合作为参数。如果当前节点未被访问,则访问该节点并标记为已访问,然后递归地对其每个未访问的邻居节点调用自身。
4.2 迭代实现(使用栈)
def dfs_tree_iterative(root):if root is None:returnstack = [root]while stack:node = stack.pop()print(node.val) # 访问节点# 先将右子节点入栈,再将左子节点入栈,这样左子节点会先被访问if node.right:stack.append(node.right)if node.left:stack.append(node.left)# 同样使用之前构建的树示例
dfs_tree_iterative(root)
在这个迭代实现中,首先将根节点入栈。然后在循环中,每次从栈顶取出一个节点进行访问。接着将该节点的右子节点和左子节点依次入栈(顺序很重要,这样能保证先访问左子树),直到栈为空,完成遍历。
图的深度优先搜索迭代实现(使用栈)示例:
def dfs_graph_iterative(graph, start_node):visited = set()stack = [start_node]while stack:node = stack.pop()if node not in visited:print(node) # 访问节点visited.add(node)# 将邻居节点逆序入栈,保证先访问先入栈的邻接顶点,符合深度优先顺序for neighbor in reversed(graph[node]):if neighbor not in visited:stack.append(neighbor)# 还是使用之前的示例图
dfs_graph_iterative(graph, 'A')
此代码中,先将起始节点入栈,并使用visited集合记录已访问节点。在循环里,弹出栈顶节点,如果未被访问则访问并标记,然后将其未访问的邻居节点逆序入栈,以便后续按照深度优先的顺序进行探索。
五、算法复杂度分析
5.1 时间复杂度
在最坏情况下,深度优先搜索算法需要遍历图或树中的所有节点和边,因此时间复杂度与节点数 和边数 相关,为 。例如,在一个完全图中,每个节点都与其他所有节点相连,此时边数 ,时间复杂度接近 。但在一些稀疏图或树结构中,边数相对较少,深度优先搜索算法的时间复杂度可能相对较低,相比其他一些需要对所有节点进行多次重复访问或计算的算法,具有一定的优势。然而,如果图或树的结构非常复杂,存在大量的分支和回溯,或者在搜索过程中陷入了无限循环(例如图中存在环且搜索策略不当),则可能会导致算法耗时较长,时间复杂度难以准确估计。
5.2 空间复杂度
深度优先搜索算法的空间复杂度相对较低,主要是因为它只需要存储当前路径上的节点信息以及用于标记节点访问状态的数据结构。在递归实现中,递归调用栈的深度最多为树的高度 ,因此空间复杂度为 。对于迭代实现(使用栈),栈中存储的节点数量也不会超过树的高度或图中最长路径的长度。但需要注意的是,如果树的深度非常大,递归实现可能会因为系统栈空间有限而导致栈溢出错误。在这种情况下,可以考虑使用迭代实现或者对算法进行优化,如采用尾递归优化等方式来减少栈空间的使用。
六、DFS的优势与局限
6.1 优势
- 空间开销小:与宽度优先搜索相比,深度优先搜索通常需要的空间较少,因为它使用递归栈(或显式栈)来存储待访问的节点,而不需要额外的队列来存储所有待处理的节点。在处理大规模数据时,深度优先搜索能够更有效地利用系统资源,减少内存占用。
- 适合解决某些问题:深度优先搜索特别适合解决某些类型的问题,如寻找解的存在性、遍历树或图中的所有节点等。例如,在判断一个图是否连通时,只需从任意一个节点开始进行深度优先搜索,如果能够访问到所有节点,则说明该图是连通的;在遍历树或图的所有节点时,深度优先搜索能够沿着一条路径尽可能深地探索,直到无法继续或达到目标节点,然后回溯到前一步,继续探索其他路径,确保每个节点都被访问到。
- 简单直观:深度优先搜索的思想较为简单直观,容易理解和实现。其递归实现方式代码简洁,逻辑清晰,能够快速构建出搜索算法的基本框架。对于初学者来说,深度优先搜索是学习图和树遍历算法的一个很好的入门选择,也便于在实际应用中快速开发和调试。
6.2 局限
- 难以找到最优解:由于深度优先搜索总是尽可能深地搜索图,它可能无法快速找到最短路径或最优解。例如在一个有权图中寻找从起点到终点的最短路径时,深度优先搜索可能会先沿着一条较长的路径深入搜索,而忽略了其他更短的路径。这是因为深度优先搜索主要关注的是节点的深度探索,而不是路径的长度或代价。
- 递归实现可能导致栈溢出:如果图的结构非常深,递归实现的深度优先搜索可能会因为调用栈过深而导致栈溢出错误。在递归过程中,每深入一层,系统就会在栈中保存当前的函数调用信息,当图的深度过大时,栈空间可能会被耗尽。例如,在处理一个具有大量层级的树结构时,如果使用递归的深度优先搜索,就容易出现栈溢出的问题。
- 不保证搜索顺序:深度优先搜索的搜索顺序取决于图的存储结构和节点的访问顺序,因此不保证总是按照相同的顺序遍历图。这可能会导致在某些情况下,搜索结果的不确定性。例如,对于同一个图,不同的存储方式或不同的起始节点选择,可能会得到不同的深度优先搜索遍历顺序,这在一些对搜索顺序有严格要求的应用场景中可能会带来问题。
七、应用场景举例
7.1 图的连通性判断
在无向图中,判断图的连通性是深度优先搜索算法的一个常见应用。例如,我们有一个社交网络的无向图,其中每个节点代表一个用户,边代表用户之间的好友关系。要判断这个社交网络是否是连通的,即是否任意两个用户之间都存在一条路径,可以使用深度优先搜索算法。从任意一个用户节点开始进行深度优先搜索,标记已访问的节点。如果在搜索过程中能够访问到图中的所有节点,那么说明这个社交网络是连通的;反之,如果存在未被访问到的节点,则说明图是非连通的。
以下是一个简单的示例代码:
def is_connected(graph):start = next(iter(graph)) # 选择图中的任意一个起始节点visited = dfs_recursive(graph, start)return len(visited) == len(graph)# 示例图
graph = {'A': ['B', 'C'],'B': ['A', 'D', 'E'],'C': ['A', 'F'],'D': ['B'],'E': ['B'],'F': ['C']}
print(is_connected(graph))
在上述代码中,is_connected 函数首先选择图中的一个起始节点,然后调用前面定义的 dfs_recursive 函数进行深度优先搜索,并将访问过的节点存储在 visited 集合中。最后,通过比较 visited 集合的长度和图中节点的总数,如果相等则说明图是连通的,否则是非连通的。
7.2 拓扑排序
拓扑排序是将有向无环图(DAG)中的节点排序,使得对于每一条有向边 (u, v),节点 u 在节点 v 之前。深度优先搜索是实现拓扑排序的常用方法之一。例如,在一个项目管理系统中,存在多个任务,某些任务之间存在依赖关系,即一个任务必须在另一个任务完成之后才能开始。这些任务和依赖关系可以用有向无环图来表示,其中节点表示任务,有向边表示任务之间的依赖关系。通过拓扑排序,可以确定任务的执行顺序,使得所有的依赖关系都得到满足。
以下是一个使用深度优先搜索实现拓扑排序的示例代码:
def topological_sort(graph):visited = set()stack = []def dfs(node):visited.add(node)for neighbor in graph[node]:if neighbor not in visited:dfs(neighbor)stack.append(node)for node in graph:if node not in visited:dfs(node)return stack[::-1] # 逆序返回拓扑排序的结果# 示例有向无环图
dag = {'A': ['C'],'B': ['C', 'D'],'C': ['E'],'D': ['F'],'E': ['F'],'F': []}
print(topological_sort(dag))
在上述代码中,topological_sort 函数首先初始化一个空的访问集合 visited 和一个空的栈 stack。然后,对于图中的每个节点,如果该节点未被访问,则调用 dfs 函数进行深度优先搜索。在 dfs 函数中,先将当前节点标记为已访问,然后递归地对其未访问的邻居节点进行深度优先搜索。当一个节点的所有邻居节点都被访问后,将该节点入栈。最后,将栈中的节点逆序输出,得到拓扑排序的结果。
7.3 路径查找
深度优先搜索算法还可以用于在图或树中查找从起点到终点的路径。例如,在一个迷宫游戏中,迷宫可以看作是一个二维的图,每个格子是一个节点,相邻的格子之间有边相连。我们可以使用深度优先搜索算法来寻找从迷宫的入口到出口的路径。从入口节点开始,沿着不同的方向进行深度优先搜索,当找到出口节点时,搜索结束,并记录下从入口到出口的路径。如果搜索完所有可能的路径都没有找到出口,则说明迷宫无解。
以下是一个简单的使用深度优先搜索寻找迷宫路径的示例代码(这里使用一个简单的二维矩阵来表示迷宫,0 表示可通行的路径,1 表示墙壁):
def find_path(maze, start, end):def dfs(x, y, path):if (x, y) == end:return path + [(x, y)]if not (0 <= x < len(maze) and 0 <= y < len(maze[0]) and maze[x][y] == 0):return Nonemaze[x][y] = 1 # 标记为已访问# 向上探索up_path = dfs(x - 1, y, path + [(x, y)])if up_path:return up_path# 向下探索down_path = dfs(x + 1, y, path + [(x, y)])if down_path:return down_path# 向左探索left_path = dfs(x, y - 1, path + [(x, y)])if left_path:return left_path# 向右探索right_path = dfs(x, y + 1, path + [(x, y)])if right_path:return right_pathreturn Nonereturn dfs(start[0], start[1], [])
# 示例迷宫
maze = [[0, 0, 1, 0],[0, 1, 0, 0],[0, 0, 0, 1],[1, 1, 0, 0]]
start = (0, 0)
end = (3, 3)
print(find_path(maze, start, end))
在上述代码中,find_path 函数内部定义了 dfs 函数用于深度优先搜索。从起始位置开始,依次向上、下、左、右四个方向进行探索,如果某个方向上的位置在迷宫范围内、是可通行的且未被访问过,则递归地在该方向上继续搜索。如果找到了终点,则返回从起点到终点的路径;如果所有方向都探索完仍未找到终点,则返回 None。在搜索过程中,将已访问的位置标记为 1,以避免重复访问。
另外,在路径查找问题中,深度优先搜索常常与回溯算法结合使用。回溯算法能够在搜索过程中记录下已经走过的路径,当遇到死路时,可以回溯到上一个节点,继续尝试其他路径。这种结合方式使得深度优先搜索在处理复杂的路径搜索问题时更加灵活和有效,能够在搜索空间中全面地探索所有可能的路径,直到找到目标路径或者确定不存在目标路径为止。例如在上述迷宫问题中,path 参数就用于记录从起点到当前节点的路径,当搜索到终点时,直接返回该路径;如果在某个方向上的搜索失败,则回溯到上一个节点,继续尝试其他方向的搜索,通过不断地回溯和重新探索,最终找到从起点到终点的完整路径或者确定无解。
八、算法优化策略
8.1 剪枝优化
剪枝是一种在深度优先搜索过程中去除不必要搜索分支的优化策略,它基于问题的特定条件或约束,提前判断某些分支不可能产生目标解,从而避免对这些分支的深入探索,以提高算法效率。例如,在求解数独问题时,我们可以根据每行、每列和每个小九宫格中已经填入的数字,对后续可能的数字选择进行剪枝。如果某一行已经存在数字 1-5,那么在当前单元格中就无需尝试填入这些数字,直接跳过对这些数字的搜索分支。以下是一个简单的数独求解代码示例,展示了如何使用剪枝优化:
def solveSudoku(board):def is_valid(row, col, num):# 检查行、列和 3x3 子网格中是否存在相同数字for i in range(9):if board[row][i] == num or board[i][col] == num:return Falsestart_row, start_col = 3 * (row // 3), 3 * (col // 3)for i in range(start_row, start_row + 3):for j in range(start_col, start_col + 3):if board[i][j] == num:return Falsereturn Truedef dfs(row, col):if row == 9:return Trueif col == 9:return dfs(row + 1, 0)if board[row][col]!= '.':return dfs(row, col + 1)for num in '123456789':if is_valid(row, col, num):board[row][col] = numif dfs(row, col + 1):return Trueboard[row][col] = '.' # 回溯,撤销当前选择return Falsedfs(0, 0)return board
在上述代码中,is_valid 函数用于检查在特定位置填入某个数字是否合法。在 dfs 函数中,当遇到已经填入数字的单元格时,直接跳过后续搜索,继续下一个单元格的处理。而对于可填数字的单元格,通过 is_valid 函数进行剪枝,只尝试合法的数字,避免了大量无效的搜索。
8.2 启发式搜索
启发式搜索是将深度优先搜索与启发式信息相结合的一种搜索算法,它通过引入启发函数来引导搜索方向,使得搜索更倾向于朝着目标节点前进,从而更快地找到解。其中,A* 算法是一种典型的启发式搜索算法,它在搜索过程中综合考虑了从起始节点到当前节点的实际代价 g(n) 和从当前节点到目标节点的估计代价 h(n),通过计算每个节点的 f(n) = g(n) + h(n) 值来确定下一步的搜索方向,总是选择 f 值最小的节点进行扩展。例如,在路径规划问题中,我们可以使用曼哈顿距离或欧几里得距离作为启发函数来估计当前节点到目标节点的距离。以下是一个简单的 A* 算法在二维网格地图中寻找最短路径的示例代码:
import heapqdef heuristic(a, b):# 使用曼哈顿距离作为启发函数return abs(a[0] - b[0]) + abs(a[1] - b[1])def astar_search(graph, start, end):open_list = []heapq.heappush(open_list, (0, start))came_from = {}cost_so_far = {start: 0}while open_list:_, current = heapq.heappop(open_list)if current == end:breakfor neighbor in graph[current]:new_cost = cost_so_far[current] + 1 # 假设相邻节点之间的代价为 1if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]:cost_so_far[neighbor] = new_costpriority = new_cost + heuristic(neighbor, end)heapq.heappush(open_list, (priority, neighbor))came_from[neighbor] = current# 重构路径path = []current = endwhile current in came_from:path.append(current)current = came_from[current]path.append(start)path.reverse()return path
在上述代码中,heuristic 函数计算曼哈顿距离作为启发式估计代价。astar_search 函数实现了 A* 算法的主要逻辑,通过维护一个优先队列 open_list,按照节点的 f 值进行排序,优先选择 f 值最小的节点进行探索。在搜索过程中,不断更新从起始节点到各个节点的实际代价 cost_so_far 和节点的前驱节点信息 came_from,当找到目标节点后,根据前驱节点信息重构出最短路径。
8.3 记忆化搜索
记忆化搜索主要用于处理具有重复子问题的树或图结构,它通过使用数据结构(如哈希表)存储已经计算过的子问题的解,在后续搜索过程中,当遇到相同的子问题时,直接从存储结构中获取解,避免了重复计算,从而提高算法效率。例如,在计算斐波那契数列时,我们可以使用记忆化搜索来避免重复计算中间项。以下是一个简单的记忆化搜索计算斐波那契数列的示例代码:
def fibonacci(n, memo={}):if n in memo:return memo[n]if n == 0 or n == 1:return nmemo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)return memo[n]
在上述代码中,使用一个字典 memo 作为记忆化存储结构,在计算斐波那契数列的第 n 项时,首先检查 memo 中是否已经存在该项的结果,如果存在则直接返回,否则按照斐波那契数列的定义进行计算,并将结果存储到 memo 中,以便后续使用。
九、总结与展望
深度优先搜索算法作为树与图路径搜索中的关键算法,具有独特的原理与广泛的应用。通过从起始节点沿着深度方向探索,结合回溯与剪枝思想,它能够有效地遍历树与图结构,解决诸如连通性判断、拓扑排序、路径查找等多种问题。在实现上,递归与迭代(栈)方式各有优劣,可根据具体场景选择合适的方法。其时间复杂度为,空间复杂度相对较低,但在递归实现时需注意栈溢出问题。
深度优先搜索算法的优势明显,如空间开销小、适合特定问题求解且简单直观,然而也存在难以找到最优解、递归可能导致栈溢出以及搜索顺序不固定等局限。为克服这些局限,一系列优化策略如剪枝优化、启发式搜索和记忆化搜索应运而生,这些策略能显著提高算法效率,拓展其应用范围。
展望未来,随着数据规模的不断增大和问题复杂度的持续提升,深度优先搜索算法仍将在数据结构与算法领域发挥重要作用。在人工智能、大数据分析、网络科学等领域,其与其他算法的结合应用将成为研究热点。例如,在处理大规模图数据时,如何更高效地进行分布式深度优先搜索,以及如何设计更精准的启发式函数以提高搜索效率等问题,都有待进一步探索与研究。相信随着技术的不断发展,深度优先搜索算法将不断完善与创新,为解决更多复杂的实际问题提供有力支持。
相关文章:
【数据结构与算法】深度优先搜索:树与图的路径探寻之道
一、引言 在计算机科学领域,树与图的路径搜索是一个基础且重要的问题,而深度优先搜索算法(Depth First Search,简称 DFS)则是解决此类问题的经典算法之一。深度优先搜索算法通过从起始节点开始,沿着一条路径…...
vue3项目结合Echarts实现甘特图(可拖拽、选中等操作)
效果图: 图一:选中操作 图二:上下左右拖拽操作 本案例在echarts示例机场航班甘特图的基础上修改 封装ganttEcharts组件,测试数据 airport-schedule.jsonganttEcharts代码: 直接复制粘贴可测…...
【EXCEL 逻辑函数】AND、OR、XOR、NOT、IF、IFS、IFERROR、IFNA、SWITCH
目录 AND:当所有条件都为真时返回 TRUE,否则返回 FALSE OR:当任一条件为真时返回 TRUE,否则返回 FALSE XOR:当奇数个条件为真时返回 TRUE,否则返回 FALSE NOT :反转逻辑值 IF:根…...
单片机长耗时前后台任务优化
代码: void Task_10ms(void) {... }//改 void Task_2ms(void) {static uint8_t s_state 0switch(s_state){case 0:....s_state 1;break;case 1:....s_state 2;break;case 3:....s_state 1;break;default: //此段可以去除s_state 0;break; } } 参考链接 MCU长…...
java引入jedis并且关于开放redis端口问题
博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:数据库 JavaEE专栏:JavaEE 软件测试专栏:软件测试 关注博主带你了解更多知识 目录 1. 引入jedis 编辑 2. 关于java客户端开放redis端口问题 3. 连接redis服务器 redis服务器在官网公开了使用的协议: resp…...
测试电脑是否真实多核CPU
测试电脑是否真实多核CPU 在CPU的描述上现在多数看到的是多核心/多内核,看上去就像是多CPU的样子。但核心是有分真实核心和虚拟核心。如果是真实的多核心,多线程是能够并行。如果不是多核心,多线程就只能够并发。 这里就直接采用多线程的应用…...
Ubuntu 安装实时内核指南
在运行需要高精度和低延迟响应的机器人驱动程序时,安装一个具备实时内核(Real-Time Kernel)的 Ubuntu 系统是至关重要的。若缺乏实时系统的支持,高频率的控制指令可能会导致机器人运动轨迹不流畅,甚至产生抖动现象。以…...
LeetCode:1387. 将整数按权重排序(记忆化搜索 Java)
目录 1387. 将整数按权重排序 题目描述: 实现代码与解析: 记忆化搜索 原理思路: 1387. 将整数按权重排序 题目描述: 我们将整数 x 的 权重 定义为按照下述规则将 x 变成 1 所需要的步数: 如果 x 是偶数ÿ…...
某音最新滑块3.5.68(Web/App皆可支持)
某音滑块核心是 captchaBody 参数 难度较大 h5_sdk_version - 代表验证码的版本 如何代表通过验证了呢? 1.web端 fp参数 - verify_m4zafhzb_yARRD6RZ_YwNj_4gjp_AdsL_yxw0thiqv0ub 2.移动端 did参数 - 1997744780462444 当该接口返回如下数据即通过验证码 该设…...
FFmpeg 框架简介和文件解复用
文章目录 ffmpeg框架简介libavformat库libavcodec库libavdevice库 复用(muxers)和解复用(demuxers)容器格式FLVScript Tag Data结构(脚本类型、帧类型)Audio Tag Data结构(音频Tag)V…...
观察者模式(sigslot in C++)
大家,我是东风,今天抽点时间整理一下我很久前关注的一个不错的库,可以支持我们在使用标准C的时候使用信号槽机制进行观察者模式设计,sigslot 官网: http://sigslot.sourceforge.net/ 本文较为详尽探讨了一种观察者模…...
git企业开发的相关理论(二)
目录 git企业开发的相关理论(一) 八.修改文件 九.版本回退 十.撤销修改 情况一(还没有add) 情况二(add后还没有commit) 情况三(commit后还没有push) 十一.删除本地仓库中的文件 方法一 方法二 十二.理解分支 1.常见的分支工作流程 2.合并冲…...
力扣-图论-70【算法学习day.70】
前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程(例如想要掌握基础用法,该刷哪些题?)我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非…...
jmeter中的prev对象
在jmeter中通过beanshell、JSR223的各种处理器编写脚本时,都会看到页面上有这样的说明 这些ctx、vars、props、OUT、sampler、prev等等都是可以直接在脚本中使用的对象,由jmeter抛出 今天主要讲一下prev的使用 SampleResult prev jmctx.getPreviousRe…...
机器学习中的密度聚类算法:深入解析与应用
在机器学习的广阔领域中,聚类算法作为一种无监督学习方法,扮演着至关重要的角色。其中,密度聚类算法以其独特的优势,在数据挖掘、图像分割、市场细分等多个领域得到了广泛应用。 一、密度聚类算法的基本原理 密度聚类算法是一种…...
简单分析一下 a,b,c=a+1,a+1,b+1 执行原理
在 Go 语言中,赋值表达式 a, b, c x, y, z 是同时进行的,但是其计算顺序是从左到右依次进行的。即在 a, b, c 被赋值之前,先计算 x, y, z 的值,并依次将它们赋值给 a, b, c。 例如:a, b, c a1, a1, b1,其…...
2025年前端面试热门题目——HTML|CSS|Javascript|TS知识
以下是对这些 HTML 面试问题的详细解答: 1. HTML 的 src 和 href 属性有什么区别? src (Source) 属性: 用于嵌入资源,例如图像、脚本或 iframe。加载资源时,当前页面的加载会暂停,直到资源加载完成。常用于 <img&g…...
将4G太阳能无线监控的视频接入电子监控大屏,要考虑哪些方面?
随着科技的飞速发展,4G太阳能无线监控系统以其独特的优势在远程监控领域脱颖而出。这种系统结合了太阳能供电的环保特性和4G无线传输的便捷性,为各种环境尤其是无电或电网不稳定的地区提供了一种高效、可靠的视频监控解决方案。将这些视频流接入大屏显示…...
【102. 二叉树的层序遍历 中等】
题目: 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]] 示例…...
文件包含tomato靶机通关
靶机地址:192.168.152.152 注:靶机打开后在 kali 中扫描一下就能得到 打开网站 第一步信息收集 将网址放到 dirb 中扫描一下 得到了三个目录 我们挨个访问一下 第一个是主目录 第二个是主页面 第三个报错 第二步 我们在主目录页面继续访问 我们进行…...
oracle dblink 的创建及使用
Oracle Database Link(DB Link)是Oracle提供的一种功能,允许你在一个数据库中直接访问另一个远程或本地数据库的对象(如表、视图、序列等)。DB Link的设置简化了跨数据库操作,使得数据的集成和同步变得更加…...
java开发入门学习五-流程控制
流程控制语句 if, if...else, if..else if..else 与前端相同 略 switch case 与前端不同的是case不能使用表达式,使用表达式会报错 class TestSwitch {public static void main(String[] args) {// switch 表达式只能是特定的数据类型…...
【蓝桥杯——物联网设计与开发】拓展模块3 - 温度传感器模块
一、温度传感器模块 (1)资源介绍 🔅原理图 蓝桥杯物联网竞赛实训平台提供了一个拓展接口 CN2,所有拓展模块均可直接安装在 Lora 终端上使用; 图1 拓展接口 温度传感器模块电路原理图如下所示: 图2 …...
Zookeeper 底层原理解析
一、引言 在分布式系统的浩瀚星空中,Zookeeper 宛如一颗最为闪耀的导航星,为众多分布式应用指引方向、保驾护航。无论是大名鼎鼎的 Hadoop、HBase,还是其他各类复杂的分布式架构,Zookeeper 都扮演着不可或缺的关键角色。它如同一…...
面试题整理9----谈谈对k8s的理解1
谈谈对k8s的理解 1. Kubernetes 概念 1.1 Kubernetes是什么 Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,方便进行声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态系统,其服务、支持和工具的…...
PromptGIP:Unifying lmage Processing as Visual Prompting Question Answering
“Unifying Image Processing as Visual Prompting Question Answering” 文章提出了一种名为 PromptGIP 的通用模型,将图像处理任务统一为视觉提示问答范式,在多个图像处理任务上展现出良好性能,为通用图像处理提供了新的思路和方法。 confe…...
chart文件结构
在 Helm 中,Chart 是一个用于定义、安装和升级 Kubernetes 应用程序的包。Chart 文件结构遵循一定的目录和文件组织方式,以下是典型的 Helm Chart 文件结构: 1. Chart 文件结构示例 mychart/ ├── Chart.yaml # 描述 Chart 的基…...
SQL优化
SQL优化 插入数据 insert优化 批量插入 insert into tb_test 2values(1, Tom), (2, Cat), (3, jerry); 手动提交事务 start transaction; insert into test1 values(4, Tom), (5, Cat), (6, jerry); insert into test1 values(7, Tom), (8, Cat), (9, jerry); insert int…...
输出1-100之间的随机数,控制输出格式,每行10个(注释有详解)
C 随机数生成与格式化输出 在编程中,随机数的生成是一个常见的需求,尤其是在游戏开发、模拟实验和数据分析等领域。本文将通过一个简单的 C 程序来演示如何生成随机数并进行格式化输出。我们将逐步解析代码,并讨论其工作原理及应用场景。 代…...
【数字化】华为数字化转型架构蓝图-2
目录 1、客户联结的架构思路 1.1 ROADS体验设计 1.2 具体应用场景 1.3 统一的数据底座 1.4 案例与成效 2、一线作战平台的架构思路 2.1 核心要素 2.2 关键功能 2.3 实施路径 2.4 案例与成效 3、能力数字化的架构思路 3.1 能力数字化的核心目标 3.2 能力数字化的实…...
MyBatis是什么?为什么有全自动ORM框架还是MyBatis比较受欢迎?
MyBatis是什么? MyBatis是一个半自动的ORM持久层框架,内部封装了JDBC,mybatis是通过XML或注解的方式将需要执行的statement配置,支持定制化sql,存储过程以及高级映射。 解释 所谓的半自动ORM意思就是将JDBC的工作交…...
基础元器件的学习
1、二极管 1.1二极管的符号 ZD是稳压二极管 VD、V、D是普通二极管的符号。 1.2二极管的反向恢复时间 首先交流电为上正下负,然后下正上负。当二极管接到反向电压,二极管存在寄生电容,电压不能立刻突变,当输入频率变高时&#…...
GTID下复制问题和解决
环境介绍 数据库1主2从,mysql版本是v5.19 表结构 一、主库新增记录,从库提示主键冲突 模拟故障 1, master上关闭 sql_log_bin,删除id 103 后打开 2, 确认此时从库有id103,主库没有 3, master insert id103 主从异常…...
Linux 下的 GPT 和 MBR 分区表详解
文章目录 Linux 下的 GPT 和 MBR 分区表详解一、分区表的作用二、MBR(Master Boot Record)1. **特点**2. **优点**3. **缺点**4. **适用场景** 三、GPT(GUID Partition Table)1. **特点**2. **优点**3. **缺点**4. **适用场景** 四…...
mysql的事务控制和数据库的备份和恢复
事务控制语句 行锁和死锁 行锁 两个客户端同时对同一索引行进行操作 客户端1正常运行 客户端2想修改,被锁行 除非将事务提交才能继续运行 死锁 客户端1删除第5行 客户端2设置第1行为排他锁 客户端1删除行1被锁 客户端2更新行5被锁 如何避免死锁 mysql的备份和还…...
2014年IMO第4题
△ A B C \triangle ABC △ABC 中, B C BC BC 上有一点 P P P 满足 ∠ B A P = ∠ A C B \angle BAP=\angle ACB ∠BAP=∠ACB, 还有一点 Q Q Q 满足 ∠ A = Q A C = ∠ A B C \angle A=QAC=\angle ABC ∠A=QAC=∠ABC. 分别延长 A P AP AP, A Q AQ AQ 一倍至 M M M, N …...
如何实现层叠布局
文章目录 1 概念介绍2 使用方法3 示例代码我们在上一章回中介绍了GirdView Widget,本章回中将介绍Stack这种Widget,闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 在Flutter中Stack主要用来叠加显示其它的Widget,类似我们日常生活中的楼层或者说PS中的图层,因此它也是一…...
Qwen2.5-7B-Instruct Lora微调
Qwen2.5-7B-Instruct Lora 微调 本文简要介绍如何基于 transformers、peft 等框架,对 Qwen2.5-7B-Instruct 模型进行 Lora 微调。Lora 是一种高效微调方法。 环境配置 在完成基本环境配置和本地模型部署的情况下,你还需要安装一些第三方库,…...
MacOS安装MySQL
官网下载MySQL 苹果芯片选择ARM版本 安装过程中会要求你输入root的密码(不少于8位),这里设置为12345678 打开系统设置查看是否成功安装MySQL 配置MySQL环境变量 vi ~/.zshrc加入一行export PATH$PATH:/usr/local/mysql/bin 执行source ~/…...
基础库正则表达式
我们已经可以用requests 库来获取网页的源代码,得到 HTML 代码。但我们真正想要的数据是包含在 HTML代码之中的,要怎样才能从 HTML,代码中获取想要的信息呢?正则表达式就是其中一个有效的方法。 本篇博客我们将了解一下正则表达式的相关用法。正则表达…...
Matlab 和 R 语言的数组索引都是从 1 开始,并且是左闭右闭的
文章目录 一、前言二、主要内容三、小结 🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 一、前言 在早期的计算机科学中,数组索引从 1 开始是很常见的。例如,Fortran 和 Pascal 等编程语言也采用了从 1 开始的索引。 这种索引…...
选择排序和冒泡排序;MySQL架构
1. 选择排序和冒泡排序 (1)选择排序 原理: 选择排序有升序和降序两种排序方法。升序排序的原理是:对于一个无序数列,先假定其中一个数为这个数列的最小值,然后让这个假定最小值和其他数依次比较࿰…...
蓝桥杯算法训练 黑色星期五
题目描述 有些西方人比较迷信,如果某个月的13号正好是星期五,他们就会觉得不太吉利,用古人的说法,就是“诸事不宜”。请你编写一个程序,统计出在某个特定的年份中,出现了多少次既是13号又是星期五的情形&am…...
Mybatis-Plus快速入门
参考:黑马MyBatisPlus教程全套视频教程,快速精通mybatisplus框架 1.Mapper-plus配置 1.MapperScan("Mapper目录的位置") 2.Mapper层文件需要继承BaseMapper extends BaseMapper<实体类> 3.开启日志 4.配置类 Configuration public cl…...
MySQL库的操作
目录 1. 创建数据库2. 创建数据库案例3. 认识系统编码以及字符集和校验规则4. 操纵数据库4.1 查看数据库4.2 显示创建语句4.3 修改数据库4.4 数据库的删除4.5 备份和恢复4.6 查看连接情况 1. 创建数据库 (1)语法: create database db_name;…...
JVM性能优化一:初识内存泄露-内存溢出-垃圾回收
本文主要是让你充分的认识到什么叫做内存泄露,什么叫做内存溢出,别再傻傻分不清了,别再动不动的升级服务器的内存了。 文章目录 1.基本概念1.1.内存泄露1.2.内存溢出1.3.垃圾回收1.4.内存泄露-垃圾回收-内存溢出三者的关系关系 2.代码示例2.…...
2024年山东省职业院校技能大赛网络建设与运维X86架构与ARM架构搭建赛题
完整赛题解析主页联系! 一、X86架构计算机操作系统安装与管理 1.PC1 系统为 ubuntu-desktop-amd64 系统(已安装,语言为英文),登录用户为 ubuntu,密码为Key-1122。配置ubuntu用户能免密使用sudo命令。 sud…...
flask_sqlalchemy event监听查询事件
flask_sqlalchemy event监听查询事件 在Flask-SQLAlchemy中,可以使用事件监听器来监控查询事件。这可以通过listens_for(ModelClass, “event_name”)装饰器来实现,其中ModelClass是你想要监控的模型类,event_name是你想要监控的事件名称&…...
解决vscode ssh远程连接服务器一直卡在下载 vscode server问题
目录 方法1:使用科学上网 方法2:手动下载 方法3 在使用vscode使用ssh远程连接服务器时,一直卡在下载"vscode 服务器"阶段,但MobaXterm可以正常连接服务器,大概率是网络问题,解决方法如下: 方…...
OpenAI发布全新AI模型 o3 与 o3-mini:推理与编码能力迎来重大突破. AGI 来临
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...