莫里斯算法实现二叉树的中序、前序、后序遍历,python实现

本文将记录有关莫里斯算法实现二叉树遍历的相关内容,如果有相关内容的同学欢迎邮件与我联系 [email protected]

相关内容

  • Leetcode-94 二叉树的中序遍历
  • Leetcode-144 二叉树的前序遍历
  • Leercode-145 二叉树的后序遍历
  • 莫里斯算法python实现
  • 莫里斯算法Java实现

在二叉树遍历中,递归法需要O(N)的空间复杂度,因为递归的时候需要把当前的路径记录下来直到结束;迭代法需要O(N)的空间复杂度,因为需要一个堆栈记录从高层节点到底层叶子节点的路径,然后在以此退栈,实现从底层叶子到高层节点,然后再向另一个方向的遍历(以前序为例,前序遍历,中左右,需要从中的高层根按照左子树走到左子树的叶子结点,然后利用堆栈在回到根节点,然后在访问根节点,然后在按照右子树走到右子树的叶子结点,实现左中右的访问顺序),所以需要的堆栈的空间大小就是树的高度,在最差的情况下就是节点个数N。

递归法和迭代法的时间复杂度都是O(n),因为每个节点都需要被访问起码一次,迭代法需要被访问两次,但是仍然是常数级的,仍然是O(N)的时间复杂度。

而莫里斯算法实现了时间复杂度O(N),但是空间复杂度O(1)的中、前、后序遍历。

莫里斯算法的核心思路是给每一个根节点找到他的中序前驱节点(左子树的最右的叶子),该中序前驱节点的右子树本来为空,利用这个空的内存指向根节点,作为从左子树回到根节点的方式,这么做就不需要堆栈来记录从根节点到叶子节点的顺序来返回了。

对于根节点而已设置了指向他的中序前驱节点的右子树之后,每个根节点会被访问两次,第一次访问时候是找到中序前驱节点然后设置空内存,第二次访问是利用中序前驱节点的右子树指向了根,这个时候根节点是第二次被访问,这个时候去找根节点的中序前驱节点的右子树,这个时候中序前驱的右子树已经指向了根节点,那么这个时候就需要将右子树值为空,重新回到原始的样子。

