目录
一.概念
二.分部模拟实现(K模型)
1.二叉树结点
2.二叉搜索树构建
3.查找(非递归)
4.插入(非递归)
5.删除(非递归)
6.查找(递归)
7.插入(递归)
8.删除(递归)
三.模拟实现总代码(K模型)
四.模拟实现(KV模型)
五.二叉搜索树的应用
1.K模型
2.KV模型
六.二叉搜索树的性能
前言:二叉搜索树是为学习map和set做的铺垫,了解了二叉搜索树的特性,有助于更好的理解map和set。
二叉搜索树又称二叉排序树,它可以是一颗空树,或者是具有以下性质的二叉树:
① 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值
② 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值
③ 它的左右子树也分别为二叉搜索树
_key为该二叉树值,_left为左结点,_right为右结点。
template
struct BSTreeNode
{
BSTreeNode* _left;
BSTreeNode* _right;
K _key;
BSTreeNode(const K& key)
: _left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
实现二叉搜索树的构造、拷贝构造、赋值重载、析构函数。
拷贝构造是一定要实现的,通过子函数CopyTree通过前序遍历进行依次构造结点,而构造函数用编译器默认生成的就够了,但是因为实现了拷贝构造,编译器就不会生成默认的构造,因此使用C++11的关键字default来强制编译器生成构造。
赋值重载是现代写法,析构函数也是通过子函数DestoryTree,通过递归依次释放结点。
template
class BSTree
{
typedef BSTreeNode Node;
private:
void DestroyTree(Node* root)
{
if (root == nullptr)
{
return;
}
DestroyTree(root->_left);
DestroyTree(root->_right);
delete root;
}
Node* CopyTree(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* copyNode = new Node(root->_key);
CopyTree(root->_left);
CopyTree(root->_right);
return copyNode;
}
public:
// C++11,强制让编译器自己生成构造
BSTree() = default;
BSTree(const BSTree& t)
{
_root = CopyTree(t._root);
}
BSTree& operator=(BSTree t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
DestroyTree(_root);
_root = nullptr;
}
private:
Node* _root = nullptr;
};
遍历寻找,如果要查找的key值大于该结点的key值,就往该结点的右子树走,如果要查找的key值小于该结点的key值,就往该结点的左子树走。
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;
}
找到位置就插入进去。如果是空树,直接插入作为根结点。
需要定义一个parent变量,这样在找到插入的位置后,可以让该插入结点的父节点parent链接上该新插入的结点。
前面与查找类似,如果要查找的key值大于该结点的key值,就往该结点的右子树走,如果要查找的key值小于该结点的key值,就往该结点的左子树走,如果相等了,就不能插入了,直接返回false。
找到要插入的位置之后,new出要插入的新结点,然后判断这个结点的值是大于parent还是小于parent,如果大于就放在右边,如果小于就放在左边。
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;
}
删除需要考虑的比较多。
首先也是需要定义一个parent变量,这样删除掉结点后,可以让其parent连接到删除结点的孩子结点。
接着也是与查找类似,在找到之后,就要分情况讨论。
第一种情况是要删除的结点的左孩子为空:这种情况首先要注意要删除的结点是根结点的情况,因为是根结点,所以parent是空,因此要单独写,让新的根结点变为原根结点的右结点。另外就是正常情况,要判断删除的这个结点的值是parent的左孩子结点还是右孩子结点,如果是左孩子结点,就让其parent的左孩子结点链接上该结点的右孩子结点,如果是右孩子结点,就让parent的右孩子结点也链接上该结点的右孩子结点。
第二种情况是要删除的结点的右孩子为空:这种情况与第一种情况类似,也是要注意特殊情况,但是这种情况是让新的根结点变为原根结点的左结点。然后再判断删除的这个结点时parent的左孩子结点还是右孩子结点,如果是左孩子结点,就让parent的左孩子结点链接上该结点的左孩子结点,如果是右孩子结点,就让parent的右孩子结点链接上该结点的左孩子结点。
第三种情况是要删除的结点的左右孩子都不为空:出现这种情况时,不能直接删除掉需要删除的结点,而是需要替代删除,保证二叉搜索树的特性。这种替代有两种方法(第一种是用该结点的左子树的最大结点替代,第二种是用该结点的右子树的最小结点替代),这里我们采用第二种方法。首先需要定义两个变量,一个是用来找到这个去替代的结点minRight,另一个是该替代的结点的父节点minParent,这里要注意定义minParent时,要让其等于cur,如果让其等于nullptr,那么如果该结点的左结点没有结点了就会导致minParent一直为空,出现野指针问题。定义了之后,通过while循环找到该结点右子树的最小结点(即该结点右子树的最左的结点),接下来可以交换要删除结点的值和替代结点的值,也可以直接赋值,因为替代了结点后就被删除,是否交换并不重要。接下来再判断替代结点是minParent的左孩子结点还是右孩子结点,如果是左孩子结点,就让minParent的左孩子节点链接上替代结点的右孩子节点,如果是右孩子结点,就让minParent的右孩子结点链接上替代结点的右孩子结点。
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
{
// 左孩子为空
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;
}
// 右孩子为空
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
// 两个孩子都不为空
else
{
// 用右子树的最小结点去替代
Node* minParent = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
minParent = minRight;
minRight = minRight->_left;
}
swap(minRight->_key, cur->_key);
if (minParent->_left == minRight)
{
minParent->_left = minRight->_right;
}
else
{
minParent->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
递归写法就采用子函数的方法,查找、插入、删除都是如此。
如果要查找的值比此结点大,就递归调用该结点的右子树结点,如果要查找的值比此结点小,就递归调用该结点的左子树结点,如果相等了就返回true,遇到空结点就返回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);
}
else
{
return true;
}
}
与递归查找法类似,只是插入是遇到空会创建插入结点,返回true,相等时返回false。
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);
}
else
{
return false;
}
}
前面就是查找,找到之后,也是要分三种情况讨论。
这里我们注意形参root的变量类型是Node*&,是指针的引用。
第一种左为空:因为是指针引用,因此直接让root等于root的右子树结点,因为是引用,所以如果上一个递归调用的是root->_right那么这个root就是上一个root->_right的别名,因此就相当于上一个的root->_right = 当前的root->_right,这样就相当于删除掉了这个root。
第二种右为空:与第一种类似,root = root->_left,把_right改为_left即可。
第三种左右都不为空:这个就只需要定义一个替代变量minRight即可,然后通过while找到这个变量,这个一定要交换,不能赋值更改,因为接下来是要使用递归的。要注意这里递归的是root的右子树结点(不能是当前结点,原因是在交换之后,该结点已经不满足二叉搜索树的特性了,用该结点就会出错),其右子树结点及其右子树所有结点都是满足二叉搜索树的特性的,这样递归之后,就满足上面两种情况之一,就可以完成删除了。
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* minRight = root->_right;
while (minRight->_left)
{
minRight = minRight->_left;
}
swap(root->_key, minRight->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
namespace key
{
template
struct BSTreeNode
{
BSTreeNode* _left;
BSTreeNode* _right;
K _key;
BSTreeNode(const K& key)
: _left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
template
class BSTree
{
typedef BSTreeNode Node;
private:
void DestroyTree(Node* root)
{
if (root == nullptr)
{
return;
}
DestroyTree(root->_left);
DestroyTree(root->_right);
delete root;
}
Node* CopyTree(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* copyNode = new Node(root->_key);
CopyTree(root->_left);
CopyTree(root->_right);
return copyNode;
}
public:
// C++11,强制让编译器自己生成构造
BSTree() = default;
BSTree(const BSTree& t)
{
_root = CopyTree(t._root);
}
BSTree& operator=(BSTree t)
{
swap(_root, t._root);
return *this;
}
~BSTree()
{
DestroyTree(_root);
_root = nullptr;
}
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;
}
// const Node* Find(const K& key)
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
{
// 左孩子为空
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;
}
// 右孩子为空
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
delete cur;
}
// 两个孩子都不为空
else
{
// 用右子树的最小结点去替代
Node* minParent = cur;
Node* minRight = cur->_right;
while (minRight->_left)
{
minParent = minRight;
minRight = minRight->_left;
}
swap(minRight->_key, cur->_key);
if (minParent->_left == minRight)
{
minParent->_left = minRight->_right;
}
else
{
minParent->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
// 递归写法:
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);
}
void InOrder()
{
_InOrder(_root);
}
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);
}
else
{
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);
}
else
{
return false;
}
}
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* minRight = root->_right;
while (minRight->_left)
{
minRight = minRight->_left;
}
swap(root->_key, minRight->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
Node* _root = nullptr;
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
};
}
这个和K模型的很类似,就大致实现一下,这里用修改的是递归版本的。
只需要增加一下value变量,增加一下模板,注意key加const。
namespace key_value
{
template
struct BSTreeNode
{
BSTreeNode* _left;
BSTreeNode* _right;
const K _key;
V _value;
BSTreeNode(const K& key, const V& value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
, _value(value)
{}
};
template
class BSTree
{
typedef BSTreeNode Node;
public:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
Node* FindR(const K& key)
{
return _FindR(_root, key);
}
bool InsertR(const K& key, const V& value)
{
return _InsertR(_root, key, value);
}
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* minRight = root->_right;
while (minRight->_left)
{
minRight = minRight->_left;
}
swap(root->_key, minRight->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
bool _InsertR(Node*& root, const K& key, const V& value)
{
if (root == nullptr)
{
root = new Node(key, value);
return true;
}
if (root->_key < key)
return _InsertR(root->_right, key, value);
else if (root->_key > key)
return _InsertR(root->_left, key, value);
else
return false;
}
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
return nullptr;
if (root->_key < key)
{
return _FindR(root->_right, key);
}
else if (root->_key > key)
{
return _FindR(root->_left, key);
}
else
{
return root;
}
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
}
K模型只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值。
例子:判断单词是否拼写正确
用词库中的单词集合中的每个单词作为key,构建二叉搜索树。
每一个关键码key,都有与之对应的值value,即
例子:①英汉词典,中英文的对应关系, 通过英文可以快速找到对应的中文。
②统计单词的次数,单词与其出现次数构成键值对。
对于相同的元素,如果key插入的次序不同,可能会得到不同结果的二叉搜索树。
这里要注意二叉搜索树的效率是O(N),而不是O(log2N) 。
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其比较次数是log2N。
最差情况下,二叉搜索树退化为单支树(或者类似单支),其比较次数为N
因此,我们接下来要学习平衡二叉搜索树(AVL树、红黑树),这个就使二叉搜索树趋近于完全二叉树,尽可能保证搜索效率。