树- 二叉树

树的类型定义

在这里插入图片描述

查找类(引用型操作):

  • 求根节点
  • 求当前结点元素
  • 求双亲
  • 求最左孩子
  • 求右兄弟
  • 判空
  • 求深度
  • 遍历

插入类:

  • 初始化空树
  • 按定义构造树
  • 给当前结点赋值
  • 将以e为根的树插入结点为p的第i棵子树

删除类:

  • 清空树,结构还在
  • 销毁树的结构
  • 删除结点p的第i棵子树

有向树:

  • 有确定的根
  • 树根和子树根之间为有向关系
  • 有序树:子树之间存在确定的次序关系。
  • 无序树:子树之间不存在确定的次序关系。
    在这里插入图片描述

二叉树

二叉树的定义

概念:二叉树或为空树;或是由一个根结点加上两棵分别称为左子树和右子树的、互不相交的二叉树组成
这里约定根结点所在层数为第一层(有的书规定为第0层)
树的深度:树的最大层数。
树的高度:树的最大层数。
(注:其实深度和高度的定义并不统一,详细的看这里 )
:拥有的孩子数。
双亲结点:就是父节点(如果有,就一个,不要被字面意思迷惑)。
二叉树:度不大于2的有序树。

二叉树的五种基本形态:

  • 空树
  • 只含根节点
  • 右子树为空树
  • 左子树为空
  • 左右子树都不为空

二叉树的重要特性

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

两类特殊的二叉树

满二叉树:深度为k 且含有 2^k-1个结点的二叉树。

在这里插入图片描述

完全二叉树:

树中所含的n 个结点和满二叉树中编号为1 至n 的结点一一对应。

完全二叉树(高度为k )的性质:

  • 完全二叉树的前k−1 层为满二叉树;
  • 完全二叉树的叶子结点可能出现在k−1 层和k 层;

在这里插入图片描述
在这里插入图片描述

二叉树的遍历

问题的提出

顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次。
二叉树是非线性存储结构,每个结点有两个后继,则存在如何遍历,即按什么样的搜索路径进行遍历的问题。

对于二叉树而言,可以有三种搜索路径:

  • 先上后下的按层次遍历;
  • 先左(子树)后右(子树)的遍历;
  • 先右(子树)后左(子树)的遍历。

先左后右的遍历算法

  • 先序遍历
  • 中序遍历
  • 后序遍历

其中,先、中、后指的是根的访问顺序
先(根)序的遍历算法:遇到根就访问,然后进入左子树
中(根)序的遍历算法:遇到根先不访问,先访问左子树,等到左子树访问回来,再访问根
后(根)序的遍历算法:遇到根先不访问,先访问左子树,等到左子树访问回来,再访问右子树,最后再访问根
无论是先序、中序、后序,路径都是一样的。都是从双亲下来去根,从根去左,再回来,从根去右,再回来。即下图黄色线,整个搜索路径会有三次经过根:
在这里插入图片描述

先序遍历算法:

若二叉树为空树,则空操作;否则:

  • 访问根结点
  • 先序遍历左子树
  • 先序遍历右子树
    在这里插入图片描述
    对于上图,无论是先序、中序、后序,路径都是一样的。整个搜索路径会有三次经过根:第一次是红色线,第二次是绿色线,第三次是深蓝色线。
    不同的是先序遍历在第一次经过根的时候就访问了(红色线时访问,相对应的,结点用红色表示)
    访问顺序:A B C D E F G H K

中序遍历算法:

若二叉树为空树,则空操作;否则:

  1. 中序遍历左子树
  2. 访问根结点
  3. 中序遍历右子树
    在这里插入图片描述
    中序遍历在第二次经过根(从左子树回来)的时候访问(绿色线时访问,相对应的,结点用绿色表示)。
    访问顺序:B D C A E H G K F

后序遍历算法:

若二叉树为空树,则空操作;否则:
4. 后序遍历左子树
5. 后序遍历右子树
6. 访问根结点
在这里插入图片描述
后序遍历在第三次经过根(从左子树回来)的时候访问(深蓝色线时访问,相对应的,结点用深蓝色表示)。
访问顺序:D C B H K G F E A

算法的递归和非递归版本

先序遍历

1.递归版

