【数据结构6】平衡二叉树(AVL树)、AVL树的旋转-左旋和右旋、二叉搜索树的扩展应用-B树(B-Tree)、B+树

1 平衡二叉树(AVL树)
2 AVL树的旋转-左旋和右旋
2.1 AVL树的旋转实现
3 二叉搜索树的扩展应用-B树
3.2 B+树

1 AVL树

AVL树:AVL树是一棵自平衡的二叉搜索树。

AVL树具有以下性质:
	根的左右子树的高度之差的绝对值不能超过1
	根的左右子树都是平衡二叉树

平均情况下,二叉搜索树进行搜索的时间复杂度为O(lgn)。
最坏情况下,二又搜索树可能非常偏斜。

解决方案:
	1 随机化插入
	2 D AVL树

在这里插入图片描述

2 AVL树的旋转-左旋和右旋

1 插入一个节点可能会破坏AVL树的平衡,可以通过"旋转"操作来进行修正
2 插入一个节点后,只有从插入节点到根节点的路上的节点的平衡可能被改变。
	需要找出第一个破坏了平衡条件的节点,称之为K。K的两颗子树的高度差23 不平衡的出现可能有4种情况

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.1 AVL树的旋转实现

class BiTreeNode:
    def __init__(self, data):
        """
        初始化二叉树节点
        :param data: 节点的值
        """
        self.data = data  # 节点的值
        self.lchild = None  # 左子节点
        self.rchild = None  # 右子节点
        self.parent = None  # 父节点


class BST:
    def __init__(self, li: list):
        """
        初始化二叉搜索树,并插入给定的值
        :param li: 包含插入值的列表
        """
        self.root = None  # 初始化根节点为空
        if li:
            for val in li:
                self.insert_no_rec(val)  # 使用非递归插入方法将列表中的值插入树中

    def insert_no_rec(self, val):
        """
        非递归插入节点到二叉搜索树中
        :param val: 要插入的值
        :return: None
        """
        p = self.root
        if not p:
            # 如果树为空,将新节点设置为根节点
            self.root = BiTreeNode(val)
            return
        while True:
            if val < p.data:
                # 如果要插入的值小于当前节点的值,则移动到左子树
                if p.lchild:
                    p = p.lchild
                else:
                    # 如果左子树为空,则在此位置插入新节点
                    p.lchild = BiTreeNode(val)
                    p.lchild.parent = p  # 更新新节点的父节点
                    return
            elif val > p.data:
                # 如果要插入的值大于当前节点的值,则移动到右子树
                if p.rchild:
                    p = p.rchild
                else:
                    # 如果右子树为空,则在此位置插入新节点
                    p.rchild = BiTreeNode(val)
                    p.rchild.parent = p  # 更新新节点的父节点
                    return
            else:
                # 如果要插入的值等于当前节点的值,则不做任何操作(BST中不允许重复值)
                return

    def query_no_rec(self, val):
        """
        非递归查询二叉搜索树中的节点
        :param val: 要查询的值
        :return: 节点对象(如果找到)或 None(如果未找到)
        """
        p = self.root
        while p:
            if p.data < val:
                # 如果当前节点的值小于要查询的值,则移动到右子树
                p = p.rchild
            elif p.data > val:
                # 如果当前节点的值大于要查询的值,则移动到左子树
                p = p.lchild
            else:
                # 如果当前节点的值等于要查询的值,返回当前节点
                return p
        # 如果遍历结束仍未找到值,返回 None
        return None

    def pre_order(self, root):
        """
        二叉树的前序遍历
        :param root:
        :return:
        """
        if root:
            print(root.data, end=',')
            self.pre_order(root.lchild)
            self.pre_order(root.rchild)

    def mid_order(self, root):
        """
        二叉树的中序遍历
        :param root:
        :return:
        """
        if root:
            self.mid_order(root.lchild)
            print(root.data, end=',')
            self.mid_order(root.rchild)

    def post_order(self, root):
        """
        二叉树的后序遍历
        :param root:
        :return:
        """
        if root:
            self.post_order(root.lchild)
            self.post_order(root.rchild)
            print(root.data, end=',')