不同的遍历方式决定跟节点在第几次访问的时候被输出(后序除外)。
以下是具体的python代码

	'''
	莫里斯算法中序遍历
	莫里斯算法是将每个节点的中序的前驱节点的空右子树的空内存
	(左子树的最右叶子节点的空右子树的空右子树的空内存)指向该节点,
	作为从中序前驱节点返回上一层的途径。
	和之前的迭代法相比较,迭代法需要一个堆栈依次记录从上层到下层的记录,
	当到达最低层的节点时通过出栈的方式获得上层节点。
	而莫里斯算法,利用之前设置的空内存从底层节点返回上层节点。
	'''
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        result = []
        current = root  # 当前的节点,从根节点开始
        while current:
        	# 如果当前节点没有左子树,那么就输出该节点,并且转到右子树上
        	# 因为,中序遍历,左中右,没有了左子树之后,就输出中,然后转到右子树
            if not current.left:  
                result.append(current.val)
                current = current.right
            else:
            	# 当有左子树时,对于当前的节点,找到它的中序前驱节点
            	# 中序前驱节点是当前节点的左子树的最右叶子节点
                pre = current.left 
                # 找到当前节点的中序前驱,并且判断它的右子树是不是空
                while pre.right and pre.right != current:
                    pre = pre.right
                # 根节点的中序前驱节点是个叶子节点,叶子节点的右子树应该是None
                # 利用这个None的空内存指向该根节点
                # 通过该前驱节点的右子树是不是空 判断 是不是第一次访问该根节点
                # 如果该前驱节点的右子树是空,那么是第一次访问根节点
                # 如果该前驱节点的右子树已经指向了根节点,那么该根节点已经是第二次访问
                if not pre.right:
                	# 如果是第一次访问根节点,第一次找到中序前序
                	# 那么就将中序前驱(叶子)右子树的空内存指向该根节点
					# 并且当前节点指向左子树
					# 指向左子树的原因是已经记录了从底层节点返回上层的方式
                    pre.right = current
                    current = current.left
                else:
                	# 如果现在叶子节点的右子树的内存已经指向根节点了
                	# 那么就将该内存设置为空,回到树之前的样子
                	# 并且打印根节点的值
                	# 因为此时根节点已经是第二次访问了,该根节点下面的节点已经都被打印了
                	# 按照左中右的顺序,根节点的左子树下面的节点都被打印了,那么就到了对应的根节点【中】上,打印【中】,
                	# 然后转到根节点的右子树上
                    pre.right = None
                    result.append(current.val)
                    current =current.right
        return result

	



	'''
	莫里斯算法前序遍历
	莫里斯算法对于每一个节点将该节点的中序前驱节点(左子树上最右的叶子节点)的空右子树的内存指向自己
	作为从底层叶子节点返回高层根节点的途径。
	在迭代法中需要维护一个堆栈,作为从中到左,之后以此退栈实现从左子树回到根节点然后在进行向右子树的递归
	'''
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        self.result = []
        current = root  # 当前节点设置为根节点
        while current:
            if not current.left:  # 前序遍历是中左右的顺序,当没有左子树时,直接输出值,并转到右子树上
                self.result.append(current.val)
                current = current.right
            else:  # 当存在左子树时,找到根节点的中序前驱节点,也就是左子树的最右的叶子
                pre = current.left
                while pre.right and pre.right!=current:  # 判断左子树的最右叶子的右子树(本来是空内存)是否为空
                    pre = pre.right
                if not pre.right:  
                	# 当右子树是空,说明该根节点是第一次被访问
                	# 按照前序 中左右,根节点第一次被访问时就应该输出
                	#
                	# 前序和中序遍历不一样的地方在于,中序遍历是左中右,是从中到左到中的时候才将根节点输出
                	# 所以中序遍历是在第二次访问节点是输出
                	#
                	# 第一次访问根节点时,将中序前驱节点的右子树空内存指向根节点
                	# 然后按照中左右的顺序,访问了根节点就需要左子树,将当前节点转向左子树,
                    self.result.append(current.val)
                    pre.right = current
                    current = current.left
                else:
                	# 此时中序前驱节点的右子树的本来是空的内存已经指向了根节点
                	# 说明当前的根节点,已经是从左子树访问完了又回到了根节点,第二次访问根节点了
                	# 此时就需要将中序前序节点的右子树的内存值为空,回到最初树的形状
                	# 然后当前节点转向右子树
                    pre.right = None
                    current = current.right
        return self.result






	'''
	莫里斯算法后序遍历
	莫里斯算法的后序遍历需要一个额外的空间,将该空间的左子树指向根节点
	然后依次找到每个根节点的中序前驱节点
	当第二次访问该节点时,倒叙输出从根节点左子树到前序节点的结果到最终结果上
	'''
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        dump = TreeNode()  # 新建空节点,空节点的左子树指向根节点
        dump.left = root
        result = []
        current = dump
        while current:
        	# 当没有左子树时转向右子树
        	# 因为当没有左子树时,按照后序左右中的顺序,已经没有左了,只需要考虑右和中就可以
            if not current.left:  
                current = current.right
            else:
           		# 当存在左子树时,找到根节点的中序前驱节点
           		# 并通过中序前驱节点本来应该是空的右子树判断是第几次访问根节点
                this = current.left  # 记录左子树的节点,待会得到输出结果
                pre = current.left
                while pre.right and pre.right!=current:
                    pre = pre.right
                if not pre.right:
                	# 当第一次访问根节点,
                	# 则将中序前驱节点的右子树指向根节点
                	# 当前节点指向左子树
                    pre.right = current
                    current = current.left
                else:
                	# 如果是第二次访问根节点了
                	# 则【倒着】输出从左子树到中序节点的结果
                	# 然后转到右子树
                	# 
                	# 假设是一颗2层的树
                	#          		0(临时节点)
                	#      		1
                	#  		2       3
                	# 当第二次访问根节点1 时
                	# 根结点1的中序前驱节点是2
                	# 则【倒着】输出从左子树2,到中序前驱2的路径结果,[2]
                	# 然后从根节点1到右子树3,3没有左子树然后转到右子树0(3是0的中序前序)
                	# 这是第二次访问0节点,然后【倒着】输出从左子树1到中序前序3的路径结果,[3,1]
                	# 按照这个顺序,实际上是依次获得左,然后倒着获得[右,中] ,
                	#
                	# 当第二次访问根节点时,已经获得了根节点左子树的全部节点,所以转到右子树
                	# 因为添加了临时节点,所以获得了根节点左子树、根节点右子树之后会回到临时节点的最高节点,
                	# 最高节点获得它的左子树的全部节点时会获得根节点的值
                    pre.right = None
                    temp = []
                    while this :
                        temp.append(this.val)
                        this = this.right
                    print(temp)
                    result.extend(temp[::-1])
                    current = current.right
        return result

你可能感兴趣的:(算法及数据结构,刷题,python代码)