二叉树-二叉搜索树(Python实现)

二叉树-二叉搜索树

时间线

  • 2020年7月9日-完成初稿

参考链接

python生成器和递归结合

上面是希望通过生成器和递归结合,获得遍历结果

二叉搜索树(一个非常好的教学网站)

前言

之所以选择二叉搜索树作为二叉树模块的起点,是因为对于二叉搜索树还是有点印象的,

但是弄懂二叉搜索树的相关内容,还是花费了一天的时间。

其中最大的时间占比是代码实现部分,因为不断地推翻与重建,最后才写出还算满意的内容;

当然代码实现只是知识的应用,所以在代码实现的过程中,也弄懂了一些细节的知识。

例如当插入二叉搜索树中已有的元素时,应该如何处理等等。

定义

定义

二叉搜索树(Binary Search Tree,简写BST)是二叉树的一种,也是一种经典的数据结构。

顾名思义,其主要用途是搜索,常用于文件系统数据库系统排序检索

性质

  • 一个节点的左子树只能包含值比该节点小的节点
  • 一个节点的右子树只能包含值比该节点大的节点
  • 以每个节点为根节点的树都是二叉搜索树

一图流

二叉搜索树的构造

根据元素的插入顺序构造的二叉搜索树

可以看到作为根节点的49,是数据集合的第一个元素

根节点的左子树的各个节点值都小于根节点值49,根节点的右子树的各个节点值都大于根节点值49

在这里插入图片描述

二叉搜索树无唯一

具有相同元素的数据集合,元素的顺序不同,根据元素顺序而构造的二叉搜索树也不唯一,如下:

数据集合内有共同元素49,38,65,但顺序不同,而构造出的二叉搜索树也不同

在这里插入图片描述

二叉搜索树元素不可重复

当元素出现重复时,将不符合二叉搜索树的定义

例如上图,出现重复元素49,则违反定义 根节点右子树的节点值大于根节点的值

在这里插入图片描述

逻辑

无论是数据库还是数据结构,都逃不过4种主要操作行为:插入、删除、修改、查询(CRUD)

所以二叉搜索树主要有以下操作:

插入:插入元素到二叉搜索树

相对简单,根据元素的值插入到二叉搜索树中

定义插入元素为待插入元素

  • 从根节点出发

    • 当根节点的值为空时,直接将根节点的值设置为带插入元素的值

    • 当根节点的值不为空时,取根节点的值与待插入元素的值进行比较

      • 当根节点的值等于待插入元素的值时,不做任何操作

      • 当根节点的值大于待插入元素的值时,说明待插入元素应该在根节点的左子树,

        则根节点下移到根节点的左子节点

      • 当根节点的值小于待插入元素的值时,说明待插入元素应该在根节点的右子树,

        则根节点下移到根节点的右子节点

    • 重复以上步骤,直至完成插入

删除:删除某个元素

相对复杂,见逻辑思考-节点的状态与相关节点的位置关系是删除二叉树节点的关键

修改:修改某个值

二叉搜索树的主要用途是搜索,所以不加记录

如果实现的话,可以猜想:整个二叉搜索树的结构会发生非常大的变化,而不是简单地替换

查询:最小元素/最大元素

相对简单,

在二叉搜索树中,最小元素节点即使最左下节点,也即中序遍历的“后继节点”

而最大元素即最右下节点,也即中序遍历的的“前驱节点”

(这里圈起来是因为需要为整个二叉搜索树增加一个假设值Null的“双亲节点”,如下)

在这里插入图片描述

