二叉搜索树的实现

📋 个人简介

  • 💖 作者简介:大家好,我是菀枯😜

  • 🎉 支持我:点赞👍+收藏⭐️+留言📝

  • 💬格言:不要在低谷沉沦自己,不要在高峰上放弃努力!☀️

    v2-af3cfbda99d44f8eb114cc6c74c92253_720w

前言

今天我们来学一个新的数据结构:二叉搜索树

介绍

二叉搜索树也称作二叉排序树,它具有以下性质:

  1. 非空左子树的所有键值小于其根节点的键值
  2. 非空右子树的所有键值大于其根节点的键值
  3. 左,右子树都是二叉搜索树

那么我先画一个二叉搜索树给大家看看,是不是真的满足上面的性质。

image-20220822110306524

我们就以根节点6为例子来看,我们会发现比6小的都在6的左边,而比6大的都在6的右边。对于6的左右子树来说,所有的节点都遵循这个规则。

同时我们还可以发现如果对搜索二叉树进行一个中序遍历,我们得到的序列是个有序序列,这就是为什么二叉搜索树也可以称作二叉排序树。

实现

节点的实现

千里之行始于足下。首先我们需要写一个二叉树最基本的节点。

template <typename K>
struct BTreeNode
{
	BTreeNode<K>* _left;
	BTreeNode<K>* _right;
	K _key;

	BTreeNode(const K& key)
		:_key(key), _left(nullptr), _right(nullptr)
	{}
};

二叉搜索树的查找

二叉搜索树的查找思路很简单:要找的值比当前节点小就去左子树找,比当前节点大就往右子树找,找到空节点就说明没找到返回false即可。

动画

首先我们先看看递归的版本。

bool findR(const T &val, Node *root) //T为节点的K, Node为节点
{
    if (root == nullptr)
    {
        return false;
    }

    if (root->_key < val)
    {
        return findR(root->_right, val);
    }
    else if (root->_key > val)
    {
        return findR(root->_left, val);
    }
    else
    {
        return true;
    }
}

接着看看非递归的版本

bool find(const T &val)
{
    Node *cur = _root; //_root为二叉搜索树的根节点
    while (cur)
    {
        if (val < cur->_key)
        {
            cur = cur->_left;
        }
        else if (val > cur->_key)
        {
            cur = cur->_right;
        }
        else
        {
            return true;
        }
    }
    return false;
}

二叉搜索树的插入

二叉搜索树的插入和查找差别不大,首先寻找插入位置,比当前节点小就往左子树找,比当前节点大就往右子树找,直到找到空指针时,就可以进行一个插入。

那么要插入的值和当前节点相同该怎么办呢?我们此时实现的二叉搜索树是一个无重复元素的二叉搜索树,所以对于相同的值我采取的方式是返回一个false,大家如果想实现一个有重复元素的二叉搜索树,可以选择将重复的值放在右子树或左子树都可。

二叉搜索树的插入和查找一样,有递归和非递归两个版本,首先我们先来看看非递归的版本。

