C++数据结构之二叉搜索树的实现详解

目录
  • 前言
  • 介绍
  • 实现
    • 节点的实现
    • 二叉搜索树的查找
    • 二叉搜索树的插入
    • 二叉搜索树的删除
  • 总结

前言

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

介绍

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

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

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

我们就以根节点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)
{
    //找不到返回false
    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
    {
        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);
        }
        return true;
    }
}

总结

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

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

到此这篇关于C++数据结构之二叉搜索树的实现详解的文章就介绍到这了,更多相关C++二叉搜索树内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++ 二叉搜索树(BST)的实现方法

    废话不多说了,直接给大家贴代码了,具体代码如下所示: class BST { public: struct Node { int key;//节点的key int value;//节点的value Node* left; Node *right; int N;//节点的叶子节点数目 Node(int _key, int _value, int _N) { key = _key; value = _value; N = _N; } }; BST(); ~BST(); void put(int ke

  • C++数据结构二叉搜索树的实现应用与分析

    目录 概念 二叉搜索树的实现

  • C++实现LeetCode(96.独一无二的二叉搜索树)

    [LeetCode] 96. Unique Binary Search Trees 独一无二的二叉搜索树 Given n, how many structurally unique BST's (binary search trees) that store values 1 ... n? Example: Input: 3 Output: 5 Explanation: Given n = 3, there are a total of 5 unique BST's: 1         3  

  • C++ 超详细快速掌握二叉搜索树

    目录 二叉搜索树概念与操作 二叉搜索树的概念 二叉搜索树的操作 查找 插入 删除 二叉搜索树的应用 二叉树的性能分析 二叉搜索树概念与操作 二叉搜索树的概念 二叉搜索树又称二叉排序树,若它的左子树不为空,则左子树上所有节点的值都小于根节点的值:若它的右子树不为空,则右子树上所有节点的值都大于根节点的值,它的左右子树也分别未二叉搜索树.也可以是一颗空树. int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 }; 二叉搜索树的操作 查找 迭代: Node* Find(c

  • C++深入细致探究二叉搜索树

    目录 1.二叉搜索树的概念 2.二叉搜索树的操作 二叉搜索树的查找 二叉搜索树的插入 二叉搜索树的删除 3.二叉搜索树的实现 4.二叉搜索树的性能分析 1.二叉搜索树的概念  二叉搜索树又称二叉排序树,它可以是一颗空树,亦可以是一颗具有如下性质的二叉树:   ①若根节点的左子树不为空,则左子树上的所有节点的值域都小于根节点的值   ②若根节点的右子树不为空,则右子树上的所有节点的值域都大于根节点的值   ③根节点的左右子树分别也是一颗二叉搜索树 例如下面的这棵二叉树就是一棵二叉搜索树: 注意:判

  • C++数据结构之二叉搜索树的实现详解

    目录 前言 介绍 实现 节点的实现 二叉搜索树的查找 二叉搜索树的插入 二叉搜索树的删除 总结 前言 今天我们来学一个新的数据结构:二叉搜索树. 介绍 二叉搜索树也称作二叉排序树,它具有以下性质: 非空左子树的所有键值小于其根节点的键值 非空右子树的所有键值大于其根节点的键值 左,右子树都是二叉搜索树 那么我先画一个二叉搜索树给大家看看,是不是真的满足上面的性质. 我们就以根节点6为例子来看,我们会发现比6小的都在6的左边,而比6大的都在6的右边.对于6的左右子树来说,所有的节点都遵循这个规则.

  • JavaScript二叉搜索树构建操作详解

    目录 前言 什么是二叉搜索树 构建一颗二叉搜索树 二叉搜索树的操作 向二叉搜索树中插入数据 查找二叉搜索树中的数据 删除二叉搜索树的某个节点 前驱后继节点 删除一个节点的三种情况 实现代码 完整代码 总结 前言 前面我们介绍了二叉树这个数据结构以及二叉树的遍历算法,这篇文章我们来学习一下一个特殊的二叉树——二叉搜索树(BST Binary Search Tree),也叫二叉排序树.二叉查找树. 什么是二叉搜索树 二叉搜索树首先它是一棵二叉树,而且还满足下面这些特质: 对于任何一个非空节点来说,它

  • C++二叉搜索树BSTree使用详解

    目录 一.概念 二.基础操作 1.查找find 2.插入Insert 3.中序遍历InOrder 4.删除erase 三.递归写法 1.递归查找 2.递归插入 3.递归删除 四.应用 五.题目练习 一.概念 二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 左<根<右 它的左右子树也分别为二叉搜索树 之所以又叫二叉排序树,是因为二叉搜索树中序遍历的结果

  • Java二叉搜索树遍历操作详解【前序、中序、后序、层次、广度优先遍历】

    本文实例讲述了Java二叉搜索树遍历操作.分享给大家供大家参考,具体如下: 前言:在上一节Java二叉搜索树基础中,我们对树及其相关知识做了了解,对二叉搜索树做了基本的实现,下面我们继续完善我们的二叉搜索树. 对于二叉树,有深度遍历和广度遍历,深度遍历有前序.中序以及后序三种遍历方法,广度遍历即我们寻常所说的层次遍历,如图: 因为树的定义本身就是递归定义,所以对于前序.中序以及后序这三种遍历我们使用递归的方法实现,而对于广度优先遍历需要选择其他数据结构实现,本例中我们使用队列来实现广度优先遍历.

  • Java深入了解数据结构之二叉搜索树增 插 删 创详解

    目录 ①概念 ②操作-查找 ③操作-插入 ④操作-删除 1. cur.left == null 2. cur.right == null 3. cur.left != null && cur.right != null ⑤性能分析 ⑥完整代码 ①概念 二叉搜索树又称二叉排序树,它或者是一棵空树**,或者是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树 ②操作-查找

  • Java数据结构之二叉搜索树详解

    目录 前言 性质 实现 节点结构 初始化 插入节点 查找节点 删除节点 最后 前言 今天leetcode的每日一题450是关于删除二叉搜索树节点的,题目要求删除指定值的节点,并且需要保证二叉搜索树性质不变,做完之后,我觉得这道题将二叉搜索树特性凸显的很好,首先需要查找指定节点,然后删除节点并且保持二叉搜索树性质不变,就想利用这个题目讲讲二叉搜索树. 二叉搜索树作为一个经典的数据结构,具有链表的快速插入与删除的特点,同时查询效率也很优秀,所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数

  • javascript数据结构之二叉搜索树实现方法

    本文实例讲述了javascript二叉搜索树实现方法.分享给大家供大家参考,具体如下: 二叉搜索树:顾名思义,树上每个节点最多只有二根分叉:而且左分叉节点的值 < 右分叉节点的值 . 特点:插入节点.找最大/最小节点.节点值排序 非常方便 二叉搜索树-javascript实现 <script type="text/javascript"> // <![CDATA[ //打印输出 function println(msg) { document.write(msg

  • 如何利用JavaScript实现二叉搜索树

    计算机科学中最常用和讨论最多的数据结构之一是二叉搜索树.这通常是引入的第一个具有非线性插入算法的数据结构.二叉搜索树类似于双链表,每个节点包含一些数据,以及两个指向其他节点的指针:它们在这些节点彼此相关联的方式上有所不同.二叉搜索树节点的指针通常被称为"左"和"右",用来指示与当前值相关的子树.这种节点的简单 JavaScript 实现如下: var node = { value: 125, left: null, right: null }; 从名称中可以看出,二

  • Java数据结构超详细分析二叉搜索树

    目录 1.搜索树的概念 2.二叉搜索树的简单实现 2.1查找 2.2插入 2.3删除 2.4修改 3.二叉搜索树的性能 1.搜索树的概念 二叉搜索树是一种特殊的二叉树,又称二叉查找树,二叉排序树,它有几个特点: 如果左子树存在,则左子树每个结点的值均小于根结点的值. 如果右子树存在,则右子树每个结点的值均大于根结点的值. 中序遍历二叉搜索树,得到的序列是依次递增的. 二叉搜索树的左右子树均为二叉搜索树. 二叉搜索树的结点的值不能发生重复. 2.二叉搜索树的简单实现 我们来简单实现以下搜索树,就不

随机推荐