一篇文章教会你什么是二叉搜索树

在这里插入图片描述

二叉搜索树

  • 二叉搜索树概念
  • 二叉搜索树操作
    • 1.二叉搜索树的查找
    • 2.二叉搜索树的插入
    • 3.二叉搜索树的删除
    • 4.二叉搜索树的遍历
  • 二叉搜索树的实现
    • 1.二叉搜索树节点结构
    • 2.二叉搜索树类
    • 3.二叉搜索树的构造及析构
    • 4.二叉搜索树的拷贝构造及赋值重载
    • 5.二叉搜索树插入
    • 6.二叉搜索树查找
    • 7.二叉搜索树删除
    • 8.二叉搜索树中序遍历
    • 9.二叉搜索树所有代码
  • 二叉搜索树的应用
  • 二叉搜索树的性能分析

二叉搜索树概念

二叉搜索树Binary Search Tree,BST)是一种二叉树的特殊形式,它具有以下关键性质:

  1. 有序性:每个节点都包含一个值,且左子树中的所有节点的值都小于等于该节点的值,右子树中的所有节点的值都大于等于该节点的值。这意味着对于任何节点,它的左子树都包含比它小的值,右子树都包含比它大的值。
  2. 递归性质:整棵二叉搜索树本身也是一个二叉搜索树,因为它的左子树和右子树也满足二叉搜索树的性质。这意味着可以递归地应用相同的规则来构建和操作树。
  3. 无重复值:通常,BST中不允许包含重复的值。每个值在树中只能出现一次。

由于这些性质,二叉搜索树在很多应用中都非常有用,尤其是在需要高效地进行搜索、插入和删除操作的情况下。

以下是一些关于二叉搜索树的详细解释:

  1. 插入操作
    • 插入操作从根节点开始,根据节点值的大小逐步向下搜索树,直到找到一个合适的位置插入新节点。
    • 如果要插入的值小于当前节点的值,就继续在左子树中搜索,否则在右子树中搜索,直到找到一个空位置插入新节点。
  2. 搜索操作
    • 搜索操作也从根节点开始,根据节点值的大小逐步向下搜索树。
    • 如果要搜索的值等于当前节点的值,就找到了目标节点。
    • 如果要搜索的值小于当前节点的值,就在左子树中搜索;如果大于当前节点的值,就在右子树中搜索。
    • 如果一直搜索到叶子节点仍然没有找到目标值,则说明目标值不在树中。
  3. 删除操作
    • 删除操作通常比较复杂,因为需要考虑多种情况。删除节点时有以下几种情况:
      • 被删除节点是叶子节点(没有子节点):直接删除即可。
      • 被删除节点有一个子节点:将子节点替换为被删除节点的位置。
      • 被删除节点有两个子节点:找到被删除节点的中序遍历前驱或后继节点,用其值替换被删除节点的值,然后递归删除前驱或后继节点。
  4. 遍历操作
    • 二叉搜索树可以通过不同的遍历方式进行访问,包括中序遍历、前序遍历和后序遍历。
    • 中序遍历可以按照升序顺序遍历树中的所有节点,因为它首先遍历左子树,然后当前节点,最后右子树。
  5. 平衡性
    • 当二叉搜索树的高度不平衡时,搜索、插入和删除操作的时间复杂度可能会退化为O(n),其中n是节点数。为了避免这种情况,通常使用平衡二叉搜索树(如AVL树或红黑树)来确保树的高度保持在较小的范围内。

总之,二叉搜索树是一种基于节点值大小有序排列的二叉树,它支持高效的搜索、插入和删除操作,但需要注意平衡性问题以避免性能退化。

请添加图片描述

二叉搜索树操作

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

1.二叉搜索树的查找

在二叉查找树b中查找x的过程为:

  1. 若b是空树,则搜索失败,否则:
  2. 若x等于b的根节点的数据域之值,则查找成功;否则:
  3. 若x小于b的根节点的数据域之值,则搜索左子树;否则:
  4. 查找右子树

2.二叉搜索树的插入

