用Python 类实现两种排序BFS/DFS算法

**

用Python 类实现两种排序BFS/DFS算法

什么是BFS和DFS算法
BFS和DFS算法代码实现
BFS和DFS算法(第3讲)—— 从BFS到Dijkstra算法

思路:
大家做这道题的时候,首先自己要创建很多的节点,然后自己构建节点之间的连接关系,打散时候排序,排序的话大家想想根节点有什么特点,很容易就会找到根节点的。
另外就是:创建节点可以有自己的创建方式,属性可以有input node 和output node,然后根据input 和output 直接构建网络,这种左右节点方式还是easy的
提供思考的代码模板:

# coding:utf8

class Node:
    
    def __init__(self,name,value):
        self.name = name
        self.value = value
        self.input_node = [] # 存放input node 的 name
        self.output_node = [] # 存放 output node 的 name
        self.depth = 0 # dfs 排序中用于设定深度
        self.index = -1 # 存储该节点拍好序后的index,没ready为-1
        
    def setInput(self,name):
        self.input_node.append(name)
    
    def setOutput(self, name):
        self.output_node.append(name)
        
    def __repr__(self):
        return self.name
        

        
class Net:
    ''' 深度学习中net 是有不同node 连接而成,每个node都有input node 和 output node
    当每个节点的input 都已经计算好了 才可以往下计算,我们最终的目的是按照我们拍好序的顺序
    去执行,可以顺利计算出结果'''
    
    def __init__(self,node):
        self.input = [] #存放 net 的input node
        self.output = [] # 存放net的output node
        self.node = [] # 乱序的放这里
        self.ordered_node = [] #排好序的放在这里
        
    def addnode(self,node):
        self.node.append(node)
        
    def __getitem__(self,index): # 获取拍好序的index 的节点
        return self.ordered_node[index]
        
    def dfs(self):
        """ 自己实现深度优先排序,思路:
        1、查找已经ready的节点:input_node 为空或 input node 都已经ready
        2、将ready的node 存入 self.ordered_node,
        3、建立临时list 存放unredy_node
        4、建立stack_list 存放待处理的node,从self.ordered_node 中选取node 找到output 节点 入栈,准备处理
        5、处理栈中的节点:
                A、如果该节点已经是ready的话: 自行处理(关键点:节点所有子节点都处理好后才能释放该节点);
                B、如果没有ready,将该节点入栈;
        """
        
        pass
        return self.ordered_node
    
    def bsf(self):
        """ 广度优先排序的实现,设定每个node的深度,然后按照深度排序"""
        pass
        
        return self.ordered_node
        
    
    
if __name__ == "__main__":
    
    node = [node1, node2, node3, node4, node5] # 自己建立node 并指定连接关系
    net = Net(node)
    resulf1 = net.dfs()
    result2 = net.bsf()

模板方法
使用BFS,DFS的题目,在leetcode上一般标记为medium或者hard。但从思维逻辑上看,其难度定义偏高。可能从代码量来看,写一道BFS或者DFS的篇幅比其他类型的题目要多。
BFS,DFS既然思维固定,必然有其套路。套用模板方法,让我们的解题更加流畅。有如庖丁解牛,游刃有余。

BFS模板
BFS思路是先将邻居加进来,如果只是口述的话难免枯燥。
下面将以图上的搜索(找start_node到end_node之间的最短距离长度)为例,阐述怎样将思维变成代码。

得到图
正如链表其实质为节点和指向下一个节点的指针,图的实质也就是图上的节点和连接各个节点的边。
由此,对于图(这里对有向图和无向图不做区分)其实有两种表示方法:
在这里插入图片描述
用各个节点之间的边(edges)表示图。
如我画的草图,则图可以由各个边表示,即为edges[[0,1],[0,2],[1,4],[2,3],[2,4]],每一条边连接两个顶点。根据题目,可以规定是否是有向图的边。
用节点到节点之间的连接表示图
假设图有n个节点,草图里面有5个。 则graph表示为[[1,2],[0,4],[0,3],[2],[1,2]]。graph中index表示节点的标号,即节点0和节点1、2相连,依次类推。此对应关系也可以使用字典表示。
BFS第一步要得到图,可以使用这两种方式中任一种,视自己熟悉程度和解题的快捷程度而定。
当然,可以根据edges构建图,不是很难:
用各个节点之间的边(edges)表示图。
如我画的草图,则图可以由各个边表示,即为edges[[0,1],[0,2],[1,4],[2,3],[2,4]],每一条边连接两个顶点。根据题目,可以规定是否是有向图的边。
用节点到节点之间的连接表示图
假设图有n个节点,草图里面有5个。 则graph表示为[[1,2],[0,4],[0,3],[2],[1,2]]。graph中index表示节点的标号,即节点0和节点1、2相连,依次类推。此对应关系也可以使用字典表示。
BFS第一步要得到图,可以使用这两种方式中任一种,视自己熟悉程度和解题的快捷程度而定。
当然,可以根据edges构建图,不是很难:

