Python实现二叉搜索树

1.二叉树

二叉树(Binary Tree)是一种数据结构,它由节点组成,每个节点最多有两个子节点,分别称为左子节点和右子节点。节点中包含一个值和指向左右子节点的指针。如图

1.1二叉树的链式存储

将而二叉树的节点定义为一个对象,节点之间通过类似链表的方式来连接。

1.1.1 节点定义

# 二叉树
# 节点定义
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.rchild = None  # 右孩子
        self.lchild = None  # 左孩子

1.1.2插入数据

为了介绍后面的二叉树的遍历,我们插入上图中的几个数据。

a = BiTreeNode("A")
b = BiTreeNode("B")
c = BiTreeNode("C")
d = BiTreeNode("D")
e = BiTreeNode("E")
f = BiTreeNode("F")
g = BiTreeNode("G")

# 如图对应各个节点对应相应的左右孩子
e.lchild = a
e.rchild = g
a.rchild = c
c.lchild = b
c.rchild = d
g.rchild = f

root = e   # 根节点

1.2 二叉树的遍历

1.2.1前序遍历

# 前序遍历
def pre_order(root):
    if root:  # 如果有数据
        print(root.data, end=' ')
        pre_order(root.lchild)
        pre_order(root.rchild)

# 输出前序遍历
pre_order(root)

输出结果:

如何理解

        借助图来理解,在前序遍历上述二叉树的时候,使用递归的方式来进行代码实现。在递归过程中,先打印根节点E,再递归左子树(先递归)和右子树(后递归),即上图的中的两个黑色的框,在左边黑色框里,先打印E的左子树中的节点A,然后再递归A的左右子树,在递归到A的右子树时(蓝色框),同样先打印节点C,再递归C的左右子树,所以C后面会打印B和D。同理右边的黑色框,会先打印G然后再递归G的子树,接着打印F,所以整体打印出来就是EACBDGF。

1.2.2中序遍历

# 中序遍历
def in_order(root):
    if root:
        in_order(root.lchild)
        print(root.data, end=' ')
        in_order(root.rchild)

# 打印中序遍历
in_order(root)

输出结果:

如何理解

        在中序遍历上述二叉树的时候,先递归E的左子树(左黑色框),再打印根节点,再递归E的右子树(右黑色框),再递归E的左子树时,同样先递归A的左子树,再打印A,再递归A的右子树,由于A没有左子树,所以打印A之后递归右子树(绿色框),在递归A的右子树时同理,先递归C的左子树(打印B),再打印C,在递归C的右子树(打印D)。在递归完E的左子树后,打印E,再递归E的右子树,同理,先递归左子树,再打印G,再递归右子树。

1.2.3后序遍历

# 后序遍历
def post_order(root):
    if root:
        post_order(root.lchild)
        post_order(root.rchild)
        print(root.data, end=' ')

# 打印后序遍历
post_order(root)

输出结果:

如何理解

        在看懂前面两种遍历之后,后序遍历就很好懂了,先递归E的左子树(左边黑色框),再递归E的右子树(右边黑色框),再打印E。在递归E的左子树的时候,同样先递归A的左子树(图中没有)再递归A的右子树,再打印A,同理在递归A的右子树时,也是先左子树(B)后右子树(D)最后打印A,在E的左子树递归完成以后,再看E的右子树,同理,先打印F,再打印G,当E的左右子树都递归完成以后,最后打印E。

1.2.4三种遍历方式的联系

在前序遍历中第一个元素E是根节点,对应到中序遍历,E的两边分别是E的左子树和右子树的元素,将左右子树的元素对应到前序遍历中又可以得到子树中的根节点(前序遍历中子树元素中的第一个元素对应的该子树的根节点)。

在后序遍历中最后一个元素E是根节点,再对应到中序遍历,可以得到E的左右子树中的元素,再将左右子树中的元素对应到后序遍历中,在后序遍历中子树元素中的最后的一个元素是该子树的根节点。