向一个二叉搜索树b中插入一个节点s的算法,过程为:

  1. 若b是空树,则将s所指节点作为根节点插入,否则:
  2. s->data等于b的根节点的数据域之值,则返回,否则:
  3. s->data小于b的根节点的数据域之值,则把s所指节点插入到左子树中,否则:
  4. 把s所指节点插入到右子树中。(新插入节点总是叶子节点)

在这里插入图片描述

3.二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:

情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除

在这里插入图片描述

4.二叉搜索树的遍历

二叉搜索树的遍历最常用的是中序遍历,因为二叉搜索树(BST)的中序遍历结果是升序的。中序遍历是一种遍历二叉树的方式,按照以下规则进行:

  1. 首先遍历左子树。
  2. 然后遍历当前节点。
  3. 最后遍历右子树。

由于BST的性质,即左子树的值都小于当前节点的值,右子树的值都大于当前节点的值,所以中序遍历的过程中,首先访问左子树,然后访问当前节点,最后访问右子树。这导致中序遍历结果是按照升序排列的。

因此,中序遍历是一种有助于获取BST中所有节点按升序排列的有效方法。这个性质也是BST在进行搜索操作时能够快速定位目标值的关键之一。

二叉搜索树的实现

1.二叉搜索树节点结构

template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};
  1. _left:指向左子节点的指针。
  2. _right:指向右子节点的指针。
  3. _key:存储节点的关键字或值。

每个节点都有一个关键字 _key,用来表示节点在二叉搜索树中的位置。这个关键字通常是用来比较节点的大小,以便将节点插入到正确的位置,以满足二叉搜索树的性质。根据节点关键字的大小,节点将被放置在左子树或右子树中。

这是一个模板类,它可以用于创建不同类型的二叉搜索树,只需指定不同的关键字类型 K 即可。这使得代码更加通用,可以适用于不同类型的数据。

2.二叉搜索树类

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
private:Node* _root = nullptr;
}

二叉搜索树Binary Search Tree,BST)类模板 BSTree 的定义。这个类包含一个私有成员 _root,它是指向树的根节点的指针。

  • template<class K>:这是一个类模板声明,使用了模板参数 K,它表示关键字或值的数据类型。这允许你在创建 BSTree 实例时指定不同的数据类型。
  • typedef BSTreeNode<K> Node;:这行代码用于定义一个别名 Node,它是 BSTreeNode<K> 类的别名。这个别名使得在类中使用节点类型更加方便。
  • Node* _root = nullptr;:这是一个指向根节点的指针 _root,它初始化为 nullptr,表示树开始为空树。

3.二叉搜索树的构造及析构

构造函数

BSTree() = default;

BSTree() = default; 是C++中的默认构造函数的声明方式。这行代码表示你正在声明一个默认构造函数(无参数的构造函数),并使用 = default 表示使用默认的构造函数实现。

默认构造函数是一个特殊的构造函数,它在对象创建时被自动调用,不接受任何参数。默认构造函数的主要作用是初始化对象的成员变量,确保对象在创建后处于一个合法的初始状态。

使用 = default 表示你希望编译器生成默认的构造函数实现,而不需要自己编写构造函数的定义。这通常用于以下情况:

  1. 你的类不需要特殊的构造逻辑,只需要成员变量的默认初始化。
  2. 你想确保编译器生成默认构造函数,以便可以在不提供自定义构造函数的情况下创建对象。

这里需要注意的是在C++中,如果你提供了自定义的拷贝构造函数,编译器通常不会再自动生成默认的拷贝构造函数,所以我们需要再将默认构造自定义提供

析构函数

~BSTree()
{_Destory(_root);
}private:void _Destory(Node*& root){if (root == nullptr){return;}_Destory(root->_left);_Destory(root->_right);delete root;root = nullptr;}
  1. ~BSTree()
    • 这是BST类的析构函数。析构函数在对象被销毁时自动调用,用于执行清理和释放资源的操作。
    • 在这个析构函数中,它调用了私有辅助函数 _Destory(_root),传递根节点 _root 作为参数,开始递归地销毁整棵树。
  2. _Destory(Node*& root)
    • 这是一个递归辅助函数,用于销毁二叉搜索树中的节点。
    • 函数接受一个指向节点指针的引用作为参数,以便可以修改节点指针,将其设置为 nullptr,以避免悬挂指针问题。
    • 函数首先检查节点是否为空。如果节点为空,表示已经到达树的底部,函数直接返回,不执行任何操作。
    • 如果节点不为空,函数递归地调用自己来销毁左子树和右子树。
    • 然后,函数删除当前节点,释放节点的内存。
    • 最后,将当前节点指针设置为 nullptr,以确保不会再访问已释放的节点。

