二叉树基本知识及二叉树的遍历(Python版本)

二叉树基本知识及二叉树的遍历(Python版本)

基本知识

与图论中的“度”不同,树的度是如下定义的:有根树T中,结点x的子女数目称为x的度。也就是:在树中,结点有几个分叉,度就是几。

二叉树的一些基本性质:

  1. 第i层最多有2**(i-1)个结点
  2. 深度为k的二叉树至多有2**k-1个结点
  3. 二叉树中,叶子结点个数为n_0, 度为2的结点的数为n_2, 则n_0=n_2+1
  4. 具有 n 个节点的满二叉树的深度为 log2(n+1)
  5. n 个结点的完全二叉树的深度为 ⌊log2n⌋+1

!!! 如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树。
二叉树的顺序存储结构: 使用数组存储二叉树,只适用于完全二叉树
如果我们想顺序存储普通二叉树,需要提前将普通二叉树转化为完全二叉树,只需加入一些dummy node将普通的二叉树补全即可

从顺序表中还原完全二叉树也很简单。 我们知道,完全二叉树具有这样的性质,将树中节点按照层次并从左到右依次标号(1,2,3,…), 若节点
i 有左右孩子,则其左孩子节点为 2i,右孩子节点为 2i+1。

二叉树的链式存储结构: 采用链式存储二叉树时,其节点结构由 3 部分构成:
指向左孩子节点的指针(Lchild);
节点存储的数据(data);
指向右孩子节点的指针(Rchild);

class TreeNode:
    def __init__(self,val,left=None,right=None) -> None:
        self.val=val
        self.left=left
        self.right=right

遍历的递归解法

二叉树的遍历:数据结构规定,在遍历过程中,先遍历左子树,再遍历右子树;故得出(深度遍历上的)三种遍历方式

!!!对于二叉树,有深度遍历和广度遍历,深度遍历:前序、中序、后序三种;广度遍历即我们平常所说的层次遍历。
四种主要的遍历思想为:主要看根节点是第几个访问
(1)前序遍历:根结点 —> 左子树 —> 右子树
(2)中序遍历:左子树—> 根结点 —> 右子树
(3)后序遍历:左子树 —> 右子树 —> 根结点
(4)层次遍历:只需按层次遍历即可

(1)前序遍历:

def preOrder(self, root: TreeNode):
    if not root:
        return None
    print(root.val)
    self.preOrder(root.left)
    self.preOrder(root.right)

(2)中序遍历:

def inOrder(self, root:TreeNode):
    if not root:
        return None
    self.inOrder(root.left)
    print(root.val)
    self.inOrder(root.right)

(3)后序遍历:

def postOrder(self, root:TreeNode):
    if not root:
        return None
    self.postOrder(root.left)
    self.postOrder(root.right)
    print(root.val)

二叉树的非递归解法:

非递归解法(参阅文章:二叉树非递归解)即迭代解法:本质上是在模拟递归,因为在递归的过程中使用了系统栈,所以在迭代的解法中常用Stack来模拟系统栈。

注意代码中栈的先进后出顺序,输出出来的就是倒序打印。

(1)先序遍历:

'''
算法1:先序遍历非递归访问,使用栈即可实现。先序遍历的非递归访问在所有的遍历中算是最简单的了。
主要思想就是先将根结点压入栈,然后根结点出栈并访问根结点,而后依次将根结点的右孩子、左孩子入栈,直到栈为空为止。
'''
def preOrder(root: TreeNode):
    if not root:
        return None
    stack=[]
    stack.append(root)
    while stack:
        ans=stack[-1]
        stack.pop()
        print(ans.val)
        # 注意栈是先进后出,所以遍历时先访问左子树再访问右子树,因此压入栈时先压右子树再压左子树
        if ans.right:
            stack.append(ans.right)
        if ans.left:
            stack.append(ans.left)