2.二叉搜索树

二叉搜索树(Binary Search Tree,也称为二叉排序树或二叉查找树)满足以下条件:

  1. 对于任意节点N,其左子树中的所有节点的值均小于N的值,右子树中的所有节点的值均大于N的值。

  2. 左、右子树也必须分别是二叉搜索树。

如图例:

2.1二叉搜索树的实现

2.1.1节点定义

# 节点定义
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None   # 右孩子
        self.parent = None   # 父节点

2.1.2元素插入

关于元素的插入,由于二叉搜索树的特点,以上面的图示为例,比如说插入的元素是10,10比17小,要放在17的左子树,10比5大,要放在5的右子树,10比11小,要放在11的左子树,10比9大,要放在9的右子树,结束。

# 使用递归实现
    def insert_rec(node, val):  # node递归的节点  val要插入的元素
        if not node:   # 如果没有节点
            node = BiTreeNode(val)
        elif val < node.data:  # 插入元素小于节点元素
            node.lchild = insert_rec(node.lchild, val)  # 递归左子树
            node.lchild.parent = node  # 连接父节点
        elif val > node.data:  # 插入元素大于节点元素
            node.rchild = insert_rec(node.rchild, val)   # 递归右子树
            node.rchild.parent = node
        return node
# 使用非递归实现
    def insert_no_rec(val):
        p = root
        if not p: # 没有根节点
            root = BiTreeNode(val)
            return
        while True:  # 根节点存在,进入循环
            if val < p.data:
                if p.lchild:  # 如果p的左孩子存在
                    p = p.lchild
                else:  # p的左孩子不存在
                    p.lchild = BiTreeNode(val)
                    p.lchild.parent = p
                    return
            elif val > p.data:
                if p.rchild:  # 如果p的左孩子存在
                    p = p.rchild
                else:  # p的左孩子不存在
                    p.rchild = BiTreeNode(val)
                    p.rchild.parent = p
                    return
            else:  # 如果val和某个节点的数据相等
                return   # 在这里也可以在节点中多定义一个用于重复元素计数的属性,遇到相等的情况,进行+1

2.1.3元素查询

 # 递归实现
    def query_rec(node, val):   # 使用递归实现
        if not node:
            return None
        elif val < node.data:
            return query_rec(node.lchild, val)
        elif val > node.data:
            return query_rec(node.rchild, val)
        else:
            return node
# 非递归实现
    def query_no_rec(val):
        p = root
        while True:
            if val < p.data:
                p = p.lchild
            elif val > p.data:
                p = p.rchild
            else:
                return p
        else:  # 没有找到
            return None

2.1.3删除操作

1.如果要删除的节点是叶子节点:直接删除

比如要删除16这个叶子节点,直接删除

    def __remove_node_1(node):
        # 情况1,node是叶子节点
        if not node.parent:  # node没有父亲节点,说明node是根节点
            root = None
        if node == node.parent.lchild:   # node是它父亲的左孩子
            node.parent.lchild = None
        else:   # node是它父亲的右孩子
            node.parent.rchild = None

2.如果要删除的节点只有一个孩子:将此节点的父亲与孩子连接,然后删掉该节点。

比如删除25,需要把25的右孩子与25的父亲节点连接起来,然后删除25.如果要删除根节点的话,以右边的二叉树为例,删除17,需要重新选出根节点,此时由35作为新的根节点。

    def __remove_node_21(node):
        # 情况2,node只有一个左孩子
        if not node.parent:   # node是根节点
            root = node.lchild
            node.lchild.parent = None
        elif node == node.parent.lchild:  # node是其父亲的左孩子
            # node的父亲节点与node的左孩子连接
            node.parent.lchild = node.lchild
            node.lchild.parent = node.parent
        else:   # node是其父亲的右孩子
            # node的父亲节点与node的左孩子连接
            node.lchild.parent = node.parent
            node.parent.rchild = node.lchild
    def __remove_node_22(node):
        # 情况2,只有一个右孩子
        if not node.parent:
            root = node.rchild
            node.rchild.parent = None
        elif node == node.parent.rchild:
            # node的父亲节点与node的右孩子连接
            node.parent.rchild = node.rchild
            node.rchild.parent = node.parent
        else:
            # node的父亲节点与node的右孩子连接
            node.parent.lchild = node.rchild
            node.rchild.parent = node.parent

