数据结构与算法--python版-03 树及二叉树

本文介绍了树的基本概念,包括树的定义、度、深度等,并详细讲解了二叉树的特性、完全二叉树和满二叉树的区别。同时,讨论了二叉树的顺序和链式存储方式,以及中序、前序和后序遍历的递归和迭代实现。此外,还提到了二叉排序树和平衡树(如AVL树)的概念,强调了平衡树在保持高效查找性能上的重要性。
摘要由CSDN通过智能技术生成

树的概念

  • 一个前驱节点,多个后继节点的数据结构;
  • 树是n个节点的有限集合(n>=0),n为0的树为空树;
  • 非空树中,只有一个根节点,其余节点又可分为多个不相交的集合,形成子树;
  • 每个节点的分支数,称为该节点的度;所有节点的度的最大值,为该树的度
  • 同一层为兄弟节点,上层节点为祖先节点或者根节点,下层节点为子孙节点;
  • 从根节点开始,树的层数为树的深度;

树的表示

  • 双亲表示
    从根开始,用编号表示每个节点,节点对象存储数据和父节点
    在这里插入图片描述
  • 孩子表示
    在这里插入图片描述
  • 增加度(节点度)的孩子表示法
    在这里插入图片描述
  • 数组+单链表
    数组存储每个节点,每个节点在指向包含所有子节点的单链表
    在这里插入图片描述
  • 孩子兄弟表示
    在这里插入图片描述

二叉树

  • 二叉树是树的一种特殊形式;使用比较多
  • 树的度最多为2,即最多有两个分支;所有的树都可以转为唯一的二叉树
  • 二叉树是有序树,所有节点都要区分是左子树、还是右子树;即使一个节点也要区分,而树在一个节点时 就不用区分;
  • 5种基本形态:空二叉树、只有一个根的二叉树、根+左子树、根+右子树、根+左右子树;
  • 性质
    • 非空二叉树 叶子节点数 n 0 {n_0} n0 = 度为2的节点数 n 2 {n_2} n2 + 1

    • 非空二叉树,第i层最多有 2 i − 1 {2^{i-1}} 2i1个节点
      在这里插入图片描述

    • 在一个深度为k的二叉树中,最多有 2 k − 1 {2^k - 1} 2k1个节点(等比数列)

    • n个节点的完全二叉树中,最大深度为 l o g 2 ( n ) log_2(n) log2(n) 向下取整 + 1

    • n个节点的完全二叉树中,从上到下、从左到右开始编号(i=1开始),i=1表示根节点,i>1时,i//2 表示其双亲节点;若2i<=n 则左孩子为2i,否则i为叶子节点(无左孩子); 若2i+1 <=n 则 i 的右孩子为2i+1 ,否则无右孩子;
      在这里插入图片描述

  • 满二叉树
    • 每个非叶子节点都有两个分支;
    • 所有叶子节点都在同一层;
    • 即深度为k的二叉树,有 2 k − 1 {2^k - 1} 2k1个节点的二叉树
      在这里插入图片描述
  • 完全二叉树
    • 满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树;
    • 完全二叉树从上到下,从左到右依次对节点编号,与满二叉树编号对应;
    • 除最后一层外,以上所有层都是满的,最后一层叶子节点要么满、要么集中在最左边;所有叶子节点在最后一层或者倒数第二层。
    • 完全二叉树可以使用数组存储,根节点索引为 i {i} i,左子节点为 2 ∗ i + 1 {2*i + 1} 2i+1,右子节点为 2 ∗ i + 2 {2*i + 2} 2i+2
    • 最后一个非叶子的节点为 n / 2 − 1 {n/2 -1 } n/21,n为数组长度;
      在这里插入图片描述
    • 二叉树表示
      在这里插入图片描述

二叉树存储

顺序存储

顺序存储只适合完全二叉树,如,它是一个存在数组中的完全二叉树。