这样,当你销毁BST对象时,析构函数会从根节点开始递归地销毁整棵树,确保释放所有节点的内存,防止内存泄漏。这是一种正确释放资源的方式,以确保程序的健壮性。

4.二叉搜索树的拷贝构造及赋值重载

拷贝构造

BSTree(const BSTree<K>& t)
{_root = _Copy(t._root);
}private:Node* _Copy(Node* root){if (root == nullptr){return nullptr;}Node* copyRoot = new Node(root->_key);copyRoot->_left = _Copy(root->_left);copyRoot->_right = _Copy(root->_right);return copyRoot;}
  1. BSTree(const BSTree<K>& t)
    • 这是拷贝构造函数,它用于创建一个新的BST对象,该对象是根据另一个BST对象 t 进行拷贝构造的。
    • 在构造函数内部,它调用了私有的辅助函数 _Copy(t._root),传递 t 的根节点 _root 作为参数,从而创建了一个新的BST。
  2. _Copy(Node* root)
    • 这是一个递归辅助函数,用于复制一棵二叉树。
    • 如果传入的根节点 root 为空(nullptr),则返回 nullptr,表示空树。
    • 如果根节点不为空,函数首先创建一个新的节点 copyRoot,并将其关键字设置为 root 节点的关键字。
    • 然后,函数递归地调用自己来复制左子树和右子树,并将它们分别赋值给 copyRoot->_leftcopyRoot->_right
    • 最后,返回 copyRoot,这样递归过程会构建出一棵与原始树相同结构和内容的树。

这样,拷贝构造函数 _Copy 会递归地复制整棵树,确保你创建的新BST对象与原始对象具有相同的结构和内容。这对于在不破坏原始树的情况下创建一个新树非常有用。

5.二叉搜索树插入

迭代写法

public:bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}
  1. 首先,函数检查树是否为空(即 _root 是否为 nullptr)。如果树为空,它会创建一个新的根节点,该节点的关键字为 key,然后返回 true 表示插入成功。
  2. 如果树不为空,函数会进入一个循环,用来搜索插入位置。循环中的代码执行以下操作:
    • 如果当前节点的关键字小于 key,则继续向右子树移动,将 parent 设置为当前节点,以便稍后插入新节点到右子树。
    • 如果当前节点的关键字大于 key,则继续向左子树移动,同样将 parent 设置为当前节点,以便稍后插入新节点到左子树。
    • 如果当前节点的关键字等于 key,则返回 false,表示不允许重复关键字的节点插入。
  3. 一旦找到插入位置,函数创建一个新的节点 cur,该节点的关键字为 key
  4. 接着,根据 parent 节点的关键字与 key 的比较结果,将新节点 cur 插入到正确的位置。如果 parent->_key < key,则将 cur 插入为 parent 的右子节点,否则插入为 parent 的左子节点。
  5. 最后,函数返回 true,表示插入成功。

这个 Insert 函数实现了BST的插入操作,它确保新节点按照BST的有序性质被插入到正确的位置。如果关键字已经存在于树中,函数会返回 false,表示插入失败(因为BST不允许重复关键字)。

递归写法