class AVLNode(BiTreeNode):
    def __init__(self, data):
        BiTreeNode.__init__(self, data)
        self.bf = 0  # balance_factor


class AVLTree(BST):
    def __init__(self, li: list = None):
        BST.__init__(self, li)

    def insert_no_rec(self, val):
        """
        插入节点到二叉搜索树中
        :param val: 要插入的值
        :return: None
        """
        # 1.插入节点
        p = self.root
        if not p:
            # 如果树为空,将新节点设置为根节点
            self.root = AVLNode(val)
            return
        while True:
            if val < p.data:
                # 如果要插入的值小于当前节点的值,则移动到左子树
                if p.lchild:
                    p = p.lchild
                else:
                    # 如果左子树为空,则在此位置插入新节点
                    p.lchild = AVLNode(val)
                    p.lchild.parent = p  # 更新新节点的父节点
                    node = p.lchild  # node 存储的就是插入的节点
                    break
            elif val > p.data:
                # 如果要插入的值大于当前节点的值,则移动到右子树
                if p.rchild:
                    p = p.rchild
                else:
                    # 如果右子树为空,则在此位置插入新节点
                    p.rchild = AVLNode(val)
                    p.rchild.parent = p  # 更新新节点的父节点
                    node = p.rchild
                    break
            else:  # val = p.data
                # 如果要插入的值等于当前节点的值,则不做任何操作(BST中不允许重复值)
                return
        # 2.更新balance factor
        while node.parent:  # node.parent不为空
            if node.parent.lchild == node:  # 传递是从左子树来的,左子树更沉了
                # 更新node.parent的bf -= 1
                if node.parent.bf < 0:  # 原来的node.parent.bf == -1,更新变成-2
                    # 做旋转
                    # 看node哪边沉
                    g = node.parent.parent  # 为了连接旋转之后的子树
                    x = node.parent  # 旋转前的子树的根
                    if node.bf > 0:  # 右边沉
                        n = self.rotate_left_right(node.parent, node)
                    else:  # 左边沉
                        n = self.rotate_right(node.parent, node)
                    # 记得把n和g连起来
                elif node.parent.bf > 0:  # 原来node.parent.bf = 1,更新之后变成0
                    node.parent.bf = 0
                    break
                else:  # 原来的node.parent.bf = 0,更新之后变成-1
                    node.parent.bf = -1
                    node = node.parent
                    continue
            else:  # 传递是从右子树来的,右子树更沉了
                # 更新node.parent.bf+=1
                if node.parent.bf > 0:  # 原来node.parent.bf = 1,更新之后变成2
                    # 做旋转
                    # 看哪边沉
                    g = node.parent.parent  # 为了连接旋转之后的子树
                    x = node.parent  # 旋转前的子树的根
                    if node.bf < 0:  # 左边沉
                        n = self.rotate_right_left(node.parent, node)
                    else:  # 右边沉
                        n = self.rotate_left(node.parent, node)
                    # 记得把n和g连起来
                elif node.parent.bf < 0:  # 原来node.parent.bf = -1,更新之后变成0
                    node.parent.bf = 0
                    break
                else:  # 原来node.parent.bf = 0,更新之后变成1
                    node.parent.bf = 1
                    node = node.parent
                    continue
            # 连接旋转后的子树,把n和g连起来
            n.parent = g
            if g:  # g不是空
                if x == g.lchild:
                    g.lchild = n
                else:
                    g.rchild = n
                break
            else:
                self.root = n
                break

    def rotate_left(self, p, c):
        """
        左旋操作
        :param p: 当前节点 p(旋转前的根节点)
        :param c: 右孩子节点 c(旋转后的新根节点)
        :return: 新的子树根节点 c
        """
        s2 = c.lchild  # s2 是 c 的左孩子
        p.rchild = s2  # p 的右孩子指向 s2
        if s2:
            s2.parent = p  # 如果 s2 存在,更新 s2 的父节点为 p
        c.lchild = p  # c 的左孩子指向 p
        p.parent = c  # 更新 p 的父节点为 c

        # 更新平衡因子
        p.bf = 0
        c.bf = 0
        return c  # 返回新的根节点 c

    def rotate_right(self, p, c):
        """
        右旋操作
        :param p: 当前节点 p(旋转前的根节点)
        :param c: 左孩子节点 c(旋转后的新根节点)
        :return: 新的子树根节点 c
        """
        s2 = c.rchild  # s2 是 c 的右孩子
        p.lchild = s2  # p 的左孩子指向 s2
        if s2:
            s2.parent = p  # 如果 s2 存在,更新 s2 的父节点为 p

        c.rchild = p  # c 的右孩子指向 p
        p.parent = c  # 更新 p 的父节点为 c

        # 更新平衡因子
        p.bf = 0
        c.bf = 0
        return c  # 返回新的根节点 c

    def rotate_right_left(self, p, c):
        """
        右旋-左旋复合操作
        :param p: 当前节点 p(旋转前的根节点)
        :param c: 右孩子节点 c(旋转前的右孩子)
        :return: 新的子树根节点 g(旋转后的新根节点)
        """
        g = c.lchild  # g 是 c 的左孩子(旋转后的新根节点)

        s3 = g.rchild  # s3 是 g 的右孩子
        c.lchild = s3  # c 的左孩子指向 s3
        if s3:
            s3.parent = c  # 如果 s3 存在,更新 s3 的父节点为 c

        g.rchild = c  # g 的右孩子指向 c
        c.parent = g  # 更新 c 的父节点为 g

        s2 = g.lchild  # s2 是 g 的左孩子
        p.rchild = s2  # p 的右孩子指向 s2
        if s2:
            s2.parent = p  # 如果 s2 存在,更新 s2 的父节点为 p

        g.lchild = p  # g 的左孩子指向 p
        p.parent = g  # 更新 p 的父节点为 g

        # 更新平衡因子
        if g.bf > 0:
            p.bf = -1
            c.bf = 0
        elif g.bf < 0:
            p.bf = 0
            c.bf = 1
        else:
            p.bf = 0
            c.bf = 0
        g.bf = 0
        return g  # 返回新的根节点 g

    def rotate_left_right(self, p, c):
        """
        左旋-右旋复合操作
        :param p: 当前节点 p(旋转前的根节点)
        :param c: 左孩子节点 c(旋转前的左孩子)
        :return: 新的子树根节点 g(旋转后的新根节点)
        """
        g = c.rchild  # g 是 c 的右孩子(旋转后的新根节点)

        s2 = g.lchild  # s2 是 g 的左孩子
        c.rchild = s2  # c 的右孩子指向 s2
        if s2:
            s2.parent = c  # 如果 s2 存在,更新 s2 的父节点为 c

        g.lchild = c  # g 的左孩子指向 c
        c.parent = g  # 更新 c 的父节点为 g

        s3 = g.rchild  # s3 是 g 的右孩子
        p.lchild = s3  # p 的左孩子指向 s3
        if s3:
            s3.parent = p  # 如果 s3 存在,更新 s3 的父节点为 p

        g.rchild = p  # g 的右孩子指向 p
        p.parent = g  # 更新 p 的父节点为 g

        # 更新平衡因子
        if g.bf < 0:
            p.bf = 1
            c.bf = 0
        elif g.bf > 0:
            p.bf = 0
            c.bf = -1
        else:
            p.bf = 0
            c.bf = 0
        g.bf = 0
        return g  # 返回新的根节点 g