链式存储

二叉树中,一个节点存储数据、左孩子指针、右孩子指针

二叉树的遍历

中序遍历

  • 遍历左子树,根节点,遍历右子树;
  • 左子树中再递归地解决左子节点、左子树的根节点、右子节点;

递归实现


class TreeNode(object):
    def __init__(self, data, left=None, right=None):
        self.data = data
        self.left = left
        self.right = right

# 左子树
g = TreeNode("g")
h = TreeNode("h")
d = TreeNode('d', left=g, right=h)
b = TreeNode("b", left=d)
# 右子树
i = TreeNode("i")
e = TreeNode('e', right=i)
f = TreeNode("f")
c = TreeNode("c", left=e, right=f)

# 根节点
a = TreeNode("a", left=b, right=c)

#		b
#	d		 c
# g	   h   e   f
#			i

# 中序遍历
# 左子树、root、右子树
def visit_tree(root):
    if root is None: # java中为null
        return

    # 访问左子树
    visit_tree(root.left)
    # 访问根节点的值
    print(root.data)
    # 访问右子树
    visit_tree(root.right)


if __name__ == '__main__':
    visit_tree(a)

循环迭代
借助栈,每个root根节点压栈;处理时弹栈;


if __name__ == '__main__':
    #
    stack = []
    root = a
    while root or stack:
        while root:
            # 根节点入栈
            stack.append(root)
            root = root.left
        root = stack.pop()
        print(root.data)
        root = root.right

中序遍历:g,d,h,b,a,e,i,c,f

java循环
在这里插入图片描述
 

前序遍历

  • 先访问根节点,再先序遍历左子树、最后先序遍历右子树;
  • a,b,d,g,h,c,e,i,f

递归实现

# 先序遍历
def first_travel(root):
    if root is None:
        return
    print(root.data)
    first_travel(root.left)
    first_travel(root.right)

循环迭代

def stack_first_travel(root):
    if root is None:
        return
    stack = []

    # 先序处理
    while root or stack:
        while root:
            # 先序遍历根
            print(root.data, end=",")
            # 右子树 入栈
            if root.right:
                stack.append(root.right)
            # 先序遍历左子树
            root = root.left

        # 右子树出栈
        if stack:
            root = stack.pop()

 

后序遍历

  • 左子树、右子树、根节点
    递归实现
def last_travel(root):
    if root is None:
        return
    last_travel(root.left)
    last_travel(root.right)
    print(root.data)

练习

分别输出一下二叉树的先序遍历中序遍历后序遍历
在这里插入图片描述
 
 
在这里插入图片描述
 

二叉树的恢复

  • 任意一棵二叉树的先序遍历、中序遍历都是唯一的;

  • 已知节点的先序序列和中序序列,则可以唯一确定这棵二叉树;

    • 由先序序列,逐一获取每个根节点或子树的根节点;
    • 在中序序列中,用每个根节点,划分左右子树;
    • 再取一个根节点,递归地划分左右子树。
      例如:
      一棵二叉树的先序序列 abcdefghi; 中序序列bcaedghfi;恢复该二叉树。
      在这里插入图片描述
      先、中可以恢复;
      后、中可以恢复;
      编码实现恢复;
  • 先序+中序 唯一恢复二叉树

# 递归恢复二叉树
def restore_btree(mid_seq): # 中序遍历序列或者子集
    global first_seq # 先序遍历全局
    if not mid_seq:  # 中序遍历的分治部分
        return None
    elif len(mid_seq) == 1:
        return TreeNode(mid_seq[0])
    while first_seq:
        first_data = first_seq.pop(0)
        if first_data in mid_seq:
            root = TreeNode(first_data)
            idx = mid_seq.index(first_data)
            mid_left = mid_seq[0 : idx]
            mid_right = mid_seq[idx + 1 : ]
            root.left = restore_btree(mid_left) # 根据中序左子集创建 左子树
            root.right = restore_btree(mid_right) # 根据中序右子集创建 右子树
            return root