#前序遍历
def preorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """

        if root == None:
            return []
        res = []
        res.append(root.val)
        res += self.preorderTraversal(root.left)
        res += self.preorderTraversal(root.right)
        
        return res


非递归

先序遍历非递归算法

  • 由先序遍历过程可知,先访问根结点,再访问左子树,最后访问右子树。
  • 因此,先将根结点进栈,在栈不为空时循环;出栈p,访问*p结点,将其右孩子结点进栈,再将左孩子结点进栈。

#非递归实现前序遍历
def preOrderNonRec(self,root):
    if root == None:
        return
    #用数组当栈
    stack = []
    node=root
    #res=[]
    while node or stack:
        while node:
            # 从根节点开始,一直找它的左子树
            print(node.data)
            #将右子树压入栈中
            #res.append(node.data)
            stack.append(node)
            #一直往下找左子树
            node = node.left
        # while结束表示当前节点Node为空,即前一个节点没有左子树了
        # 栈中开始弹出上右子树,再重复前面步骤
        node = stack.pop()
        node=node.right

	#return res

def preorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root is None:
            return []
        res = []           # LeetCode的要求非打印,而是返回一list,因而又准备了一个空list:res
        stack = []         # 思路中提及的“辅助栈”
        stack.append(root)
        while stack:
            cur = stack.pop()
            res.append(cur.val)
            if cur.right:
                stack.append(cur.right)
            if cur.left:
                stack.append(cur.left)
        return res
 


中序遍历

递归版
#中序遍历
 def inorderTraversal(self, root):
    	if root ==None:
            return[]
        res=[]
        res+=self.inorderTraversal(root.left)
        res.append(root.val)
        res+=self.inorderTraversal(root.right)
        return res


非递归

考虑中序遍历:第一次经过根没有访问,对其左子树访问完,第二次经过根时,才访问根,需要一个栈的结构来保存结点的地址。凡是递归定义的数据结构,想用非递归的方法实现,需要用户自己定义栈!
中序遍历非递归算法:

  • 由中序遍历过程可知,采用一共栈保存需要返回的结点指针,先扫描(并非访问)根结点的所有左结点并将它们一一进栈。
  • 然后出栈一个结点,显然该结点没有左孩子结点或者左孩子结点已访问过,则访问它。
  • 然后扫描该机欸但的右孩子结点,将其进栈,再扫描该右孩子结点的所有左结点并一一进栈,如此这样,直到栈空为止。
def preOrderNonRec(self,root):
    if root == None:
        return
    #用数组当栈
    stack = []
    node=root
    # res=[]
    while node or stack:
        while node:
            # 从根节点开始,一直找它的左子树
            #将父节点压入栈中
            stack.append(node)
            #一直往下找左子树
            node = node.left
        # while结束表示当前节点Node为空,即前一个节点没有左子树了
        # 栈中开始弹出上右子树,再重复前面步骤
        node = stack.pop()
        #res.append(node.data)
        print(node.data)
        node=node.right
    #return res


def preorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root is None:
            return []
        res = []           # LeetCode的要求非打印,而是返回一list,因而又准备了一个空list:res
        stack = []         # 思路中提及的“辅助栈”
        stack.append(root)
        while stack:
            cur = stack.pop()
            res.append(cur.val)
            if cur.right:
                stack.append(cur.right)
            if cur.left:
                stack.append(cur.left)
        return res
 

后续遍历

递归版
def postorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        
        if root == None:
            return []
        res=[]
        res+=self.postorderTraversal(root.left)
        res+=self.postorderTraversal(root.right)
        res.append(root.val)
        return res

非递归
def post_order_stack(self, root):  # 堆栈实现后序遍历(非递归)
        # 先遍历根节点,再遍历右子树,最后是左子树,
        #这样就可以转化为和先序遍历一个类型了,最后只把遍历结果逆序输出就OK了。
        if not root:
            return
        myStack1 = []
        myStack2 = []
        node = root
        while myStack1 or node:
            while node:
                myStack2.append(node)
                myStack1.append(node)
                node = node.right
            node = myStack1.pop()
            node = node.left
        while myStack2:
            print (myStack2.pop().data)


def postorderTraversal(self, root):
        """
        :type root: TreeNode
        :rtype: List[int]
        """
        if root is None:
            return []
        res = []              # 栈2
        stack = []            # 栈1
        stack.append(root)    # 当前节点“中”最先压栈2
        while stack:
            cur = stack.pop()   
            res.append(cur)
# 当前节点的孩子节点按“左右”次序压栈
#1。最终按“先右后左”顺序弹栈并压进栈
#2,致使栈2从上到下的顺序为“左、右、中”,这也正是后序遍历打印顺序
            if cur.left:        
                stack.append(cur.left)
            if cur.right:
                stack.append(cur.right)
        final = []             # 返回答案用的空list
        while res:
            final.append(res.pop().val)
        return final


构造二叉树

二叉树的先序和中序序列建树

给先序和后序不能建树,为什么?先序是根左右,中序是左根右。所以先序是得到根,然后代入中序,得到左右划分,进而确定左右子树的根。
跟后序和中序来建树的道理是一样的。
因此,给先序和后序只能确定根在哪,无法确定左和右的划分,不能唯一的确定二叉树。
例子:仅知道二叉树的先序序列"abcdefg"不能唯一确定一棵二叉树,如果同时已知二叉树的中序序列"cbdaegf",则会如何?
在这里插入图片描述
通过二叉树的先序序列确定根是谁,把根代入中序序列得到左右划分,然后回到先序序列的左子树(右子树)确定谁是根,再将根代入中序序列得到左子树(右子树)的左右划分。

在这里插入图片描述
如上图,在先序序列先确定根结点a,然后代入中序序列得到左右划分cbd和egf
在这里插入图片描述
继续划分,找到根b,然后根c,c左子树为空,然后右子树为空。返回上一层b,找右子树d,左子树为空,然后右子树为空,再放回上一层a,a的右子树为空,再返回,到达根a。
在这里插入图片描述

 def tree_by_pre_mid(self, pre, mid):
        if len(pre) != len(mid) or len(pre) == 0 or len(mid) == 0:
            return
        T = NodeTree(pre[0])
        index = mid.index(pre[0])
        T.lchild = self.tree_by_pre_mid(pre[1:index + 1], mid[:index])
        T.rchild = self.tree_by_pre_mid(pre[index + 1:], mid[index + 1:])
        return T

中序和后序
def tree_by_post_mid(self, post, mid):
        if len(post) != len(mid) or len(post) == 0 or len(mid) == 0:
            return
        T = NodeTree(post[-1])
        index = mid.index(post[-1])
        T.lchild = self.tree_by_post_mid(post[:index], mid[:index])
        T.rchild = self.tree_by_post_mid(post[index:-1], mid[index + 1:])
        return T

算法的应用

树的声明
class Node:
 
    def __init__(self,data,left=None,right=None):
        self.data=data
        self.left=left
        self.right=right

统计树的节点

def countNode(self,t):
    if t is None:
        return 0
    else:
        return 1+self.countNode(t.left)+self.countNode(t.right)

树的深度
def TreeDepth(self, pRoot):
        # write code here
        if pRoot == None:
            return 0
        lDepth = self.TreeDepth(pRoot.left)
        rDepth = self.TreeDepth(pRoot.right)
        return max(lDepth,rDepth)+1
复制二叉树(后序遍历)

采用后序遍历:先复制左子树,后复制右子树,最后确定根。

生成一个二叉树的结点(其数据域为item,左指针域为lptr,右指针域为rptr)


# 对二叉树进行拷贝
# 先对分别对左右子树进行拷贝
# 再把其作为新的树的左右子树
 

class BiTNode:
    def __init__(self):
        self.data = None
        self.lchild = None
        self.rchild = None
 
def createDupTree(root):
    if root == None:
        return None 
    # 二叉树根结点
    dupTree = BiTNode()
    dupTree.data = root.data 
    # 复制左子树
    dupTree.lchild = createDupTree(root.lchild)
    # 复制右子树
    dupTree.rchild = createDupTree(root.rchild)




按层次遍历二叉树

  • 由层次遍历的定义可知,在进行层次遍历时,对一层结点访问完后,再按照它们的访问次序对各个结点的左孩子和右孩子顺序访问,这样一层一层进行,先遇到的结点先访问,这与队列的操作原则比较吻合。

  • 因此,在进行层次遍历时,可设置一个队列结构,遍历从二叉树根结点开始,先将根结点指针入队列,然后从队头取出一个元素,每取一个元素,执行下面两个操作

    1. 访问该元素所指结点

    2. 若该元素所指结点的左、右孩子结点非空,则将该元素所指结点的左孩子指针和右孩子指针顺序入队。

此过程不断进行,当队列为空时,二叉树的层次遍历结束。

class Solution:
    # 返回从上到下每个节点值列表,例:[1,2,3]
    def PrintFromTopToBottom(self, root):
        # write code here
        res=[]
        queue=[root]
        while queue and root:
            node=queue.pop(0)
            res.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        return res

小结:

  • 若先序序列与后序序列相同,则或为空树,或为只有根结点的二叉树。
  • 若中序序列与后序序列相同,则或为空树,或为任一结点至多只有左子树的二叉树。
  • 若先序序列和中序序列相同,则或为空树,或为任一结点至多只有右子树的二叉树。
  • 若中序序列与层次序列相同,则或为空树,或为任一结点至多只有右子树的二叉树
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值