bool InsertR(const K& key)
{return _InsertR(_root, key);
}	
private:bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key)return _InsertR(root->_right, key);else if (root->_key > key)return _InsertR(root->_left, key);elsereturn false;}
  1. bool InsertR(const K& key)
    • 这是公有函数,用于在二叉搜索树中插入新节点。它首先调用私有递归函数 _InsertR,并将根节点 _root 和要插入的关键字 key 作为参数传递给它。
  2. bool _InsertR(Node*& root, const K& key)
    • 这是私有递归辅助函数,用于在给定的二叉搜索树中插入新节点。
    • 函数首先检查当前根节点 root 是否为空。如果为空,表示找到了插入位置,它会创建一个新节点并将其关键字设置为 key,然后将 root 指向新节点,最后返回 true 表示插入成功。
    • 如果当前根节点 root 不为空,它会比较当前节点的关键字 root->_key与要插入的关键字 key
      • 如果 root->_key < key,则递归地在右子树中插入新节点,调用 _InsertR(root->_right, key)
      • 如果 root->_key > key,则递归地在左子树中插入新节点,调用 _InsertR(root->_left, key)
      • 如果 root->_key == key,表示已经存在相同关键字的节点,直接返回 false 表示插入失败。

6.二叉搜索树查找

迭代写法

bool Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return false;
}

bool Find(const K& key)

  • 这是一个公有函数,用于查找二叉搜索树中是否存在指定关键字 key
  • 函数首先初始化一个指针 cur 为根节点 _root,然后进入一个循环。
  • 在循环中,它会不断根据当前节点的关键字与目标关键字 key 的比较结果来决定向左子树还是右子树移动:
    • 如果当前节点的关键字小于 key,则说明要查找的关键字应该在当前节点的右子树中,所以将 cur 指向右子节点 cur->_right
    • 如果当前节点的关键字大于 key,则说明要查找的关键字应该在当前节点的左子树中,所以将 cur 指向左子节点 cur->_left
    • 如果当前节点的关键字等于 key,则表示找到了目标关键字,函数返回 true 表示查找成功。
  • 循环会一直进行,直到 cur 指向空(nullptr),这意味着整棵树都被搜索过了,但没有找到目标关键字,所以函数返回 false 表示查找失败。

递归写法

bool FindR(const K& key)
{return _FindR(_root, key);
}
private:
bool _FindR(Node* root, const K& key)
{if (root == nullptr)return false;if (root->_key < key)return _FindR(root->_right, key);else if (root->_key > key)return _FindR(root->_left, key);elsereturn true;
}
  1. bool FindR(const K& key)
    • 这是公有函数,用于在二叉搜索树中查找指定关键字 key 是否存在。它首先调用私有的递归函数 _FindR,并将根节点 _root 和要查找的关键字 key 作为参数传递给它。
  2. bool _FindR(Node* root, const K& key)
    • 这是私有的递归辅助函数,用于在给定的二叉搜索树中递归查找指定关键字 key 是否存在。
    • 函数首先检查当前根节点 root 是否为空(nullptr)。如果为空,表示已经搜索到了树的底部,关键字 key 不存在于树中,所以返回 false 表示查找失败。
    • 如果当前根节点root 不为空,函数会比较当前节点的关键字 root->_key 与要查找的关键字 key
      • 如果 root->_key < key,则说明要查找的关键字应该在当前节点的右子树中,所以递归调用 _FindR(root->_right, key) 在右子树中查找。
      • 如果 root->_key > key,则说明要查找的关键字应该在当前节点的左子树中,所以递归调用 _FindR(root->_left, key) 在左子树中查找。
      • 如果 root->_key == key,表示找到了目标关键字,函数返回 true 表示查找成功。

这个递归查找操作会在树的节点之间不断递归地进行,直到找到目标关键字或确认目标不存在。如果目标关键字存在于树中,函数返回 true;否则,返回 false

7.二叉搜索树删除

迭代写法

bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{// 1、左为空// 2、右为空// 3、左右都不为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;cur = nullptr;}else if (cur->_right == nullptr){if (_root == cur){_root = cur->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;cur = nullptr;}else{// 找到右子树最小节点进行替换Node* minParent = cur;Node* min = cur->_right;while (min->_left){minParent = min;min = min->_left;}swap(cur->_key, min->_key);if (minParent->_left == min)minParent->_left = min->_right;elseminParent->_right = min->_right;delete min;}return true;}}return false;
}
  1. 首先,函数初始化两个指针 parentcur 分别为 nullptr 和根节点 _root,然后进入一个循环。这个循环用于在BST中找到待删除节点,并进行删除操作。
  2. 在循环中,函数会不断根据当前节点的关键字与目标关键字 key 的比较结果来决定向左子树还是右子树移动,同时更新 parentcur 指针。
  3. 如果找到了目标关键字 key,表示要开始删除操作。删除操作分为以下几种情况:
    • 情况1:当前节点的左子树为空。在这种情况下,直接用当前节点的右子树替换当前节点即可。
    • 情况2:当前节点的右子树为空。在这种情况下,直接用当前节点的左子树替换当前节点即可。
    • 情况3:当前节点的左右子树都不为空。在这种情况下,需要找到右子树中的最小节点(通常是右子树中最左边的节点),将其关键字与当前节点的关键字进行交换,然后删除最小节点。
  4. 删除操作执行完毕后,函数返回 true 表示删除成功。
  5. 如果循环结束时仍然没有找到目标关键字 key,说明关键字不存在于树中,函数返回 false 表示删除失败。

递归写法

bool EraseR(const K& key)
{return _EraseR(_root, key);
}
private:
bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;if (root->_key < key)return _EraseR(root->_right, key);else if (root->_key > key)return _EraseR(root->_left, key);else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{// 找右树的最左节点替换删除Node* min = root->_right;while (min->_left){min = min->_left;}swap(root->_key, min->_key);return _EraseR(root->_right, key);}delete del;return true;}
}
  1. bool EraseR(const K& key)
    • 这是公有函数,用于在二叉搜索树中删除指定关键字 key 的节点。它首先调用私有的递归函数 _EraseR,并将根节点 _root 和要删除的关键字 key 作为参数传递给它。
  2. bool _EraseR(Node*& root, const K& key)
    • 这是私有的递归辅助函数,用于在给定的二叉搜索树中递归删除指定关键字 key 的节点。
    • 函数首先检查当前根节点 root 是否为空(nullptr)。如果为空,表示已经搜索到了树的底部,关键字 key 不存在于树中,所以返回 false 表示删除失败。
    • 如果当前根节点 root 不为空,函数会比较当前节点的关键字 root->_key与要删除的关键字 key
      • 如果 root->_key < key,则说明要删除的关键字应该在当前节点的右子树中,所以递归调用 _EraseR(root->_right, key) 在右子树中进行删除操作。
      • 如果 root->_key > key,则说明要删除的关键字应该在当前节点的左子树中,所以递归调用 _EraseR(root->_left, key) 在左子树中进行删除操作。
      • 如果 root->_key == key,表示找到了要删除的目标节点,此时需要执行删除操作。
  3. 删除操作执行的逻辑分为以下几种情况:
    • 情况1:当前节点的左子树为空。在这种情况下,可以直接用当前节点的右子树替换当前节点。
    • 情况2:当前节点的右子树为空。在这种情况下,可以直接用当前节点的左子树替换当前节点。
    • 情况3:当前节点的左右子树都不为空。在这种情况下,需要找到右子树中的最小节点,通常是右子树中最左边的节点,将其关键字与当前节点的关键字进行交换,然后继续递归删除右子树中的最小节点。
  4. 删除操作执行完毕后,函数返回 true 表示删除成功。
  5. 如果循环结束时仍然没有找到目标关键字 key,说明关键字不存在于树中,函数返回 false 表示删除失败。

8.二叉搜索树中序遍历

void InOrder()
{_InOrder(_root);cout << endl;
}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}
  1. void InOrder()
    • 这是公有函数,用于执行中序遍历二叉搜索树的操作。
    • 首先,它调用了私有的递归函数 _InOrder(_root),并传递根节点 _root 作为参数,开始进行中序遍历。
    • 遍历完成后,会输出一个换行符,以便在输出时每个节点值都在一行上。
  2. void _InOrder(Node* root)
    • 这是私有的递归辅助函数,用于执行中序遍历的实际操作。
    • 函数首先检查当前根节点 root 是否为空。如果为空,表示到达树的底部,直接返回,不执行任何操作。
    • 如果当前节点不为空,函数按照中序遍历的顺序执行以下操作:
      • 递归调用 _InOrder(root->_left),遍历左子树。
      • 输出当前节点的关键字值 root->_key,通常是将节点值打印到控制台。
      • 递归调用 _InOrder(root->_right),遍历右子树。