逻辑思考

  • 节点的状态与相关节点的位置关系是删除二叉树节点的关键

    定义需要删除的节点为待删除节点,待删除节点的双亲节点为双亲节点

    待删除节点的子节点为子节点

    1. 无子节点:节点无子节点,则表示该节点是叶子节点,

      可以直接删除

    2. 只有一个子节点:这个子节点的位置非常关键

      • 待删除节点是双亲节点的左子节点:将子节点设置为双亲节点的左子节点

      • 待删除节点是双亲节点的右子节点:将子节点设置为双亲节点的右子节点

    3. 有两个子节点

      1. 找到以右子节点的根节点的二叉搜索树的最小值节点,

        即待删除节点的后继节点

      2. 将该后继节点的值设置为待删除节点的值

      3. 删除后继节点

        后继节点的删除也是节点删除中的一种,但不会是由两个子节点的。

        因为后继节点是待删除节点的右子树中最小的一个数,那么说明:

        后继节点只有两种可能,无子节点或者只有一个右子节点

时间复杂度

实现

Python

注意:测试中的main函数内的代码可以直接粘贴到源文件中,就不会出现导入问题

源文件
# encoding:utf-8
"""
@author:zhj1121
@time:2020.7.9
@filename:mybst.py
"""
class Node(object):
    def __init__(self,data):
        self.data = data
        self.leftChild = None
        self.rightChild = None
class BST(object):
    def __init__(self,root = None):
        self.root = root
    def insert(self,data):
        if not self.root:
            self.root = Node(data)
            return True
        else:
            flag,currentNode,parent,around= self.search(self.root,self.root,data)
            """ 不存在则可插入 """
            if not flag:
                newNode = Node(data)
                if parent.data > data:
                    parent.leftChild = newNode
                else:
                    parent.rightChild = newNode
                return True
            else:
                return False
    def insertList(self,eleList):
        try:
            for i in eleList:
                self.insert(i)
            return True
        except:
            return Exception("exception")
    def search(self,node,parent,data,around=None):
        """ 返回值:flag,当前节点,双亲节点 """
        if not node:
            #node节点的双亲节点是None
            return False,node,parent,around
        else:
            if node.data == data:
                return True,node,parent,around
            elif node.data > data:
                return self.search(node.leftChild,node,data,0)
            else:
                return self.search(node.rightChild,node,data,1)
    def getMinByNode(self,node,parent):
        """ 寻找以node为根节点的的树最小节点
        这个最小节点是是parent的中序遍历的后继节点
            查找整棵树的最小节点,
        可以:node = self.root
            查找根节点的后继节点,
        可以:node = self.root.rightChild
        return: node,parent
        """
        #当前节点为空
        if not node:
            return None,parent
        #当前节点无左孩子,则表示当前节点是最小节点
        elif not node.leftChild:
            return node,parent
        #否则,则递归当前节点的左孩子
        else:
            return self.getMinByNode(node.leftChild,node)
    def getMindata(self):
        minnode,parent = self.getMinByNode(self.root,self.root)
        if not minnode:
            return parent.data
        else:
            return minnode.data
    def getMaxByNode(self,node,parent):
        """ 寻找以node为根节点的树中最大节点
        这个最大节点是以parent的中序遍历的前驱节点
        return : node,parent
        """
        if not node:
            return None,parent
        elif not node.rightChild:
            return node,parent
        else:
            return self.getMaxByNode(node.rightChild,node)
    def getMaxdata(self):
        maxnode,parent = self.getMaxByNode(self.root,self.root)
        if not maxnode:
            return parent.data
        else:
            return maxnode.data
    def delNodeBydata(self,root,data):
        flag,node,parent,around = self.search(root,root,data)
        #二叉搜索树没有对象data
        if not flag:
            return Exception("不存在该对象")
        #否则
        #flag,node,parent : True,待删除节点,待删除节点的双亲节点
        else:
            #待删除节点 : 无左孩子
            if not node.leftChild:
                #around:获得node与parent的位置关系
                #注意:
                # 这里还有一个逻辑,即node节点是无左右孩子的
                #这时,也是执行if逻辑,并不会冲突
                #因为node.rightChild = None
                #parent.rightChild = None
                #所以,是正确的
                #parent.rightChild = node.rightChild
                if around:#node在parent的右边
                    parent.rightChild = node.rightChild
                else:#node在parent的左边
                    parent.leftChild = node.rightChild
            # 待删除节点 : 无右孩子
            elif not node.rightChild:
                if around:
                    parent.rightChild = node.leftChild
                else:
                    parent.leftChild = node.rightChild
            # 待删除节点 : 有左右孩子
            else:
                # #找到待删除节点的后继节点,即待删除节点的右子树中,最小的节点
                # current,current_parent = self.getMinByNode(node.rightChild,node)
                # #无左孩子
                # if not current:
                #     node.data = current_parent.data
                # 找到待删除节点的后继节点,即待删除节点的右子树中,最小的节点
                preNode = node.rightChild#获得右子树的根节点
                #右子树没有左孩子,则根节点是最小节点
                if not preNode.leftChild:
                    node.data = preNode.data
                    node.rightChild = preNode.rightChild
                    del preNode
                else:
                    #往下遍历
                    nextNode = preNode.leftChild
                    while nextNode.leftChild:
                        preNode = nextNode
                        nextNode = nextNode.leftChild
                    #nextNode将会是后继节点
                    node.data = nextNode.data
                    preNode.leftChild = nextNode.rightChild
    def preOrderTraversal(self,node):
        """ 先序遍历 """
        if node:
            yield node.data
            for i in self.preOrderTraversal(node.leftChild):
                yield i
            for i in self.preOrderTraversal(node.rightChild):
                yield i
    def inOrderTraversal(self,node):
        """ 中序遍历 """
        if node:
            for i in self.inOrderTraversal(node.leftChild):
                yield i
            yield node.data
            for i in self.inOrderTraversal(node.rightChild):
                yield i
    def postOrderTraversal(self,node):
        """ 后序遍历 """
        if node:
            for i in self.postOrderTraversal(node.leftChild):
                yield i
            for i in self.postOrderTraversal(node.rightChild):
                yield i
            yield node.data
    def getInorderTraversalList(self,node):
        result = []
        yielder = self.inOrderTraversal(node)
        for i in yielder:
            result.append(i)
        return result
    def getTraversalList(self,node):
        """ 先序/中序/后序遍历 """
        preList = [_ for _ in self.preOrderTraversal(node)]
        inList = [_ for _ in self.inOrderTraversal(node)]
        postList = [_ for _ in self.postOrderTraversal(node)]
        return preList,inList,postList
