1. 二叉搜索树
- 二叉搜索树又称二叉排序树,
性质:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值;
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值;
- 它的左右子树也分别是二叉搜索树;
- 可以是一棵空树。
2. 二叉搜索树的操作
- 二叉树的结构
template<class T>
struct BNode
{
T _data;
typedef BNode<T> Node;
Node* _left;
Node* _right;
BNode(const T& data)
:_data(data)
,_left(nullptr)
,_right(nullptr)
{}
};
public:
//拷贝二叉搜索树的数据和结构
Node* copy(Node* root)
{
if (root == nullptr)
return nullptr;
//前序拷贝
Node* newnode = new Node(root->_data);
newnode->_left = copy(root->_left);
newnode->_right = copy(root->_right);
return newnode;
}
//拷贝构造
BTree (const BTree<T>& btree)
:_root(copy(btree._root))
{}
BTree()
:_root(nullptr)
{}
void destroy(Node* root)
{
if (root)
{
destroy(root->_left);
destroy(root->_right);
cout << "destroy:" << root->_data << endl;
delete root;
}
}
~BTree()
{
if (_root)
{
destroy(_root);
_root = nullptr;
}
}
private:
Node* _root;
2.1 二叉搜索树的查找
根节点不为空:
- 若根节点key==查找key,返回true
- 若根节点key>查找key,在其左子树查找
- 若根节点key>查找key,在其右子树查找
- 否则,返回false
typedef BNode<T> Node;
Node* find(const T& val)
{
Node* cur = _root;
while (cur)
{
if (cur->_data == val)
return cur;
else if (cur->_data > val)
cur = cur->_left;
else
cur = cur->_right;
}
}
2.2 二叉搜索树的插入
- 树为空,直接插入,返回true
- 树不空,按二叉搜索树性质查找插入位置,插入新节点
//不插入重复的值
bool insert(const T& val)
{
if (_root == nullptr)
{
_root = new Node(val);
return true;
}
//搜索,找到合适的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (cur->_data == val)
return false;
else if (cur->_data > val)
cur = cur->_left;
else
cur = cur->_right;
}
//插入
cur = new Node(val);
if (parent->_data > val)
parent->_left = cur;
else
parent->_right = cur;
}
2.3 二叉搜索树的删除
- 首先查找元素是否在二叉树中,如果不存在,则返回,否则要删除的情况如下:
- 要删除的节点无孩子节点;
- 要删除的节点只有左孩子节点;-- 删除该节点且使被删除节点的双亲节点指向被删除节点的左孩子节点;
- 要删除的节点只有右孩子节点;-- 删除该节点且使被删除节点的双亲节点指向被删除节点的右孩子节点;
- 要删除的节点有左、右节点; – 在它的右子树中寻找中序下的第一个节点(关键码最小),用它的值填补到被删除节点中,再处理该节点的删除问题。
//删除
bool erase(const T& val)
{
//查找
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_data == val)
break;
parent = cur;
if (cur->_data > val)
cur = cur->_left;
else
cur = cur->_right;
}
//判断是否找到了要删除的节点
if (_root == nullptr)
return false;
//删除
//1.删除的为叶子节点
if (cur->_left == nullptr && cur->_right == nullptr)
{
//判断是否为根节点
if (cur == _root)
{
_root = nullptr;
}
//判断需要删除的节点在父节点哪一边
else
{
if (parent->_left == cur)
parent->_left = nullptr;
else
parent->_right = nullptr;
}
//删除节点
delete cur;
}
//2. 非叶子节点,左孩子为空
else if(cur->_left == nullptr)
{
//判断当前节点是否是根节点
if (cur == _root)
//更新根节点
_root = cur->_right;
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
//删除节点
delete cur;
}
//3.非叶子节点,右孩子为空
else if (cur->_right == nullptr)
{
//判断当前节点是否是根节点
if (cur == _root)
//更新根节点
_root = cur->_left;
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
//删除节点
delete cur;
}
//4.非叶子节点,左右孩子都存在
else
{
//1.假设找左子树的最右节点
Node* leftRightMost = cur->_left;
parent = cur;
while (leftRightMost->_right)
{
parent = leftRightMost;
leftRightMost = leftRightMost->_right;
}
//2.交换
swap(cur->_data, leftRightMost->_data);
//3. 删除最右节点
if (parent->_left == leftRightMost)
parent->_left = leftRightMost->_left;
else
parent->_right = leftRightMost->_left;
delete leftRightMost;
}
return true;
}
3. 二叉搜索树的性能分析
- 插入和删除操作都必须先查找,查找效率代表了二叉搜索树的各个操作的性能。
- 对有n个节点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是节点在二叉搜索树的深度的函数,即节点越深,比较次数越多。
- 最优情况下:二叉搜索树为完全二叉树,其平均比较次数为log2N
- 最差情况下:二叉搜索树退化为单支树,其平均比较次数为:N/2