'''
'''
算法2:先序遍历的非递归算法另一算法,也是用的栈,只是稍微复杂点,当左子树遍历完后,需要回溯并遍历右子树。
'''
def preOrder(root: TreeNode):
    stack=[]
    while root or stack:
        if root:
            print(root.val) # 访问结点并入栈
            stack.append(root)
            root=root.left # 一直遍历左子树,直到左子树为空
        else:
            root=stack[-1] # 回溯父亲结点
            stack.pop()
            root=root.right  # 访问右子树    

(2)中序遍历:

'''
中序遍历非递归算法也是采用栈实现,与上面的先序遍历算法2类似,只是访问根结点的时机不同。
'''
def inOrder(root: TreeNode):
    stack=[]
    while root or stack:
        if root:
            stack.append(root) 
            root=root.left  
        else:
            root=stack[-1]  # 将所有的左子树都访问完压入栈后,再访问根结点
            print(root.val)
            stack.pop()
            root=root.right # 访问右子树

(3)后序遍历

'''
后序遍历的非递归算法较复杂,使用一个栈可以实现,但是过程很繁琐,这里可以巧妙的用两个栈来实现后序遍历的非递归算法。
注意到后序遍历可以看作是下面遍历的逆过程:即先遍历某个结点,然后遍历其右孩子,然后遍历其左孩子。
上面这个过程逆过来就是后序遍历。算法步骤如下:
1.Push根结点到第一个栈s中。
2.从第一个栈s中Pop出一个结点,并将其Push到第二个栈output中。
3.然后Push结点的左孩子和右孩子到第一个栈s中。
4.重复过程2和3直到栈s为空。
5.完成后,所有结点已经Push到栈output中,且按照后序遍历的顺序存放,直接全部Pop出来即是二叉树后序遍历结果。
'''
def postOrder(root: TreeNode):
    if not root:
        return
    stack=[]
    output=[]
    stack.append(root)
    while stack:
        cur=stack[-1]
        output.append(cur)
        stack.pop()
        if cur.left:
            stack.append(cur.left)
        if cur.right:
            stack.append(cur.right)
    
    while output:
        print(output[-1].val)
        output.pop()

层次遍历

  • 中序前序后序都是深度优先DFS,可以递归实现。层次遍历是广度优先BFS,用队列实现。
  • 深度遍历DFS的运行过程是先进后出的,自然的方法是栈和递归
  • 广度遍历BFS的运行过程是先进先出的,自然的方法是队列,一般不写递归解

参阅文章:二叉树遍历的非递归解
如果不考虑分层换行打印,则用一个队列可以很容易的通过非递归实现层序遍历。但是要每打印一层换一行,就显得稍微复杂了一点。可以有两种方法,第一种使用两个队列,代码很简练,第二种方法则是使用一个队列,代码稍显复杂。

方法一:使用两个队列

'''
第一个队列currentLevel用于存储当前层的结点,第二个队列nextLevel用于存储下一层的结点。
当前层currentLevel为空时,表示这一层已经遍历完成,可以打印换行符了。
然后将第一个空的队列currentLevel与队列nextLevel交换,然后重复该过程直到结束。这个算法比较好理解。
'''
# from queue import Queue
import queue
# Python中的队列模块queue详解见链接:https://www.cnblogs.com/itogo/p/5635629.html或https://blog.csdn.net/brucewong0516/article/details/84025027
# 队列类似于一条管道,元素先进先出,进put(arg),取get( )。put(item,[]): 将item放入队列中;get(): 从队列中移除并返回一个数据
# 需要注意的是:队列都是在内存中操作,进程退出,队列清空,另外,队列也是一个阻塞的形态。
def levelOrder1(root: TreeNode):
    if not root:
        return
    currentLevel=queue.Queue()
    nextLevel=queue.Queue()
    currentLevel.put(root)
    while currentLevel:
        cur=currentLevel.get()
        if cur: # currentLevel中只要有元素就一直访问
            print(cur.val)
            # 将cur结点的孩子们加入到下一层的队列中
            nextLevel.put(cur.left)
            nextLevel.put(cur.right)
        if currentLevel.empty(): # 当前层遍历完了,转移到下一层
            swap(currentLevel,nextLevel)

def swap(cur:queue, next:queue): # 将next队列中的node,全部put到cur队列中
    while not next.Empty():
        node=next.get()
        cur.put(node)
        

方法二:使用一个队列

# 只使用一个队列的话,需要额外的两个变量来保存当前层结点数目以及下一层的结点数目。
def levelOrder2(root:TreeNode):
    if not root:
        return
    q=queue.Queue()
    curNodeNumber=1 # 保存当前层结点数目
    nextNodeNumber=0 # 保存下一层结点数目
    q.put(root)
    while not q.empty():
        cur=q.get()
        curNodeNumber-=1
        if cur:
            print(cur.val)
            q.put(cur.left)
            q.put(cur.right)
            nextNodeNumber+=2
        if curNodeNumber==0:
            curNodeNumber=nextNodeNumber
            nextNodeNumber=0

你可能感兴趣的:(python,数据结构,算法,二叉树)