搜索二叉树
二叉搜索树概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
-
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
-
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值它的左右子树也分别为二叉搜索树
-
二叉搜索树的中序遍历为有序序列
-
二叉搜索树的子树也是二叉搜索树
-
时间复杂度:
- 在理想情况下(即树保持平衡时),二叉搜索树的查找、插入和删除操作的时间复杂度都是O(log n),其中n是树中节点的数量
- 然而,在最坏情况下(即树退化为链表时),这些操作的时间复杂度会退化到O(n)。为了避免这种情况,通常会使用平衡二叉搜索树(如AVL树、红黑树等)来保持树的平衡
二叉搜索树操作
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
二叉搜索树的查找
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return true;
}
}
return false;
}
二叉搜索树的插入
插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b.树不空,按二叉搜索树性质查找插入位置,插入新节点
bool Insert(const K& key) {
//空树
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = parent->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = parent->_right;
}
//不允许插入重复值
else
{
return false;
}
}
cur = new Node(key);
if (cur->_key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情
况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除
bool Erease(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
//找到要删除的元素
if (key < cur->_key)
{
parent = cur;
cur = parent->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = parent->_right;
}
//找到元素所在位置
else
{
//当有0/1个孩子节点时
//1. 当只有左孩子
if (cur->_right == nullptr)
{
//当cur为根节点时
if (parent == nullptr)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
return true;
}
//2. 当只有右孩子时
else if (cur->_left == nullptr)
{
if (parent == nullptr)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
cur = nullptr;
return true;
}
//当两个孩子都有
else
{
Node* rightMinP = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinP = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
if (rightMinP->_left == rightMin)
rightMinP->_left = rightMin->_right;
else
rightMinP->_right = rightMin->_right;
delete rightMin;
return true;
}
}
}
return false;
}
当有0/1个孩子时
当有两个孩子时
总结
这里主要实现了二叉树的搜索查找,搜索以及删除操作,只是二叉搜索树的简易实现
这三种操作的主要逻辑都是通过while循环来查找
-
查找通过while来寻找相同值
-
插入也就是key不存在的情况
-
删除也就是当找到key时我们要想办法将它删除
二叉搜索树的具体代码实现
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<iostream>
using namespace std;
namespace bit
{
template<class K>
struct BSTNode
{
K _key;
BSTNode<K>* _left;
BSTNode<K>* _right;
BSTNode(const K& key)
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
template<class K>
class BSTree {
typedef BSTNode<K> Node;
public:
BSTree(initializer_list<K> init)
{
for (const auto& e : init)
{
Insert(e);
}
}
bool Insert(const K& key) {
//空树
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = parent->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = parent->_right;
}
//不允许插入重复值
else
{
return false;
}
}
cur = new Node(key);
if (cur->_key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return true;
}
}
return false;
}
bool Erease(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
//找到要删除的元素
if (key < cur->_key)
{
parent = cur;
cur = parent->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = parent->_right;
}
//找到元素所在位置
else
{
//当有0/1个孩子节点时
//1. 当只有左孩子
if (cur->_right == nullptr)
{
//当cur为根节点时
if (parent == nullptr)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
cur = nullptr;
return true;
}
//2. 当只有右孩子时
else if (cur->_left == nullptr)
{
if (parent == nullptr)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
delete cur;
cur = nullptr;
return true;
}
//当两个孩子都有
else
{
Node* rightMinP = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinP = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
if (rightMinP->_left == rightMin)
rightMinP->_left = rightMin->_right;
else
rightMinP->_right = rightMin->_right;
delete rightMin;
return true;
}
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
}
相关例题
1, 关于二叉搜索树特性说法错误的是( )
A.二叉搜索树最左侧的节点一定是最小的
B.二叉搜索树最右侧的节点一定是最大的
C.对二叉搜索树进行中序遍历,一定能够得到一个有序序列
D.二叉搜索树的查找效率为O(log_2N)
2,下面关于二叉搜索树正确的说法是( )
A.待删除节点有左子树和右子树时,只能使用左子树的最大值节点替换待删除节点
B.给定一棵二叉搜索树的前序和中序遍率历结果,无法确定这棵二叉搜索树
C.给定一棵二叉搜索树,根据节点值大小排序所需时间复杂度是线性的
D.给定一棵二叉搜索树,可以在线性时间复杂度内转化为平衡二叉搜索树
3, 将整数序列(7-2-4-6-3-1-5)按所示顺序构建一棵二叉排序树a(亦称二叉搜索树),之后将整数8按照二叉排序树规则插入树a中,请问插入之后的树a中序遍历结果是( )
A.1-2-3-4-5-6-7-8
B.7-2-1-4-3-6-5-8
C.1-3-5-2-4-6-7-8
D.1-3-5-6-4-2-8-7
E.7-2-8-1-4-3-6-5
F.5-6-3-4-1-2-7-8
答案与解析:
1,
二叉搜索树的概念:
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
从概念中可以得出以下性质:
- 二叉搜索树中最左侧节点一定是最小的,最右侧节点一定是最大的
- 对二叉搜索树进行中序遍历,可以得到一个有序的序列
A:正确
B:正确
C:正确
D:错误,二叉搜索树最差情况下会退化为单支树,因此:其查找的效率为O(N)
因此:选择D
2,
A:错误,当待删除节点的左右子树均存在时,既可以在左子树中找一个最大的节点作为替代节 点,也可以在右子树中找一个最小的节点作为替代节点,左右子树中都可以找替代节点
B:错误,根据前序遍历和中序遍历,是可以确定一棵树的结构,使用两个遍历结果确定树的结构, 其中有一个遍历结果必须要是中序遍历结果。
C:正确,二叉搜索树遍历一遍,就可以得到一个有序序列,因此,时间复杂度为O(N)
D:错误,这里面还需要牵扯到旋转等其他操作,时间复杂度不是线性的
因此:选择C
3,
插入之后的树仍旧是二叉搜索树,因此只要是有序的结果则正确,而有序的结果只有A
因此:选择A
二叉搜索树的退化 ↩︎