这个中序遍历函数会遍历整个二叉搜索树,并按照从小到大的顺序输出节点的关键字值。这是一种常用的方式来访问二叉搜索树的所有节点,并按照升序排列输出它们的值。

9.二叉搜索树所有代码

namespace Key
{template<class K>struct BSTreeNode{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}};template<class K>class BSTree{typedef BSTreeNode<K> Node;public:bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return false;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{// 1、左为空// 2、右为空// 3、左右都不为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;cur = nullptr;}else if (cur->_right == nullptr){if (_root == cur){_root = cur->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;cur = nullptr;}else{// 找到右子树最小节点进行替换Node* minParent = cur;Node* min = cur->_right;while (min->_left){minParent = min;min = min->_left;}swap(cur->_key, min->_key);if (minParent->_left == min)minParent->_left = min->_right;elseminParent->_right = min->_right;delete min;}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}~BSTree(){_Destory(_root);}BSTree() = default;BSTree(const BSTree<K>& t){_root = _Copy(t._root);}// t2 = t1BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}private:Node* _Copy(Node* root){if (root == nullptr){return nullptr;}Node* copyRoot = new Node(root->_key);copyRoot->_left = _Copy(root->_left);copyRoot->_right = _Copy(root->_right);return copyRoot;}void _Destory(Node*& root){if (root == nullptr){return;}_Destory(root->_left);_Destory(root->_right);delete root;root = nullptr;}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key)return _EraseR(root->_right, key);else if (root->_key > key)return _EraseR(root->_left, key);else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{// 找右树的最左节点替换删除Node* min = root->_right;while (min->_left){min = min->_left;}swap(root->_key, min->_key);return _EraseR(root->_right, key);}delete del;return true;}}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key)return _InsertR(root->_right, key);else if (root->_key > key)return _InsertR(root->_left, key);elsereturn false;}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key < key)return _FindR(root->_right, key);else if (root->_key > key)return _FindR(root->_left, key);elsereturn true;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root = nullptr;};
}

二叉搜索树的应用

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

    以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
    在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误

  2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见

    比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对

    再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对

例如

namespace KeyValue
{template<class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key, value);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Erase(const K& key){//...return true;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root = nullptr;};void TestBSTree1(){BSTree<string, string> dict;dict.Insert("sort", "排序");dict.Insert("left", "左");dict.Insert("right", "右");dict.Insert("vector", "向量");dict.Insert("insert", "插入");string str;while (cin >> str){BSTreeNode<string, string>* ret = dict.Find(str);if (ret){cout << "对应的中文:" << ret->_value << endl;}else{cout << "对应的中文->无此单词" << endl;}}}void TestBSTree2(){string arr[] = {"苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉"};BSTree<string, int> countTree;for (auto& str : arr){auto ret = countTree.Find(str);if (ret){ret->_value++;}else{countTree.Insert(str, 1);}}countTree.InOrder();}
}

二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为log_2 N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为frac{N}{2}

优点:

  1. 快速的查找操作: BST的主要优点是在查找操作上具有较快的平均时间复杂度。在平均情况下,查找一个元素的时间复杂度是O(log n),其中n是树中节点的数量。这使得BST非常适合实现高效的搜索操作。
  2. 有序性: BST保持节点按照键的顺序排列。这意味着你可以轻松地执行范围查询,找到最小值和最大值,或者执行有序的遍历。
  3. 插入和删除操作: 插入和删除元素的平均时间复杂度也是O(log n)。这使得BST对于需要频繁插入和删除元素的应用非常有效。

缺点:

  1. 性能不稳定: 最坏情况下,BST的性能可能会下降到O(n),其中n是树中节点的数量。这种情况发生在BST变得非常不平衡时,例如,如果将有序数据插入BST,将导致树的高度为n,从而导致O(n)的查找时间。
  2. 不平衡性: BST的性能高度依赖于树的平衡性。如果树不平衡,例如,变成了链表,那么查找、插入和删除的性能都会下降到O(n)。为了解决这个问题,出现了平衡二叉搜索树(AVL树、红黑树等),它们确保树的高度保持在较小的范围内,以维持O(log n)的性能。
  3. 内存需求: BST通常需要额外的内存来存储左子树和右子树的指针,这可能会导致内存消耗较大。

总结来说,BST在平均情况下具有较好的性能,特别适用于有序数据的查找和动态数据的插入/删除操作。然而,在最坏情况下,BST的性能可能变得不稳定,因此为了确保性能稳定,可以使用平衡二叉搜索树或其他更复杂的数据结构。选择适当的数据结构取决于应用的需求和数据的特点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/65198.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ELK日志收集系统集群实验(5.5.0版)

目录 前言 一、概述 二、组件介绍 1、elasticsearch 2、logstash 3、kibana 三、架构类型 四、ELK日志收集集群实验 1、实验拓扑 2、在node1和node2节点安装elasticsearch 3、启动elasticsearch服务 4、在node1安装elasticsearch-head插件 5、测试输入 6、node1服…

找不到msvcp140.dll的解决方法【msvcp140.dll修复工具下载】

今天&#xff0c;我将为大家分享一个与我们日常工作息息相关的话题——msvcp140.dll重新安装的5种解决方法。在接下来的时间里&#xff0c;我将向大家介绍什么是msvcp140.dll,为什么会丢失&#xff0c;以及它的用途。最后&#xff0c;我将为大家提供5种解决方法&#xff0c;帮助…

spring高级源码50讲-37-42(springBoot)

Boot 37) Boot 骨架项目 如果是 linux 环境&#xff0c;用以下命令即可获取 spring boot 的骨架 pom.xml curl -G https://start.spring.io/pom.xml -d dependenciesweb,mysql,mybatis -o pom.xml也可以使用 Postman 等工具实现 若想获取更多用法&#xff0c;请参考 curl …