def initial_graph(n, edges):
    dict_graph = {}

    for i in range(n):
        dict_graph[i] = []
        
    num_e = len(edges)
    for i in range(num_e):
        u = edges[i][0]
        v = edges[i][1]
        dict_graph[u].append(v)
        dict_graph[v].append(u)

    return dict_graph

使用队列
bfs适用于层级搜索,队列先进先出,量身定做。
python中队列两种导入方法为:

1.使用queue

from queue import Queue
q = Queue() # 定义,为什么是这样涉及python的设计,不是很懂
q.put(node) # 放入
q.get() # 出队

2.使用deque

import collections
q = collections.deque() # 双向队列
q.append() # 入队
q.popleft() # 出队 

节点入队

使用Queue()定义

q.put(start_node)

为防止无向图中回溯,使用set阻断

hash_set = set()
hash_set.add(start_node)

bfs主体,需要背诵的部分

step = 0
while not q.empty():
	size = len(q)
	step += 1
	for iter_num in range(size):
		node = q.get() # current node
		# get the neighbor
		for neighbor in dict_graph[node]: 
			if neighbor == end_node: # find it!!!!
				return step
			if neighbor in hash_set: 
				continue # avoid backtracking
			hast_set.add(node)
			q.put(neighbor)
	return 0 # can't find

小节
大体的bfs过程如上所示,一些题目难点在于bfs的细节实现,例如:

  • bfs的终止条件 一些题目的终止条件可能不是找到某一终点,而是将图中的某种元素清除。如僵尸吃人的问题,需要将人全部变成僵尸。
    入队的元素表示 可以将元组(node,
    step)入队,出队时进行拆包,从而少写一层循环,进行一定程度上加速。如wordladder的问题,case给定wordList其实很长。

DFS模板

  • DFS思路是一条路走到底,撞到了墙再回头。 同上述BFS解析,下面将以找数组num的所有子集为例,对dfs的过程进行阐述。

数组的子集

  • 已知数组num[1,2,3](假设数组已经排序),要求他的所有子集,dfs的过程如草图2所示,这和人的思维方式几乎一模一样。

    从空集开始,然后将1加入,1加入之后,下一次加入2,之后加入3,这时startindex指针移出数组范围,开始回溯。将3移除后,回溯到2,(这里内部循环已经结束,执行到函数底部),2回溯之后,到1,1可以再加入3。这是程序的过程,关键是怎么写代码。

dfs函数定义

  • 因为dfs涉及到程序的递归调用,一般dfs不嵌入到程序内部过程。在主函数中,将dfs作为辅助函数调用,故此将dfs函数命名为dfsHelper。
#python中,一般定义如下:
def dfsHelper(self,
				input parameter..., 
				startIndex, 
				tmp, 
				result):