tree = AVLTree([9, 8, 7, 6, 10, 5, 4, 3, 2, 1])
tree.pre_order(tree.root)
print('')
tree.mid_order(tree.root)

执行结果:
8,4,2,1,3,6,5,7,9,10,
1,2,3,4,5,6,7,8,9,10,

3 二叉搜索树的扩展应用-B树

"B树(B-Tree): B树是一棵自平衡的多路搜索树。常用于数据库的索引。"

B-Tree(B树)是一种自平衡的树数据结构,它可以保持数据有序,并允许二分查找、顺序访问、插入和删除。
B树广泛用于数据库和文件系统的实现中,因为它能够很好地管理大量数据,并且可以有效地减少磁盘I/O操作。

B-Tree的特点:
1 节点:每个节点可以包含多个键(数据),而不是像二叉树那样每个节点只包含一个键。
2 有序性:在每个节点中,键是按照非递减顺序排列的。
3 孩子节点数量:一个节点有 m 个孩子,则节点中最多有 m-1 个键。
	B树的阶数 m 决定了节点的最小和最大子节点数量。
4 平衡性:B树是自平衡的,所有叶子节点都位于同一层,保证了查找、插入和删除操作的效率。

B-Tree的性质:
1 一个B树的节点包含至多 m-1 个键(数据),和至多 m 个子树。
2 如果根节点不是叶子节点,则根节点至少有两棵子树。
3 每个内部节点至少包含 ⌈m/2- 1 个键和 ⌈m/2⌉ 个子树。
4 所有叶子节点都在同一层。