【GUI开发】用python爬YouTube博主信息,并开发成exe软件

文章目录 一、背景介绍二、代码讲解2.1 爬虫2.2 tkinter界面2.3 存日志 三、软件演示视频四、说明 一、背景介绍 你好&#xff0c;我是马哥python说&#xff0c;一名10年程序猿。 最近我用python开发了一个GUI桌面软件&#xff0c;目的是爬取相关YouTube博主的各种信息&#…

基于Open3D的点云处理16-特征点匹配

点云配准 将点云数据统一到一个世界坐标系的过程称之为点云配准或者点云拼接。&#xff08;registration/align&#xff09; 点云配准的过程其实就是找到同名点对&#xff1b;即找到在点云中处在真实世界同一位置的点。 常见的点云配准算法: ICP、Color ICP、Trimed-ICP 算法…

ChatGPT Prompting开发实战(三)

一、关于chaining prompts与CoT的比较 前面谈到的CoT的推理过程&#xff0c;可以比作是一次性就烹调好一顿大餐&#xff0c;那么接下来要说的“chaining prompts”&#xff0c;其背后的理念是分多次来完成这样一项复杂任务&#xff0c;每次只完成其中一步或者一个子任务。核心…

如何制作并运行 jar 程序

以下是用 Intellij 制作 jar 程序&#xff0c;并运行的方法。 【1】新建工程&#xff0c;保持默认选项&#xff0c;Next 【2】保持默认选项&#xff0c;Next 【3】给工程命名&#xff0c;设置保存位置&#xff0c;Finish 【4】新建工程结束&#xff0c;进入开发界面 【5】展开…

Redis图文指南

1、什么是 Redis&#xff1f; Redis&#xff08;REmote DIctionary Service&#xff09;是一个开源的键值对数据库服务器。 Redis 更准确的描述是一个数据结构服务器。Redis 的这种特殊性质让它在开发人员中很受欢迎。 Redis不是通过迭代或者排序方式处理数据&#xff0c;而是…

Oracle21C--Windows卸载与安装