"""

其中:
input parameter一般指不变的已知量:比如数组num,在矩阵的搜索中指矩阵grid,在图中指图graph和目标节点end_node;
startIndex用来标记当前指针在数组(矩阵或者图中的位置),随着递归而改变;
tmp:用来暂存递归过程中的结构,随着递归而改变;
result:一般为全局变量,用来保存所有的结果。
在程序撰写中,可以定义很多经常使用的量为常量,从而使dfsHelper函数更加短小精悍。

dfs递归的过程,需要背诵

  • 递归的过程就是先在tmp中加入当前元素,然后调用自身(这中间指针后移),最后在tmp中移除当前元素。
    若不看dfsHelper的循环调用,可以这样理解;当tmp=[]时,将startIndex后移是依次将1,2,3加入tmp,也就是搜索的第一层。将"1"pop掉即保持了当前层的纯粹,没有这个操作会导致递归层次混乱。
for i in range(startIndex, len(nums)):
    tmp.append(nums[i])
    self.dfsHelper(nums, i + 1, tmp, result)
    tmp.pop()

结果的暂存

  • 由于是求所有的子集,再每次递归时其实tmp保存了不同的子集,故在dfs函数的最开始要考虑将tmp加入到result。由于tmp其实是指向一个地址的指针,在递归中可能随时改变,因此在加入之前先将tmp的内容拷贝一份而不是直接拿出,保证运行结果的正确。
    这里c++是否可以直接取值?有时间再研究下。
tmp_copy = tmp.copy()
result.append(tmp_copy)

dfs的出口

  • dfs的出口是这类题目的难点,因其变化多端。当定义dfsHelper函数中使用startIndex,很明显需要借助此标志进行判定。所以有:

if startIndex == len(nums):
    return

在其他的问题中,比如二维矩阵中,可能是index越界,可能是定义了visited而恰好当前位置访问过,这些针对特定的问题需要特殊对待。

一窥全貌
将此问题的完整解法列在下面,对此问题便更进一步理解了。

class Solution:
    """
    @param nums: A set of numbers
    @return: A list of lists
    """
    def subsets(self, nums):
        # 从空集开始搜索,每次将nums的节点加入空集中
        result = []
        tmp = []
        
        if nums is None:
            return None
        
        nums.sort()
        
        startIndex = 0    
        self.dfsHelper(nums, startIndex, tmp, result)
        
        return result
        
    def dfsHelper(self, nums, startIndex, tmp, result):
        # dfs出口
        tmp_copy = tmp.copy() # 拷贝一份
        result.append(tmp_copy)
        
        if startIndex == len(nums):
            return
        
        for i in range(startIndex, len(nums)):
            tmp.append(nums[i])
            self.dfsHelper(nums, i + 1, tmp, result)
            tmp.pop()
        return

小结

  • 个人觉得,DFS比BFS要难,因其在定义dfsHelper函数时多种多样,可以很灵活地根据自己的需求和习惯定义。但,换了个马甲,其内部的思维并没有变,计算量(所说的复杂度)大致没有变。
    所谓的算法,更侧重于“法”,而非“算”,即它更多的是一种方法,而不是一种算术。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单链表存储图的顶点可以使用邻接表的方式,即对于每个顶点,用一个链表存储其所有的邻接点。 BFS算法实现: ```python # 定义图的节点 class GraphNode: def __init__(self, val): self.val = val self.next = None # 定义图 class Graph: def __init__(self): self.nodes = [] # 添加节点 def add_node(self, val): node = GraphNode(val) self.nodes.append(node) # 添加边 def add_edge(self, src, dst): for node in self.nodes: if node.val == src: new_node = GraphNode(dst) new_node.next = node.next node.next = new_node break # BFS算法 def bfs(self, start): queue = [start] # 存储待遍历的节点 visited = set() # 存储已经访问过的节点 while queue: node = queue.pop(0) if node not in visited: print(node) visited.add(node) # 将该节点的所有邻接点加入队列 for n in self.get_neighbors(node): queue.append(n) # 获取某个节点的所有邻接点 def get_neighbors(self, node): for n in self.nodes: if n.val == node: neighbor_nodes = [] cur = n.next while cur: neighbor_nodes.append(cur.val) cur = cur.next return neighbor_nodes return [] # 测试BFS算法 graph = Graph() graph.add_node(1) graph.add_node(2) graph.add_node(3) graph.add_node(4) graph.add_edge(1, 2) graph.add_edge(1, 3) graph.add_edge(2, 4) graph.add_edge(3, 4) graph.bfs(1) ``` DFS算法实现: ```python # 定义图的节点 class GraphNode: def __init__(self, val): self.val = val self.next = None # 定义图 class Graph: def __init__(self): self.nodes = [] # 添加节点 def add_node(self, val): node = GraphNode(val) self.nodes.append(node) # 添加边 def add_edge(self, src, dst): for node in self.nodes: if node.val == src: new_node = GraphNode(dst) new_node.next = node.next node.next = new_node break # DFS算法 def dfs(self, start, visited=None): if visited is None: visited = set() print(start) visited.add(start) # 遍历该节点的所有邻接点 for n in self.get_neighbors(start): if n not in visited: self.dfs(n, visited) # 获取某个节点的所有邻接点 def get_neighbors(self, node): for n in self.nodes: if n.val == node: neighbor_nodes = [] cur = n.next while cur: neighbor_nodes.append(cur.val) cur = cur.next return neighbor_nodes return [] # 测试DFS算法 graph = Graph() graph.add_node(1) graph.add_node(2) graph.add_node(3) graph.add_node(4) graph.add_edge(1, 2) graph.add_edge(1, 3) graph.add_edge(2, 4) graph.add_edge(3, 4) graph.dfs(1) ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值