深度优先遍历和宽度优先遍历,其实算法原理都很相似,区别在于
**深度遍历:只有当一个节点的所有后代被访问完,才会访问同层的下一个节点;
宽度遍历: 只有同层的所有节点访问完,才会继续访问下一层节点**
因此在设计算法时,深度遍历可以借助栈,先压当前节点再压后代,就保证了不会漏掉该节点的每一个后代,先后顺序也不会乱;而宽度遍历借助队列,则保证了每次压入队列时先弹出的都是同一层的节点。
深度优先遍历(DFS)
原理:
借助栈的先进后出性质,并准备一个辅助记录数组(记录该节点之前是否已经打印过);
- 先将头结点压入栈中并打印头结点的值,并记录在辅助数组中。
- 开始循环, 从栈中弹出一个节点,挨个访问该节点的后代。如果当前访问的后代不在记录数组中(换言之,没有打印过),则先将该节点压入栈中,再将当前后代压入栈中,并且打印当前后代的值,同时记录在辅助数组中,此时不再访问该节点的下一个后代,直接break结束当前的for循环。
- 从外围的大循环开始,重新从栈中弹出一个节点(此时弹出的节点,按照压入栈的顺序和先进后出的性质,就是上一轮循环中的后代节点),访问该节点的后代节点是否已经打印过,没有执行压入栈中等操作,如果已经打印过,则查看下一个后代是否打印过,不断重复上述过程。
- 栈为空时,结束整个循环
代码如下:
DFS:
def dfs(node):
stack = [] # 辅助栈
had = set() # 记录用,判断一个节点是否已经被打印过,这里没用数组,用的集合
stack.append(node)
had.add(node)
print(node.value)
while (len(stack) != 0):
cur = stack.pop()
for n in cur.nexts: # 遍历该节点的后代
if n not in had: # 判断该后代是否已经打印过
stack.append(cur) # 注意顺序,要先将父节点压入
stack.append(n) # 再压入该后代节点
print(n.value)
had.add(n) # 记录
break # 跳出for循环
宽度优先遍历(BFS)
借助队列,一个辅助记录集合
- 开始还是先将头结点压入队列,并打印、记录
- 开始循环,从队列中弹出一个节点,开始遍历该节点的每个后代,与DFS中有所不同的是,它是遍历完一个节点的所有后代以后才结束当前的for循环,遍历时检查每个后代是否在记录集合中,如果在集合中表示已经打印过,则跳过,继续检查下一个后代;如果不在集合中,则压入队列,并记录打印;
- 当前节点的所有后代都遍历一遍之后,再次回到大循环,队列弹出一个节点,继续循环检查该节点的所有后代;
- 队列为空时,结束整个流程。
代码如下:
import queue
def bfs(node):
if node == None:
return None
had = set() # 记录一个节点是否已经出现过
q = queue.Queue() # 队列保证打印顺序
had.add(node)
q.put(node)
print(node.value)
while (not q.empty()):
cur = q.get() #
# print(cur.value)
for n in cur.nexts:
if n not in had: # 如果该节点没有进过(换言之,没有打印过)则记录并压入队列,否则跳过
had.add(n)
q.put(n)
print(n.value) # 进集合就打印