3.如果要删除的节点有两个孩子:将其右子树的最小节点(该节点最多有一个右孩子)删除,并替换当前节点。

如果要删除5这个节点,其右子树中最小的节点是7,将7替换5,并执行删除7这个节点的操作。

    # 删除操作
    def delete(val):
        if root:  # 不是空树
            node = query_no_rec(val)   # 利用查找函数,返回val所在的节点
            if not node:  # 如果没有找到val所在的节点
                return False
            if not node.lchild and not node.rchild:  # node没有左右孩子,说明node是叶子节点
                __remove_node_1(node)
            elif not node.rchild: # 如果node只有左孩子   为什么可以这样判断?如果存在右孩子,会执行上一步的if
                __remove_node_21(node)
            elif not node.lchild:  # 同理,node只有一个右孩子
                __remove_node_22(node)
            else:  # 两个孩子都有
                min_node = node.rchild   # 寻找右子树中最小的节点
                while min_node.lchild:   # 在二叉搜索树中,左孩子比父亲节点小
                    min_node = min_node.lchild  # 如果有左孩子,继续往下遍历,直到节点没有左孩子,此时为删除节点右子树中最小的节点
                node.data = min_node.data  # 用右子树中最小的数,替换删除节点的数
                if min_node.rchild: # 此时min_node没有左孩子,如果存在右孩子
                    __remove_node_22(min_node)
                else:   # min_node为叶子节点
                    __remove_node_1(min_node)

2.2二叉搜索树的效率

平均情况下,二叉搜索树进行搜索的时间复杂度为O(lgn)

最坏情况下,二叉搜索树可能非常倾斜

解决方案:1.随机化插入    

                  2.AVL树

3.总结

3.1 将二叉搜索树封装到类

# 节点定义
class BiTreeNode:
    def __init__(self, data):
        self.data = data
        self.lchild = None  # 左孩子
        self.rchild = None   # 右孩子
        self.parent = None   # 父节点