B-Tree的插入和删除:
1 插入:
	1 从根节点开始,递归找到应插入的叶子节点。
	2 如果叶子节点未满,则直接插入。
	3 如果叶子节点已满,则需要将该节点分裂成两个节点,并将中间键提升到父节点。
	  若父节点也满,则继续向上分裂,直到根节点。

2 删除:
	1 如果删除的是叶子节点中的键且节点中的键数大于最小值,则直接删除。
	2 如果删除的键在内部节点,需要选择合适的替代键(例如前驱或后继),然后递归删除该键。
	3 如果节点在删除后键数不足,则需要进行合并或从兄弟节点借键。

在这里插入图片描述

class BTreeNode:
    def __init__(self, leaf=False):
        """
        初始化B树节点。
        :param leaf: 布尔值,表示该节点是否为叶子节点。如果为True,则该节点是叶子节点。
        """
        self.leaf = leaf  # 是否为叶子节点
        self.keys = []  # 节点中存储的键值列表
        self.children = []  # 子节点列表,存储指向子节点的引用

class BTree:
    def __init__(self, t):
        """
        初始化B树。
        :param t: B树的最小度数,表示每个节点中最少包含t-1个键,最多包含2t-1个键。
        """
        self.root = BTreeNode(True)  # 创建B树的根节点,初始时默认为叶子节点
        self.t = t  # 最小度数

    def insert(self, k):
        """
        插入键值k到B树中。
        :param k: 要插入的键值。
        """
        root = self.root
        # 如果根节点已满,需要分裂
        if len(root.keys) == (2 * self.t) - 1:
            # 创建一个新的根节点,并设置它为非叶子节点
            temp = BTreeNode()
            # 将旧的根节点作为子节点挂载到新的根节点
            self.root = temp
            temp.children.append(root)
            # 分裂旧的根节点
            self._split_child(temp, 0)
            # 将键值插入到适当的位置
            self._insert_non_full(temp, k)
        else:
            # 如果根节点未满,直接插入
            self._insert_non_full(root, k)

    def _split_child(self, x, i):
        """
        分裂子节点。假设x是一个非满节点,且x的第i个子节点是满的。
        :param x: 需要分裂的节点的父节点。
        :param i: 需要分裂的子节点在父节点子节点列表中的索引。
        """
        t = self.t
        # 获取需要分裂的子节点
        y = x.children[i]
        # 创建一个新的节点z来存储y的后半部分键值和子节点
        z = BTreeNode(y.leaf)
        # 将z插入到x的子节点列表中
        x.children.insert(i + 1, z)
        # 将y的中间键值提升到x中
        x.keys.insert(i, y.keys[t - 1])
        # 将y的后半部分键值移动到z中
        z.keys = y.keys[t:(2 * t) - 1]
        # 将y的前半部分键值保留在y中
        y.keys = y.keys[0:t - 1]
        # 如果y不是叶子节点,将它的后半部分子节点也移动到z中
        if not y.leaf:
            z.children = y.children[t:(2 * t)]
            y.children = y.children[0:t]

    def _insert_non_full(self, x, k):
        """
        在非满节点中插入键值k。
        :param x: 当前节点。
        :param k: 要插入的键值。
        """
        i = len(x.keys) - 1
        if x.leaf:
            # 如果是叶子节点,直接在合适位置插入键值
            x.keys.append(None)
            while i >= 0 and k < x.keys[i]:
                x.keys[i + 1] = x.keys[i]
                i -= 1
            x.keys[i + 1] = k
        else:
            # 如果不是叶子节点,找到合适的子节点进行递归插入
            while i >= 0 and k < x.keys[i]:
                i -= 1
            i += 1
            # 如果子节点已满,先分裂子节点
            if len(x.children[i].keys) == (2 * self.t) - 1:
                self._split_child(x, i)
                # 如果新插入的键值大于分裂后的中间键值,则移到右边的子节点进行插入
                if k > x.keys[i]:
                    i += 1
            self._insert_non_full(x.children[i], k)

    def search(self, k, x=None):
        """
        在B树中查找键值k。
        :param k: 要查找的键值。
        :param x: 当前节点,默认为None表示从根节点开始查找。
        :return: 如果找到返回包含键值k的节点和索引,否则返回None。
        """
        if x is not None:
            i = 0
            # 找到第一个大于或等于k的位置
            while i < len(x.keys) and k > x.keys[i]:
                i += 1
            # 如果找到k,返回当前节点和索引
            if i < len(x.keys) and k == x.keys[i]:
                return (x, i)
            # 如果当前节点是叶子节点且没有找到,返回None
            elif x.leaf:
                return None
            else:
                # 否则递归到适当的子节点继续查找
                return self.search(k, x.children[i])
        else:
            # 如果x为None,从根节点开始查找
            return self.search(k, self.root)

    def print_tree(self, x, l=0):
        """
        打印B树结构。
        :param x: 当前节点。
        :param l: 当前节点的层级,用于格式化输出。
        """
        print("Level", l, " ", len(x.keys), end=": ")
        for i in x.keys:
            print(i, end=" ")
        print()
        l += 1
        # 递归打印每个子节点
        if len(x.children) > 0:
            for i in x.children:
                self.print_tree(i, l)

