数据结构与算法8:树、二叉树及其遍历

树的定义

  • 定义:树是n个节点的有限集。n=0时称为空树。在任何一棵非空树中:(1)有且仅有一个特定的节点称为根节点(root);(2)当n>1时,除根节点外的其他节点可分为m个互不相交的有限集T1,T2,…,Tm,其中每个集合本身又是一棵树,称为根(root)的子树(SubTree)。
  • 特点:树是一种一对多的数据结构。
  • 其他概念
    结点拥有的子树数称为结点的度(Degree)树的度是树内结点的度的最大值。度为0的结点称为树的叶子结点(当树只有一个节点时,这个节点既是根节点也是叶子结点)。
    结点的子树的根称为结点的孩子节点,该结点称为孩子节点的双亲结点,同一个双亲结点的孩子节点互称为兄弟结点
    结点的祖先是从根结点到该结点所经分支上的所有节点,反之,以某结点为根结点的子树中的所有节点都称为该结点的子孙
  • 结点分类
    • 根节点:无双亲结点,当树不为空时唯一存在
    • 中间节点:一个双亲结点,多个孩子结点
    • 叶子节点:一个双亲结点,无孩子结点,度为0
  • 高度、深度和层数
    • 结点的高度:结点到叶子结点的最长路径,树的高度=根节点的高度(最底层叶子结点的高度为0)
    • 结点的深度:根节点到这个结点所经历的边的个数(根节点的深度为0)
    • 结点的层数:结点的深度+1(根节点为第一层)

树的抽象数据类型

ADT tree
Data
	有一个根节点和若干子树构成,结点具有相同的数据结构和层次关系。
Operation
	init_tree():初始化树
	create_tree():构造树
	is_empty(T):判断树T是否为空
	depth(T):返回树的深度
	root(T):返回树的根节点
	get_value(T, node):返回树T结点node的值
	set_value(T, node, value):将树T结点node的值设为value
	parent(T, node):返回树T结点node的双亲结点
	left_child(T, node):返回树T结点node的第一个孩子节点
	right_sibling(T, node):返回树T结点node的第一个右兄弟结点
	insert_child(T, node, i, c):将c插入到树T的node结点的第i棵子树
	delete_child(T, node, i):删除树T结点node的第I棵子树

树的存储结构

  • 双亲表示法:
    • 用数组保存树的结点,对每一个节点,使用parent指针指向其双亲的位置(在数组中的下标),根结点的parent设为-1。
    • 查找双亲复杂度为O(1),查找孩子复杂度为O(n)。
  • 孩子表示法:
    • 多重链表表示法:对每个结点设置多个指针域,每个指针指向一棵子树的根结点
    • 方案一:指针域的个数等于树的度数
      • 树的各个结点的度相差很小时比较有效,各节点的度相差很大时浪费空间。
    • 方案二:指针域的个数等于结点的度,需要开辟一个位置存储指针域的个数
      • 提高了空间利用率,但维护起来困难。
    • 孩子表示法:使用顺序表存储每个节点,使用单链表存储每个节点的孩子节点
      • 查找某个节点的兄弟:O(1)
      • 查找某个节点的孩子:O(1)
      • 查找某个节点的双亲:O(n)
  • 双亲孩子表示法:在孩子表示法的基础上为每个节点增加一个parent指针
  • 孩子兄弟表示法:
    • 对任意一棵树中的任意一个结点,它的第一个孩子如果存在则唯一,它的右兄弟如果存在也唯一.使用firstchild和rightlib指针分别指向它们.
    • 查找某个节点的右兄弟:O(1)
    • 查找某个节点的左兄弟:O(n)
    • 查找某个节点的孩子:O(1)
    • 查找某个节点的双亲:O(n)
    • 好处:将一颗复杂的树变为了一颗二叉树

二叉树

  • 定义:每个结点最多有两个子结点
  • 特点:
    • 每个节点最多有2棵子树,每个节点的度最大为2。
    • 每个结点的两个子树,称为左子树和右子树。
  • 特殊二叉树
    • 斜树:每一层只有一个结点,结点个数和树的深度相等,分为左斜树和右斜树
    • 满二叉树:所有非叶子节点都存在左子树和右子树,所有叶子节点都位于最后一层
    • 完全二叉树:若对于有n个节点的二叉树按层编号,其编号与同样深度的满二叉树的编号位置相同,则成为完全二叉树
    • 扩充二叉树:对于一般的二叉树,需要知道其前(后)序遍历顺序和中序遍历顺序才能确定一棵二叉树。扩充二叉树是将所有节点的度都设置为2,对于度为1的结点添加一个空节点,对于度为0的叶子结点添加两个空节点。可以从扩充二叉树的前(后)序遍历顺序确定一棵二叉树。
    • 平衡二叉树
  • 二叉树的性质:
    • i层最多有 2 i − 1 2^{i-1} 2i1个结点
    • 深度为k的二叉树最多有 2 k − 1 2^k-1 2k1个节点,即满二叉树
    • n0,n1,n2为二叉树T中度为0,1,2的结点个数,树T结点总数为n,则满足以下条件:
      1. n0+n1+n2=n
      2. 总结点数=度数之和+1,n1+2*n2 = n-1
      • n0=n2+1
  • 完全二叉树的性质:
  1. 具有n个结点的完全二叉树的深度为:floor(log2(n))+1
  2. 对于结点i,若i=1,则为根节点无双亲;若i>1,其双亲为结点floor(i/2)
  3. 对于结点i,若i>n/2,则结点i为叶子节点;若i<=(n-1)/2,则结点i为非叶子结点。
  4. 对于非叶子结点i,其左孩子结点为2i,右孩子结点为2i+1;若(n-1)/2<i<=n/2,则结点i只有左孩子2i,无右孩子。
  5. 对于完全二叉树T来说,其第i层的结点范围为 [ 2 i − 1 , 2 i − 1 ] [2^{i-1}, 2^i-1] [2i1,2i1]
  • 二叉树的存储结构
    • 基于数组的顺序存储法:适合于完全二叉树的存储,根节点存储在下标i=1的位置,左子节点存储在2i的位置,右子结点存储在2i+1的位置。
    • 基于二叉链表的l链式存储法:适合非完全二叉树的存储,使用数据域data存储每个结点的数据,leftright作为指针域存储每个结点的左右孩子。