bool insert(const T &val)
{
    //空树直接改变根节点
    if (_root == nullptr)
    {
        _root = new Node(val);
        return true;
    }
	
 	//非空树先寻找插入位置
    Node *pre = nullptr;
    Node *cur = _root;
    while (cur)
    {
        if (val > cur->_key)
        {
            pre = cur;
            cur = cur->_right;
        }
        else if (val < cur->_key)
        {
            pre = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }
	//判断新节点该插入到哪里
    cur = new Node(val);
    if (val < pre->_key)
    {
        pre->_left = cur;
    }
    else
    {
        pre->_right = cur;
    }
    return true;
}

接下来用一个动画来表示一下这个插入过程。

动画

接下来我们来看看递归版本是如何实现的

bool insertR(const T &val, Node *&root)
{
    if (root == nullptr)
    {
        Node *newNode = new Node(val);
        root = newNode;
    }

    if (root->_key < val)
    {
        return insertR(val, root->_right);
    }
    else if (root->_key > val)
    {
        return insertR(val, root->_left);
    }
    else
    {
        return false;
    }
}

此时我们可以发现,递归版本没有非递归版本中的parent指针了,参数列表中只有一个root指针,这是为什么呢?

我们可以看见我们的root指针不仅是一个指针,同时它还是一个引用。这就意味着我们对root的修改也可以影响上一层传递过来的指针的值。所以此时我们不需要parent指针,就可以对上一个节点的指针进行一个修改。

二叉搜索树的删除

理论部分:

二叉搜索树的删除相比前面两个要麻烦那么一丢丢,首先当然是找要删除的节点,找到后通常有以下三种情况:

  1. 此节点无左右子树
  2. 此节点有右子树或右子树
  3. 此节点有左右子树

我们要做的就是对这三种情况分别进行一个处理。

  • 首先是此节点无左右子树,这种情况我们直接将此节点删除即可
  • 然后是此节点只有一颗子树,这个也比较简单,如果此节点是父节点的左节点,那么我们只需要将父节点的左指针改成指向此节点的子树即可。动画
  • 最后一种就是既有左子树又有右子树的情况了,此时为了不破坏结构,我们需要用到替换删除法。首先我们先找到要删除的节点,然后找节点的左子树的最右节点或右子树的最左节点,将两个节点进行一下互换,再将原节点删除。动画
代码部分:

首先是非递归版本

bool erase(const T &val)
{
    Node *cur = _root;
    Node *pre = nullptr;
    //寻找删除位置
    while (cur)
    {
        if (cur->_key < val)
        {
            pre = cur;
            cur = cur->_right;
        }
        else if (cur->_key > val)
        {
            pre = cur;
            cur = cur->_left;
        }
        else //找到了进行删除
        {
            if (cur->_left == nullptr) //左子树为空
            {
                if (cur == _root)
                {
                    _root = cur->_right;
                }
                else
                {
                    if (cur == pre->_left)
                    {
                        pre->_left = cur->_right;
                    }
                    else
                    {
                        pre->_right = cur->_right;
                    }
                }
                delete cur;
            }
            else if (cur->_right == nullptr) //右子树为空
            {
                if (cur == _root)
                {
                    _root = cur->_left;
                }
                else
                {
                    if (cur == pre->_left)
                    {
                        pre->_left = cur->_left;
                    }
                    else
                    {
                        pre->_right = cur->_left;
                    }
                }
                delete cur;
            }
            else //左右子树都不为空
            {
                //找左子树的最右节点
                Node *tmp = cur->_left;
                Node *tmp_pre = cur;
                while (tmp->_right) 
                {
                    tmp_pre = tmp;
                    tmp = tmp->_right;
                }
				//节点互换
                cur->_key = tmp->_key;

                if (tmp == tmp_pre->_left)
                {
                    tmp_pre->_left = tmp->_left;
                }
                else
                {
                    tmp_pre->_right = tmp->_left;
                }

                delete tmp;
            }
            return true;
        }
    }
    return false;
}

接下来是递归版本

bool _eraseR(const T &val, Node *&root)
{
    if (root == nullptr)
    {
        return false;
    }

    if (root->_key < val)
    {
        return _eraseR(val, root->_right);
    }
    else if (root->_key > val)
    {
        return _eraseR(val, root->_left);
    }
    else
    {
        //指针引用的妙用
        Node *del = root;
        if (root->_left == nullptr)
        {
            root = root->_right;
        }
        else if (root->_right == nullptr)
        {
            root = root->_left;
        }
        else
        {
            Node *cur = root->_left;
            while (cur->_right)
            {
                cur = cur->_right;
            }
            swap(cur->_key, root->_key);
            return _eraseR(val, root->_left);
        }
        delete del;
        return true;
    }
}

总结

大家觉得二叉搜索树的时间复杂度是多少呢? O ( log ⁡ n ) O(\log n) O(logn)吗?不,它的时间复杂度还是 O ( n ) O(n) O(n),当插入数据是有序的,二叉搜索树会发生退化,变成一个单支树。比如下面这张图:

image-20220822133826078

为了解决这个问题,有人对二叉搜索树进行了一些优化,如:AVL树和红黑树,之后我也会带着大家来实现一个完整的AVL树和红黑树

结语

1647941444633

欢迎各位参考与指导!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是Java实现二叉搜索树的基本步骤和代码示例: 1.定义二叉搜索树节点类 ```java class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } ``` 2.定义二叉搜索树类 ```java class BST { private TreeNode root; public BST() { root = null; } // 向二叉搜索树中插入元素 public void insert(int val) { root = insert(root, val); } private TreeNode insert(TreeNode node, int val) { if (node == null) { return new TreeNode(val); } if (val < node.val) { node.left = insert(node.left, val); } else if (val > node.val) { node.right = insert(node.right, val); } return node; } // 在二叉搜索树中查找元素 public boolean search(int val) { return search(root, val); } private boolean search(TreeNode node, int val) { if (node == null) { return false; } if (val == node.val) { return true; } else if (val < node.val) { return search(node.left, val); } else { return search(node.right, val); } } // 删除二叉搜索树中的元素 public void delete(int val) { root = delete(root, val); } private TreeNode delete(TreeNode node, int val) { if (node == null) { return null; } if (val < node.val) { node.left = delete(node.left, val); } else if (val > node.val) { node.right = delete(node.right, val); } else { if (node.left == null) { return node.right; } else if (node.right == null) { return node.left; } TreeNode minNode = findMin(node.right); node.val = minNode.val; node.right = delete(node.right, node.val); } return node; } private TreeNode findMin(TreeNode node) { while (node.left != null) { node = node.left; } return node; } } ``` 3.测试代码 ```java public class TestBST { public static void main(String[] args) { BST bst = new BST(); bst.insert(5); bst.insert(3); bst.insert(7); bst.insert(1); bst.insert(9); System.out.println(bst.search(3)); // 输出:true bst.delete(3); System.out.println(bst.search(3)); // 输出:false } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

。菀枯。

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

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

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

打赏作者

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

抵扣说明:

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

余额充值