if __name__ == '__main__':
    first_seq = list("abcdefghi")
    mid_seq = list("bcaedghfi")
    root = restore_btree(mid_seq)

    first_travel(root)
  • 先序遍历统计二叉树叶子节点数;
def count_leaf(root):
    global num
    if root is None:
        return num
    elif root.left is None and root.right is None:
        num += 1
        return num

    if root.left:
        count_leaf(root.left)
    if root.right:
        count_leaf(root.right)

    return num


num = 0
count_leaf(root)
print("total leafs:", num)

 

构建链式二叉树

根据列表数据,构建链式二叉树;
[“a”, “b”, “d”, None, “f”, None, None, None, “c”, “e”, None, None, None]

class Node:
    def __init__(self, data, left=None, right=None):
        self.data = data
        self.left = left
        self.right = right


def create_bt(): # 创建二叉树
    global alist
    # 先序 递归 创建二叉树
    if alist:
    	data = alist.pop(0)
    	if data is None:
        	return root  # 空树
    else:
    	return None
    
    # 先创建根节点
    root = Node(data)
    # 创建左子树
    root.left = create_bt()
    # 最后创建右子树
    root.right = create_bt()

    return root


# 先序遍历
def first_travel(root):
    if root is None:
        return
    print(root.data)
    first_travel(root.left)
    first_travel(root.right)


if __name__ == '__main__':
    alist = ["a", "b", "d", None, "f", None, None, None, "c", "e", None, None, None]

    root = create_bt()
    first_travel(root)

创建的二叉树如图:

在这里插入图片描述
先序遍历:a b d f c e
中序遍历:d,f,b,a,e,c
后序遍历:f,d,b,e,c,a

 

二叉排序树

Binary Search Tree,也叫二叉搜索树;没有重复值;

  • 左子节点的值 小于根节点,根节点值小于右节点;
  • 左子树中同样满足;
  • 右子树中同样满足;
  • 二叉搜索树 查找的时间复杂度为 O ( l o g 2 n ) {O(log_{2}n)} O(log2n)
    • 假设树为完全二叉树,且查找 x x x次才找到目标,则相当于查的树深度为 x x x,则树节点数n满足 n < = 2 x − 1 n <= 2^x - 1 n<=2x1 n > = 2 x − 1 n>=2^{x-1} n>=2x1

    • 通过取以2为底的对数得到 l o g 2 ( n + 1 ) = < x < = l o g 2 n + 1 log_2(n+1) =< x <= log_2n +1 log2(n+1)=<x<=log2n+1

    • 所以时间复杂度 l o g 2 n log_2n log2n;也可以每次除2的方式理解, n / 2 x = 1 n/2^x = 1 n/2x=1,即到达叶子节点;

    • 当二叉搜索树为倾斜树时,最坏时间复杂度为 O ( n ) O(n) O(n)
      在这里插入图片描述

    • 所以二叉搜索树的性能与树高有关;

# 定义二叉排序树的节点
class TreeNode:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
 
# 构建二叉排序树
def insert_node(root, val):
    if root is None:
        return TreeNode(val)
    if val > root.val:
        root.right = insert_node(root.right, val)
    else:
        root.left = insert_node(root.left, val)
    return root
 
# 中序遍历二叉排序树(升序)
def in_order_traversal(root):
    if root is None:
        return []
    res = []
    res.extend(in_order_traversal(root.left))
    res.append(root.val)
    res.extend(in_order_traversal(root.right))
    return res
 
# 示例:构建一个二叉排序树
root = None
nums = [5, 2, 7, 1, 3, 6, 8, 4, 9]
for num in nums:
    root = insert_node(root, num)
 
# 输出二叉排序树的中序遍历结果
print(in_order_traversal(root))

 

平衡树

