从此和空间复杂度O~n说再见!一节搞定Morris遍历
我习惯用python来诠释算法代码,其一,python的代码相对简洁,不像c语言等复杂,使大家思路清晰。其二,简洁的代码我也省下不少时间,毕竟专业作业真是越来越多,哎。当然实际上肯定不是用python来实现,不如c语言的效率快,毕竟真实讲原理嘛。
看不懂可以参考B站up主视频来学习:点击跳转morris算法视频
为什么二叉树普通算法空间复杂度为O(n)?
稍微了解一点二叉树的都知道二叉树的三种普通递归遍历(先序、中序、后续遍历),啊?你不知道,:点击任意门传送,对于新的学者来说,可能并不知道,这些遍历方法为什么会消耗内存,因为这些方法里似乎并没有任何的开辟空间的函数方法,对于刚学的我来说的时候亦是一样的,因为并不清楚栈的概念,甚至不清楚栈的存在,如果你暂时不想清除这一点,也就可以跳过了。
我先创建一个伪代码方便于理解:
def 函数1(num):
num-=1
if num>0:
函数1(num)
print("执行了1次")
函数1(3)
图1-1为以上函数过程的解释:
图 1-1
压栈的操作本质上存储了某个函数的执行状态,从最后的函数依次执行完,也就是所谓的先进后出,后进先出的特点。
所以,递归函数调用搜索二叉树,每次遍历到下一个节点,其实栈中都有保存函数的递归记录,即为O(1)空间复杂度,这个消耗的内存是不小于单单记录节点信息的。
为什么morris可以做到空间复杂度为O(1)?
空间的消耗来源于对于之前遍历过的节点的维护,方便使其跳转到原来的位置,但并不是说使其找到原来节点的位置就只有一一记录的方法,我们可以利用二叉树结尾端来指向之前的节点,当遍历到未节点时,发现为节点指向头节点,说明遍历到底了,此时当前节点就可以换个方向接着遍历了。
一颗二叉树:
morris遍历的过程
前面提到过要想回到原来节点,必须有记录操作,所以创建两个变量,一个代表当前节点的位置,第二个用来进行向下延申节点进行结尾与头节点的连接,或者是判定是不是第一次来到该节点。
假设cur为当前节点,MorrisRight为”判断“节点。
图 1-2
这种算法就是合理的利用了未节点空余的接口,与头节点建立联系,达到回调的效果。
创建cur和morrisRight(mostRight)变量,cur代表当前节点,morisRight为判断变量。图1-3表示遍历判断过程
图1-3
开始cur为a点,a存在左端点b,找到b节点下最右下的端点,发现h指向None,则使得h.right=a,然后cur左移到b,然后重复以上操作,直到e点,就形成了图1-2。
cur=e时,不存在左端点,则cur=cur.right(也就是图中的c),这样就实现了节点回调的效果。此时cur=c,接着下一次的循环开始,发现cur.left的最右下的端点指向cur(说明了cur的左子树遍历完了),让cur.left的右下端点的指针指向空(还原初始的状态),cur=cur.right(开始对cur的右子树遍历)。
以上就是运行过程的介绍,可能不是很详细,那就结合以上给的视频链接观看吧
Morris遍历python代码实现
优雅python,永不过时!
结合注释快速理解:
def morrist(node):
cur=node
morristright=None #创建维护节点
while cur!=None: #当cur=None,则表示遍历到树的最右下节点,所有节点遍历完成
print(cur.data)
morristright=cur.left
if morristright:
while morristright.right!=None and morristright.right!=cur: #寻找左节点的右下节点
morristright=morristright.right
if morristright.right==cur: #右下节点为cur说明该节点标记过,遍历过,遍历到了左子树的右下节点说明左子树遍历完成,开始遍历右子树(cur=cur.right)
morristright.right=None #还原未节点的None值
else:
morristright.right=cur
cur=cur.left
continue
cur=cur.right #没有左节点或是左节点的右下指向头节点,指针都应该指向右子树
结束,欢迎大佬点评交流。