# 示例使用:
btree = BTree(3)  # 创建最小度数为3的B树
data = [10, 20, 5, 6, 12, 30, 7, 17]  # 要插入的键值列表
for item in data:
    btree.insert(item)  # 将键值逐个插入B树

btree.print_tree(btree.root)  # 打印B树的结构

在这里插入图片描述

3.2 B+树

B+树 是 B树 的一种扩展版本,主要用于数据库和文件系统中的索引结构。

与 B树 相比,B+树 有以下几个显著特点:
1 所有的键值都存在叶子节点:
	在 B+树 中,所有的数据记录都存储在叶子节点中,而内部节点仅存储索引键值,用于指引搜索路径。
2 链表结构的叶子节点:
	B+树 的所有叶子节点通过一个链表连接起来,便于范围查询的实现。
	这样可以很方便地遍历数据范围,而不需要重新从根节点开始查找。
3内部节点的键值用于索引:
	B+树 的内部节点存储的是子树中最小键值的副本,而不包含指向具体记录的指针。

在这里插入图片描述

class BPlusTreeNode:
    def __init__(self, leaf=False):
        """
        初始化B+树节点。
        :param leaf: 布尔值,表示该节点是否为叶子节点。
        """
        self.leaf = leaf  # 是否为叶子节点
        self.keys = []  # 节点中存储的键值列表
        self.children = []  # 子节点列表或叶子节点中的数据记录指针
        self.next = None  # 指向下一个叶子节点的指针(仅在叶子节点中使用)