二叉树的遍历

二叉树的遍历是指从根节点开始,按照某种次序依次访问二叉树的所有结点,使每个结点被访问且仅被访问一次。二叉树的遍历分为深度优先遍历和广度优先遍历,其中深度优先遍历包括前中后序遍历,是一个递归的过程;广度优先遍历为层序遍历,采用队列实现。
- 前序遍历:根结点->左子树->右子树
- 中序遍历:左子树->根结点->右子树
- 后序遍历:左子树->右子树->根结点
- 层序遍历:从上到下,从左到右

from typing import List
from queue import Queue


class TreeNode:
    """二叉树结点"""
    def __init__(self, value):
        self.val = value
        self.left = TreeNode(None)
        self.right = TreeNode(None)


class Tree:
    def __init__(self):
        pass

    def create_binary_tree_with_array(self, nodes: List[int]):
        """从数组构建二叉树"""
        pass

    def create_binary_tree_with_linked_list(self, nodes: List[int]):
        """从链表构建二叉树,链表节点顺序为二叉树前序遍历的结果"""
        if not nodes:
            return None

    def preorder_traversal(self, root: TreeNode) -> List[int]:
        """递归实现前序遍历
        打印遍历结果的话不需要帮助函数,返回遍历结果的话需要帮助函数
        """
        res = []
        def helper(root):
            if not root:
                return
            res.append(root.val)
            helper(root.left)
            helper(root.right)
        helper(root)
        return res

    def preorder_traversal_with_stack(self, root: TreeNode) -> List[int]:
        """非递归实现前序遍历
        从根节点开始,弹出栈顶元素,并将其孩子节点入栈,先入右孩子,再入左孩子
        """
        if root == None:
            return []
        stack = []
        res = []
        stack.append(root)
        while stack:
            root = stack.pop()
            res.append(root.val)
            if root.right:
                stack.append(root.right)
            if root.left:
                stack.append(root.left)
        return res

    def inorder_traversal(self, root: TreeNode) -> List[int]:
        """递归实现中序遍历"""
        res = []
        def helper(root):
            if not root:
                return
            helper(root.left)
            res.append(root.val)
            helper(root.right)
        helper(root)
        return res

    def inorder_traversal_with_stack(self, root: TreeNode) -> List[int]:
        """非递归实现中序遍历
        根节点入栈,左孩子节点入栈,当无左孩子时栈顶元素出栈,当有右孩子时右孩子入栈,无右孩子时
        """
        stack = []
        res = []
        while root or stack:
            # 根节点及其左子节点进栈
            while root:
                stack.append(root)
                root = root.left
            # 左子节点及根节点出栈
            root = stack.pop()
            res.append(root.val)
            # 遍历当前根结点的右子树
            root = root.right
        return res

    def postorder_traversal(self, root: TreeNode) -> List[int]:
        """递归实现后序遍历"""
        res = []
        def helper(root):
            if not root:
                return
            helper(root.left)
            helper(root.right)
            res.append(root.val)
        helper(root)
        return res

    def postorder_traversal_with_stack(self, root: TreeNode) -> List[int]:
        """非递归实现后序遍历
        类似前序遍历(先入左孩子,再入右孩子),遍历结果逆序
        """
        if root == None:
            return []
        stack = []
        res = []
        stack.append(root)
        while stack:
            root = stack.pop()
            res.append(root.val)
            if root.right:
                stack.append(root.right)
            if root.left:
                stack.append(root.left)
        return res

    def level_order_with_queue(self, root: TreeNode) -> List[List[int]]:
        """层序遍历"""
        queue = Queue()  # 保存要遍历的结点
        res = []
        queue.put(root)
        while queue:
            node = queue.get()
            res.append(node.val)
            if node.left:
                queue.put(node.left)
            if node.left:
                queue.put(node.right)
        return res

二叉树经典题目

  • 前中后序遍历(递归实现和基于栈的非递归实现),Leetcode 94,144,145
  • 使用队列实现二叉树的层序遍历和锯齿形层序遍历,leetcode 102,103
  • 翻转二叉树(递归实现和基于队列的非递归实现),leetcode 226
  • 中序遍历和后(前)序遍历树构造二叉树(递归实现),leetcode 105,106
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值