算法的套路一:应该从树开始

最近刷完了剑指offer,刷的比较慢,大概刷了有2,3个星期吧。总结了一下,感觉大部分题都是一些套路上演变一下,背下这些套路可以大大提高做题的速度和敏感度。比如,树相关遍历算法,dfs,bfs及图的dfs,bfs,以及在此基础上演变而来的回溯法,分治归并就是一个套路系列,模版代码也有很大的相似度。说实话,问题的种类繁多,我还没有把其中的关系全部整理清除,网络上大量的书籍和教程个人总觉得缺了点什么,好像有种没有把什么东西说清的感觉。

1.树的DFS和BFS

树的dfs有递归和非递归两种,两种都各有很大的用处。这是我认为入门第一个非常重要的两个模版,对后期理解别的模版有极大作用。

1.1 DFS递归

理解这个对理解递归特别有好处。递归可以与动态规划结合,在递归的过程中维护一张递归表,也可以在递归过程中维护一张记忆表,解决回溯问题。
对于递归,最好背下来,然后再寻求理解。递归程序往往很难去改,一些变种也很难常规方法想到。
先构造一个二叉树,方便我们调试:

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None
root=TreeNode(1)
root.left=TreeNode(2)
root.right=TreeNode(3)
root.left.right=TreeNode(4)
print(dfs(root))
#1.基本递归dfs
def dfs(root):
	if not root:
		return 
	print(root.val)
	if root.right:
		dfs(root.right)
	if root.left:
		dfs(root.left)

1.2 非递归DFS和BFS的通用写法

非递归的dfs关键在于创建一个stack。在python中,stack和quene都可以用list来表示。

我想谈谈递归的本质问题,现代编程语言的函数存储实现方式都是类似的,在系统内存中选择一块连续的内存作为栈,然后选择一些分散的内存区域作为堆。在栈中存储函数及变量地址,在堆中放静态变量和变量地址对应的数据。所以,所谓调用一个函数,本质上就是把当前函数放入栈不执行,先去执行被调用的那个函数,在出栈执行当前函数的过程。而递归的过程则是将这个操作系统要做的事情由工程师来实现了。所以,我们实现递归,维护一个栈就行了。

#2.非递归dfs
def dfstree(node):
    stack=[]
    res=[]
    stack.append(node)
    while stack:
        node=stack.pop()
        res.append(node.val)
        if node.left:
            stack.append(node.left)
        if node.right:
            stack.append(node.right)
    return res

对于bfs,只要记住非递归形式就够了。把上面的非递归dfs中的stack改为quene就行。

#3.非递归bfs
def bfstree(node):
    quene=[]
    res=[]
    quene.append(node)
    while quene:
        node=quene.pop(0)
        res.append(node.val)
        if node.left:
            quene.append(node.left)
        if node.right:
            quene.append(node.right)
    return res

1.3 递归DFS的变种回溯算法

这个回溯算法非常多见,是由DFS变种来的,能否掌握这个可以看作小白和非小白的标志了。

def dfs2(root):
    res=[]
    def dfs(root,tmp):
        if not root.left and not root.right:
            res.append(tmp)
            return 
        if root.left:
            dfs(root.left,tmp+[root.left.val])
        if root.right:
            dfs(root.right,tmp+[root.right.val])
    dfs(root,[root.val])
    return res

看一下输出:

[[1, 2, 4], [1, 3]]

这个算法会把到每一个叶子节点的路径全部记录下来,很多设计选择,决策的题目都采用这种方法解决。
当然,非递归也是可以的,就是有点啰嗦我感觉。

def dfstree(node):
    stack=[]
    res=[]
    stack.append(node)
    tmp=[]
    while stack:
        node=stack.pop()
        tmp.append(node.val)
        if not node.left and not node.right:
            res.append(tmp)
            tmp=tmp[:-1]
        if node.left:
            stack.append(node.left)
        if node.right:
            stack.append(node.right)
    return res

1.4 DFS递归的变种分治归并

把大问题转换为小问题,把小问题解决之后合并解决大问题,与递归的思想分解的思想不谋而合。这个再想想。

2.图的DFS和BFS

这个用非递归的比较好,完全可以由树的改过来,这就是我说的一个系列的意思。区别在与图需要多一个set(),来判断这个节点是不是已经经过了,因为图和树不一样,图是连在一起的,所以也可以说树是一种特殊的图。
和树一样,我们先构造一个图,方便调试:

graph={
    'A':['B','C'],
    'B':['A','C','D'],
    'C':['A','B','D','E'],
    'D':['B','C','E','F'],
    'E':['C','D'],
    'F':['D']    
}
iter_bfs(graph,'A')

2.1图的DFS

可以看到,核心都一样,维护一个stack。

def iter_dfs(graph,s):
    stack=[]
    stack.append(s)
    seen=set()
    seen.add(s)
    while len(stack)>0:
        vertex=stack.pop()
        nodes=graph[vertex]
        for w in nodes:
            if w not in seen:
                stack.append(w)
                seen.add(w)
        print(vertex)

2.2图的BFS

也是一样,stack改成queue完事。

def iter_bfs(graph,s):
    queue=[]
    queue.append(s)
    seen=set()
    seen.add(s)
    while len(queue)>0:
        vertex=queue.pop(0)
        nodes=graph[vertex]
        for w in nodes:
            if w not in seen:
                queue.append(w)
                seen.add(w)
        print(vertex)

动态规划

快排及双指针

二分查找

最后一个不易察觉的问题:堆

总结

背熟套路是解题的基础,但不是全部,关键还是多看多做,不能忽略这点。另外,这个是中等题的套路,初级题虽然让人轻视,其实往往并不简单。因为初级题考察的是能否熟记语法,常常让人阴沟翻船。这其实是另外一种看题的思路,包括位运算,字典操作,collection标准库的使用等,都是平时很少注意的点,我会专门再总结。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值