class BPlusTree:
    def __init__(self, t):
        """
        初始化B+树。
        :param t: B+树的最小度数,表示每个节点中最少包含t-1个键,最多包含2t-1个键。
        """
        self.root = BPlusTreeNode(True)  # 创建B+树的根节点,初始时默认为叶子节点
        self.t = t  # 最小度数

    def insert(self, k):
        """
        插入键值k到B+树中。
        :param k: 要插入的键值。
        """
        root = self.root
        if len(root.keys) == (2 * self.t) - 1:  # 根节点已满,需要分裂
            temp = BPlusTreeNode()  # 创建新的根节点
            self.root = temp  # 更新根节点
            temp.children.append(root)  # 原来的根节点成为新根节点的子节点
            self._split_child(temp, 0)  # 分裂根节点的子节点
            self._insert_non_full(temp, k)  # 插入键值到新的根节点
        else:
            self._insert_non_full(root, k)  # 插入键值到非满节点

    def _split_child(self, x, i):
        """
        分裂子节点。假设x是一个非满节点,且x的第i个子节点是满的。
        :param x: 需要分裂的节点的父节点。
        :param i: 需要分裂的子节点在父节点子节点列表中的索引。
        """
        t = self.t
        y = x.children[i]  # 要分裂的子节点
        z = BPlusTreeNode(y.leaf)  # 新建一个节点z用于存储分裂出的部分

        # 在x中插入新节点z,并在x的keys中插入分裂点的中间键
        x.children.insert(i + 1, z)
        x.keys.insert(i, y.keys[t - 1])

        # 将y中大于等于中间键的键和值分裂到z中
        z.keys = y.keys[t:]
        y.keys = y.keys[:t - 1]

        if y.leaf:
            z.children = y.children[t:]  # 如果y是叶子节点,复制y的部分数据到z
            y.children = y.children[:t]
            z.next = y.next  # 更新叶子节点链表的链接
            y.next = z
        else:
            z.children = y.children[t:]  # 如果y不是叶子节点,复制y的部分子节点到z
            y.children = y.children[:t]

    def _insert_non_full(self, x, k):
        """
        在非满节点中插入键值k。
        :param x: 当前节点。
        :param k: 要插入的键值。
        """
        i = len(x.keys) - 1  # 找到要插入的位置

        if x.leaf:  # 如果是叶子节点,直接插入
            x.keys.append(None)  # 占位
            while i >= 0 and k < x.keys[i]:
                x.keys[i + 1] = x.keys[i]  # 依次后移,腾出插入位置
                i -= 1
            x.keys[i + 1] = k  # 插入键值
        else:  # 如果不是叶子节点,需要递归插入到合适的子节点中
            while i >= 0 and k < x.keys[i]:
                i -= 1
            i += 1
            if len(x.children[i].keys) == (2 * self.t) - 1:  # 如果子节点已满,先分裂
                self._split_child(x, i)
                if k > x.keys[i]:  # 如果k大于分裂出的中间键,插入到右侧的子节点
                    i += 1
            self._insert_non_full(x.children[i], k)  # 递归插入到子节点

    def search(self, k, x=None):
        """
        在B+树中查找键值k。
        :param k: 要查找的键值。
        :param x: 当前节点,默认为None表示从根节点开始查找。
        :return: 如果找到返回包含键值k的叶子节点,否则返回None。
        """
        if x is not None:  # 如果指定了节点,从该节点开始查找
            i = 0
            while i < len(x.keys) and k > x.keys[i]:  # 找到大于等于k的位置
                i += 1
            if i < len(x.keys) and k == x.keys[i] and x.leaf:  # 如果在叶子节点中找到k
                return x  # 返回该叶子节点
            elif x.leaf:  # 如果在叶子节点中未找到k
                return None  # 查找失败
            else:  # 如果是内部节点,递归查找合适的子节点
                return self.search(k, x.children[i])
        else:  # 如果未指定节点,从根节点开始查找
            return self.search(k, self.root)

    def print_tree(self, x, l=0):
        """
        打印B+树结构。
        :param x: 当前节点。
        :param l: 当前节点的层级,用于格式化输出。
        """
        print("Level", l, " ", len(x.keys), end=": ")  # 输出当前节点的层级和键值数量
        for i in x.keys:  # 输出当前节点的所有键值
            print(i, end=" ")
        print()
        l += 1
        if len(x.children) > 0:  # 如果有子节点,递归打印子节点
            for i in x.children:
                self.print_tree(i, l)


# 示例使用:
btree = BPlusTree(3)  # 创建一个B+树,最小度数为3
data = [10, 20, 5, 6, 12, 30, 7, 17]  # 要插入的数据列表
for item in data:  # 逐个插入数据
    btree.insert(item)

btree.print_tree(btree.root)  # 打印B+树结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值