class BST:
    def __init__(self, li=None):
        self.root = None  # 根节点
        # 插入列表元素
        if li:  # 列表非空
            for val in li:
                self.insert_no_rec(val)

    # 使用递归实现
    def insert_rec(self, node, val):  # node递归的节点  val要插入的元素
        if not node:   # 如果没有节点
            node = BiTreeNode(val)
        elif val < node.data:  # 插入元素小于节点元素
            node.lchild = self.insert_rec(node.lchild, val)  # 递归左子树
            node.lchild.parent = node  # 连接父节点
        elif val > node.data:  # 插入元素大于节点元素
            node.rchild = self.insert_rec(node.rchild, val)   # 递归右子树
            node.rchild.parent = node
        return node

    # 使用非递归实现
    def insert_no_rec(self, val):
        p = self.root
        if not p: # 没有根节点
            self.root = BiTreeNode(val)
            return
        while True:  # 根节点存在,进入循环
            if val < p.data:
                if p.lchild:  # 如果p的左孩子存在
                    p = p.lchild
                else:  # p的左孩子不存在
                    p.lchild = BiTreeNode(val)
                    p.lchild.parent = p
                    return
            elif val > p.data:
                if p.rchild:  # 如果p的左孩子存在
                    p = p.rchild
                else:  # p的左孩子不存在
                    p.rchild = BiTreeNode(val)
                    p.rchild.parent = p
                    return
            else:  # 如果val和某个节点的数据相等
                return   # 在这里也可以在节点中多定义一个用于重复元素计数的属性,遇到相等的情况,进行+1

    # 元素查询
    # 递归实现
    def query_rec(self, node, val):   # 使用递归实现
        if not node:
            return None
        elif val < node.data:
            return self.query_rec(node.lchild, val)
        elif val > node.data:
            return self.query_rec(node.rchild, val)
        else:
            return node

    # 非递归实现
    def query_no_rec(self, val):
        p = self.root
        while True:
            if val < p.data:
                p = p.lchild
            elif val > p.data:
                p = p.rchild
            else:
                return p
        else:  # 没有找到
            return None

    def __remove_node_1(self, node):
        # 情况1,node是叶子节点
        if not node.parent:  # node没有父亲节点,说明node是根节点
            self.root = None
        if node == node.parent.lchild:   # node是它父亲的左孩子
            node.parent.lchild = None
        else:   # node是它父亲的右孩子
            node.parent.rchild = None

    def __remove_node_21(self, node):
        # 情况2,node只有一个左孩子
        if not node.parent:   # node是根节点
            self.root = node.lchild
            node.lchild.parent = None
        elif node == node.parent.lchild:  # node是其父亲的左孩子
            # node的父亲节点与node的左孩子连接
            node.parent.lchild = node.lchild
            node.lchild.parent = node.parent
        else:   # node是其父亲的右孩子
            # node的父亲节点与node的左孩子连接
            node.lchild.parent = node.parent
            node.parent.rchild = node.lchild

    def __remove_node_22(self, node):
        # 情况2,只有一个右孩子
        if not node.parent:
            self.root = node.rchild
            node.rchild.parent = None
        elif node == node.parent.rchild:
            # node的父亲节点与node的右孩子连接
            node.parent.rchild = node.rchild
            node.rchild.parent = node.parent
        else:
            # node的父亲节点与node的右孩子连接
            node.parent.lchild = node.rchild
            node.rchild.parent = node.parent
            
    # 删除操作
    def delete(self, val):
        if self.root:  # 不是空树
            node = self.query_no_rec(val)   # 利用查找函数,返回val所在的节点
            if not node:  # 如果没有找到val所在的节点
                return False
            if not node.lchild and not node.rchild:  # node没有左右孩子,说明node是叶子节点
                self.__remove_node_1(node)
            elif not node.rchild: # 如果node只有左孩子   为什么可以这样判断?如果存在右孩子,会执行上一步的if
                self.__remove_node_21(node)
            elif not node.lchild:  # 同理,node只有一个右孩子
                self.__remove_node_22(node)
            else:  # 两个孩子都有
                min_node = node.rchild   # 寻找右子树中最小的节点
                while min_node.lchild:   # 在二叉搜索树中,左孩子比父亲节点小
                    min_node = min_node.lchild  # 如果有左孩子,继续往下遍历,直到节点没有左孩子,此时为删除节点右子树中最小的节点
                node.data = min_node.data  # 用右子树中最小的数,替换删除节点的数
                if min_node.rchild: # 此时min_node没有左孩子,如果存在右孩子
                    self.__remove_node_22(min_node)
                else:   # min_node为叶子节点
                    self.__remove_node_1(min_node)

    # 前序遍历
    def pre_order(self, root):
        if root:  # 如果有数据
            print(root.data, end=' ')
            self.pre_order(root.lchild)
            self.pre_order(root.rchild)

    # 中序遍历
    def in_order(self, root):
        if root:
            self.in_order(root.lchild)
            print(root.data, end=' ')
            self.in_order(root.rchild)

    # 后序遍历
    def post_order(self, root):
        if root:
            self.post_order(root.lchild)
            self.post_order(root.rchild)
            print(root.data, end=' ')
import random

li = list(range(30))
random.shuffle(li)

tree = BST(li)

tree.in_order(tree.root)
print("")
tree.delete(17)
tree.in_order(tree.root)

打印结果:

注意,在上述功能中没有处理传入元素相同的情况,所以如果传入列表中有相同的元素,最后打印时,之后打印一个。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值