测试
from binarySortTree.mybst import BST
if __name__ == "__main__":
    ls = [49,38,65,97,60,76,13,27,5,1,1]
    bst = BST()
    # """ 插入 """
    print("插入节点:")
    error = []
    for i in ls:
        if bst.insert(i):
            print("{}".format(i),end=' ')
        else:
            error.append(i)
    print("\n插入完成,其中{}插入失败,共{}次".format(error,len(error)))
    # or bst.insertList(ls)
    # """ 获取根节点数据 """
    print('根节点:{}'.format(bst.root.data))
    # """ 获取先/中/后序遍历 """
    prelist,inlist,postlist = bst.getTraversalList(bst.root)
    print(
        "先序遍历:{}\n中序遍历:{}\n后序遍历:{}".format(
            prelist,inlist,postlist
        )
    )
    # """ 删除节点49 """
    delELement = 49 
    print("删除节点{}".format(delELement))
    bst.delNodeBydata(bst.root,delELement)
    # """ 获取中序遍历结果 """
    print("中序遍历结果:{}".format(bst.getInorderTraversalList(bst.root)))
    # """ 获取二叉搜索树中的最大值/最小值 """
    print(
        "{}中的最大值为{},最小值为{}".format(bst.getInorderTraversalList(bst.root),
    bst.getMaxdata(),bst.getMindata())
    )
运行结果

在这里插入图片描述

思考

二叉树都有一个特点,就是"树模式"——每一个节点都可以作为一棵二叉树的根节点

插入元素的过程,就是在树上增加分支的过程,每一个元素都有一个属于自己的位置

而影响的是元素的插入顺序。

删除过程,需要考虑节点的状态以及相对位置

查询,也即遍历过程,由于"树模式",所以可以使用递归

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值