卸载方法&#xff1a; &#xff08;1&#xff09;WinR&#xff0c;输入services.msc,打开服务&#xff0c;把Oracle相关的服务全部停止运行&#xff08;重要&#xff09; &#xff08;2&#xff09;WinR&#xff0c;输入regedit&#xff0c;打开注册表&#xff0c;删除Oracle开…

无涯教程-Android - CheckBox函数

CheckBox是可以由用户切换的on/off开关。为用户提供一组互不排斥的可选选项时,应使用复选框。 CheckBox 复选框属性 以下是与CheckBox控件相关的重要属性。您可以查看Android官方文档以获取属性的完整列表以及可以在运行时更改这些属性的相关方法。 继承自 android.widget.T…

肖sir__linux详解__002(系统命令)

linux系统命令 1、df 查看磁盘使用情况 &#xff08;1&#xff09;df 查看磁盘使用情况&#xff08;按kb单位显示&#xff09; &#xff08;2&#xff09;df -h 按单位显示磁盘使用情况 2、top 实时查看动态进程 &#xff08;1&#xff09;top 详解&#xff1a; 第一行&…

19 Linux之Python定制篇-apt软件管理和远程登录

19 Linux之Python定制篇-apt软件管理和远程登录 文章目录 19 Linux之Python定制篇-apt软件管理和远程登录19.1 apt软件管理19.1.1 apt介绍19.1.2 更新软件下载地址-阿里源19.1.3 使用apt完成安装和卸载vim 19.2 远程登录Ubuntu 学习视频来自于B站【小白入门 通俗易懂】2021韩顺…

Linux x86_64 C语言实现gdb断点机制

文章目录 前言一、trap指令简介二、调用ptrace三、创建breakpoints四、CONT 和 SINGLESTEP五、完整代码演示六、增加参数检测参考资料 前言 本文参考文章&#xff1a;Implementing breakpoints on x86 Linux 一、trap指令简介 将通过在断点地址向目标进程的内存中插入一条新…

面试中的商业思维:如何展示你对业务的理解

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

SourceTree 使用技巧

参考资料 SourceTree使用教程&#xff08;一&#xff09;—克隆、提交、推送SourceTree的软合并、混合合并、强合并区别SourceTree 合并分支上的多个提交&#xff0c;一次性合并分支的多次提交至另一分支&#xff0c;主分支前进时的合并冲突解决 目录 一. 基础设置1.1 用户信息…

VR司法法治教育平台,沉浸式课堂教学培养刑侦思维和能力

VR司法法治教育平台提供了多种沉浸式体验&#xff0c;通过虚拟现实(Virtual Reality&#xff0c;简称VR)技术让用户深度参与和体验法治知识。以下是一些常见的沉浸式体验&#xff1a; 1.罪案重现 VR司法法治教育平台可以通过重现真实案例的方式&#xff0c;让用户亲眼目睹罪案发…

一文了解气象站是什么,作用有哪些?

气象站被广泛应用于气象、农业、交通、能源、水利、环保等领域&#xff0c;是一种用于收集、分析和处理气象数据的设备&#xff0c;能够为人们提供及时、准确的气象数据和决策支持。 气象站一般由传感器、环境监控主机和监控平台组成。传感器能够测量各种气象要素&#xff0c;…

Spring 中存取 Bean 的相关注解

目录 一、五大类注解 1、五大类注解存储Bean对象 1.1Controller(控制器储存) 1.2Service(服务存储) 1.3Repository(仓库存储) 1.4Component(组件存储) 1.5Configuration(配置存储) 2、五大类注解小结 2.1为什么要这么多类注解 2.2 五大类注解之间的关系 二、方法注解 1.方法注…

SQL-子查询

SQL 子查询 是指将一个SELECT查询&#xff08;子查询&#xff09;的结果用括号括起来作为另一个SQL语句的数据来源或者判断条件

【Bug】Ubuntu 有线设置打不开无反应

前言&#xff1a; 突然有线设置就没法启用了&#xff0c;但是能联网&#xff0c;能查看ip 解决&#xff1a; 最后安装了一个新的依赖包&#xff1a; sudo apt install gnome-control-center 然后就可以了 还有一个方法&#xff0c;没试过&#xff0c;但感觉有点道理的&#…