为了保证二叉排序树的中序遍历有序性,会导致树的不平衡;
为了提高查询性能,需要避免极度不平衡的树(倾斜树);

  • 理想平衡,左右子树的高度一样,需要耗费时间调整平衡;

  • 适度平衡,左右子树的高度大致一样;

    • AVL平衡二叉(查找)树,是一种特殊的二叉排序树;左右子树的高度差绝对值 < = 1 <=1 <=1;左右子树也是平衡二叉树;
      • 平衡因子,左子树的高度减去右子树的高度;

      • 当插入数据导致不平衡时,只需从下向上依次调整最小的不平衡子树;在这里插入图片描述

      • 调整平衡方法,LL型、RR型、LR型、RL型;

      • LL型,当一个节点的平衡因子不在(-1,0, 1)时,即出现了不平衡,向其左子树方向(高度大的一侧)走两次,以中间节点为轴,以走过的边为左右子树的指针,顺时针旋转,断开的节点连接到初始的树根节点。
        在这里插入图片描述
        在这里插入图片描述
         

        • LR型,向高度大的左子树走一步L,向导致不平衡的右子树走一步R,(L边先断开)以LR中间的节点逆时针旋转(断开的节点连接初始的根),旋转后的树根挂载到L边;然后再LL调整。(LR->逆时针->LL->顺时针
          在这里插入图片描述
      • RL型,(RL->顺时针->RR-逆时针
        在这里插入图片描述

      • 平衡二叉树插入
        在这里插入图片描述
        如下完成平衡二叉树的插入,思路:从下往上调整最小不平衡子树
        在这里插入图片描述
        将最小不平衡子树调整为:
        在这里插入图片描述

      • 平衡二叉树删除
        1.如果平衡二叉树为空,则返回;
        2.如果T.data==x,查找成功,若T节点有一个孩子为空,删除T节点后,其孩子代替其位置;若T有两个孩子,则删除T后,令T的直接前驱(直接后驱)代替它,并删除其直接前驱(直接后驱);直接前驱-左子树的最右叶子节点;直接后驱-右子树的最左叶子节点
        3.如果T.data<x 或者>x 则到对应的右/左子树中删除;
        4.调整平衡;
        删除案例1:直接删除该节点,调整平衡;
        在这里插入图片描述
        最后树变为:
        在这里插入图片描述
         
        删除案例2
        在这里插入图片描述
        删除后右子树的结果:
        在这里插入图片描述

     

    • 树堆Treap,特殊的二叉排序树;
    • 红黑树(理解),二叉排序树中左右子树高度差不超2倍;
      • 根黑,叶子(Null)黑,内部节点为黑或者红色;红父必黑孩子;任意节点到所有叶子的路径上黑节点数相同;

      • 构建红黑树练习 在这里插入图片描述
        思路:根为黑,父节点为黑,直接插入;父节(p)点为红,且父节点的兄弟(u)为红,即双红修正–将p&u变黑,p&u的父节点(g)变红,并将g看作是新插入的节点,继续向上处理;若p红u黑,查看g->x路径执行LL/RR/LR/RL旋转,新根变黑,两个孩子变红;
        1.插入12,为黑色根;
        在这里插入图片描述
        2.插入16,根黑,则直接插入(红节点);
        在这里插入图片描述
        3.根黑,直接插入2(红色);
        在这里插入图片描述
        4.输入30,p&u 为双红,双红修正
        在这里插入图片描述
        5.输入28,非双红要旋转,新根变黑,双子变红;
        在这里插入图片描述
        6.插入20, 双红修正,p&u变黑,g变红,并将g作为一个新插入节点,继续向上处理;父节点为黑,则直接插入g;
        在这里插入图片描述
        7.插入60、29,父节点为黑,直接插入;
        在这里插入图片描述
        8.插入85,双红修正;
        在这里插入图片描述
        在这里插入图片描述

        最终结果:
        在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

laufing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值