本文介绍了二叉搜索树的相关概念,主要介绍了如何使用和实现搜索二叉树以及搜索二叉树具体的例题练习。
二叉搜索树也称为二叉排序树。
二叉树有以下性质:
1.空树是二叉搜索树;
2.二叉搜索树的非空左子树的所有节点小于根节点的值;
3.二叉搜索树的非空右子树的所有节点大于根节点的值;
4.二叉搜索树的左右子树也是二叉搜索树。
搜索二叉树不支持修改。
pnode Find(K k)//查
{
pnode cur = _pnode;
while (cur)
{
if (cur->_k > k)
{
cur = cur->left;
}
else if (cur->_k < k)
{
cur = cur->right;
}
else
{
return cur;
}
}
return cur;
}
bool insert(K k)//增
{
pnode newnode = new node(k);
//如果该树没有节点就直接插入新节点为根节点
if (_pnode == nullptr)
{
_pnode = newnode;
return true;
}
pnode cur = _pnode;
pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
while (cur)
{
parent = cur;
if (cur->_k > k)
{
cur = cur->_left;
}
else if (cur->_k < k)
{
cur = cur->_right;
}
else//如果该节点已经在树中存在就插入失败
{
return false;
}
}
//插入元素
if (parent->_k > k) parent->_left = newnode;
else parent->_right = newnode;
return true;
}
bool erase(K k)//删
{
//树为空删除失败
if (_pnode == nullptr) return false;
pnode cur = _pnode;
pnode parent = cur;
while (cur)
{
if (cur->_k > k)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_k < k)
{
parent = cur;
cur = cur->_right;
}
else//删除元素(cur->_k == k)
{
//无孩子
if (cur->_left == nullptr && cur->_right == nullptr)
{
if (cur == parent->_left) parent->_left = nullptr;
else parent->_right = nullptr;
delete cur;
cur = nullptr;
}
//有左孩子,没有右孩子
else if (cur->_left && cur->_right == nullptr)
{
if (cur == parent->_left) parent->_left = cur ->_left;
else parent->_right = cur ->_left;
delete cur;
cur = nullptr;
}
//有右孩子,没有左孩子
else if (cur->_left == nullptr && cur->_right)
{
if (cur == parent->_left) parent->_left = cur->_right;
else parent->_right = cur->_right;
delete cur;
cur = nullptr;
}
//两个孩子都有(替换删除法)
else if (cur->_left&&cur->_right)
{
pnode pR = cur->_right;//cur的右子树的根节点
pnode pRL = cur->_right;//cur的右子树的最左节点
while (pRL->_left)
{
pRL = cur->_left;
}
K temp = pRL->_k;
erase(temp);
cur->_k = temp;
}
return true;
}
}
//树中没有该元素删除失败
return false;
}
模拟实现搜索二叉树的完整代码在文末。
只有K作为关键码,结构中只需要存储K值即可,关键码即为需要搜索到的值。
例题,判断一个单词拼写是否正确?
- 将词库中所有单词集合中每一个单词作为K值,构建一棵二叉树;
- 在这个二叉树中检索是否存在这个单词,如果存在说明拼写正确,如果不存在则说明拼写错误。
每一个关键码K都有一个对应的V值,即
例如,英汉词典,通过英文可以快速找到中文,英文与中文可以构成一个键值对
。
例如,计算单词出现的次数,用单词可以找到它出现的次数,单词与它出现的次数可以构成一个键值对。
英汉词典的例子:
namespace kv
{
template<class K,class V>
struct BSTnode
{
BSTnode(const K& k = K(),const V& v = V())
:_k(k)
, _v(v)
, _left(nullptr)
, _right(nullptr)
{}
K _k;
V _v;
BSTnode<K, V>* _left;
BSTnode<K, V>* _right;
};
template<class K, class V>
class BSTree
{
typedef BSTnode<K,V> node;
typedef node* pnode;
public:
BSTree()//构造
:_pnode(nullptr)
{}
BSTree(const BSTree<K,V>& t)//拷贝构造
{
_pnode = Copy(t._pnode);
}
BSTree<K,V>& operator=(const BSTree<K,V> t)//赋值运算符重载
{
swap(_pnode, t._pnode);
return *this;
}
~BSTree()//析构
{
destory(_pnode);
_pnode = nullptr;
}
pnode Find(const K& k)//查
{
pnode cur = _pnode;
while (cur)
{
if (cur->_k > k)
{
cur = cur->_left;
}
else if (cur->_k < k)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return cur;
}
bool insert(const K& k, const V& v)//增
{
pnode newnode = new node(k,v);
//如果该树没有节点就直接插入新节点为根节点
if (_pnode == nullptr)
{
_pnode = newnode;
return true;
}
pnode cur = _pnode;
pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
while (cur)
{
parent = cur;
if (cur->_k > k)
{
cur = cur->_left;
}
else if (cur->_k < k)
{
cur = cur->_right;
}
else//如果该节点已经在树中存在就插入失败
{
return false;
}
}
//插入元素
if (parent->_k > k) parent->_left = newnode;
else parent->_right = newnode;
return true;
}
bool erase(const K& k)//删
{
//树为空删除失败
if (_pnode == nullptr) return false;
pnode cur = _pnode;
pnode parent = cur;
while (cur)
{
if (cur->_k > k)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_k < k)
{
parent = cur;
cur = cur->_right;
}
else//删除元素(cur->_k == k)
{
//无孩子
if (cur->_left == nullptr && cur->_right == nullptr)
{
if (cur == parent->_left) parent->_left = nullptr;
else parent->_right = nullptr;
delete cur;
cur = nullptr;
}
//有左孩子,没有右孩子
else if (cur->_left && cur->_right == nullptr)
{
if (cur == parent->_left) parent->_left = cur->_left;
else parent->_right = cur->_left;
delete cur;
cur = nullptr;
}
//有右孩子,没有左孩子
else if (cur->_left == nullptr && cur->_right)
{
if (cur == parent->_left) parent->_left = cur->_right;
else parent->_right = cur->_right;
delete cur;
cur = nullptr;
}
//两个孩子都有(替换删除法)
else if (cur->_left&&cur->_right)
{
pnode pR = cur->_right;//cur的右子树的根节点
pnode pRL = cur->_right;//cur的右子树的最左节点
while (pRL->_left)
{
pRL = cur->_left;
}
K temp = pRL->_k;
erase(temp);
cur->_k = temp;
}
return true;
}
}
//树中没有该元素删除失败
return false;
}
//中序遍历
void inorder()
{
_inorder(_pnode);
cout << endl;
}
private:
void destory(pnode p)
{
if (p == nullptr) return;
destory(p->_left);
destory(p->_right);
delete p;
}
pnode Copy(pnode root)
{
if (root == nullptr) return nullptr;
pnode newnode = new node(root->_k);
newnode->_left = Copy(root->_left);
newnode->_right = Copy(root->_right);
return newnode;
}
void _inorder(pnode root)
{
if (root == nullptr) return;
_inorder(root->_left);
cout << '<'<<root->_k<<','<<root->_v<<'>' << " ";
_inorder(root->_right);
}
pnode _pnode;
};
void test()
{
kv::BSTree<string, string> dict;
dict.insert("sort", "排序");
dict.insert("string", "字符串");
dict.insert("left", "左边");
dict.insert("right", "右边");
string str;
while (cin >> str)
{
kv::BSTnode<string, string>* ret = dict.Find(str);
if (ret)
{
cout << ret->_v << endl;
}
else
{
cout << "无此单词" << endl;
}
}
}
}
二叉树的插入和删除都需要先进行查找,因此查找的效率代表着二叉树的性能。
对有n个节点的二叉树,若每个元素的查找概率相同,那么二叉搜索树平均查找次数是二叉树的高度次,即二叉树越高比较次数越多。
但对于同一个关键码的集合,如果各个关键码的插入次序不同,所得到的二叉树的结构可能不同。
二叉搜索树性能最好是它的结构为完全二叉树(或接近完全二叉树),它的平均比较次数为 l o g 2 N log_2 N log2N;
二叉搜索树性能最差是他的结构退化成单支,如图中右边的二叉树,它的平均比较次数为 N 2 \frac{N}{2} 2N。
如果搜索二叉树退化成单支,那么它的性能就丢失了,如何对其进行改进呢?这就涉及我们之后介绍的AVL树和红黑树。
namespace Jinger
{
template<class K>
struct BSTnode
{
BSTnode(const K& k = K())
:_k(k)
,_left(nullptr)
, _right(nullptr)
{}
K _k;
BSTnode<K>* _left;
BSTnode<K>* _right;
};
template<class K>
class BSTree
{
typedef BSTnode<K> node;
typedef node* pnode;
public:
BSTree()//构造
:_pnode(nullptr)
{}
BSTree(const BSTree<K>& t)//拷贝构造
{
_pnode = Copy(t._pnode);
}
BSTree<K>& operator=(const BSTree<K> t)//赋值运算符重载
{
swap(_pnode,t._pnode);
return *this;
}
~BSTree()//析构
{
destory(_pnode);
_pnode = nullptr;
}
pnode Find(const K& k)//查
{
pnode cur = _pnode;
while (cur)
{
if (cur->_k > k)
{
cur = cur->left;
}
else if (cur->_k < k)
{
cur = cur->right;
}
else
{
return cur;
}
}
return cur;
}
bool insert(const K& k)//增
{
pnode newnode = new node(k);
//如果该树没有节点就直接插入新节点为根节点
if (_pnode == nullptr)
{
_pnode = newnode;
return true;
}
pnode cur = _pnode;
pnode parent = cur;//记录当前节点的父节点,因为如果要插入就要插入在当前节点的父节点的左右孩子处
while (cur)
{
parent = cur;
if (cur->_k > k)
{
cur = cur->_left;
}
else if (cur->_k < k)
{
cur = cur->_right;
}
else//如果该节点已经在树中存在就插入失败
{
return false;
}
}
//插入元素
if (parent->_k > k) parent->_left = newnode;
else parent->_right = newnode;
return true;
}
bool erase(const K& k)//删
{
//树为空删除失败
if (_pnode == nullptr) return false;
pnode cur = _pnode;
pnode parent = cur;
while (cur)
{
if (cur->_k > k)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_k < k)
{
parent = cur;
cur = cur->_right;
}
else//删除元素(cur->_k == k)
{
//无孩子
if (cur->_left == nullptr && cur->_right == nullptr)
{
if (cur == parent->_left) parent->_left = nullptr;
else parent->_right = nullptr;
delete cur;
cur = nullptr;
}
//有左孩子,没有右孩子
else if (cur->_left && cur->_right == nullptr)
{
if (cur == parent->_left) parent->_left = cur ->_left;
else parent->_right = cur ->_left;
delete cur;
cur = nullptr;
}
//有右孩子,没有左孩子
else if (cur->_left == nullptr && cur->_right)
{
if (cur == parent->_left) parent->_left = cur->_right;
else parent->_right = cur->_right;
delete cur;
cur = nullptr;
}
//两个孩子都有(替换删除法)
else if (cur->_left&&cur->_right)
{
pnode pR = cur->_right;//cur的右子树的根节点
pnode pRL = cur->_right;//cur的右子树的最左节点
while (pRL->_left)
{
pRL = cur->_left;
}
K temp = pRL->_k;
erase(temp);
cur->_k = temp;
}
return true;
}
}
//树中没有该元素删除失败
return false;
}
//中序遍历
void inorder()
{
_inorder(_pnode);
cout<<endl;
}
private:
void destory(pnode p)
{
if (p == nullptr) return;
destory(p->_left);
destory(p->_right);
delete p;
}
pnode Copy(pnode root)
{
if (root == nullptr) return nullptr;
pnode newnode = new node(root->_k);
newnode->_left = Copy(root->_left);
newnode->_right = Copy(root->_right);
return newnode;
}
void _inorder(pnode root)
{
if (root == nullptr) return;
_inorder(root->_left);
cout << root->_k << " ";
_inorder(root->_right);
}
pnode _pnode;
};
//测试插入和删除
void test1()
{
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
BSTree<int> t;
for (auto e : a)
{
t.insert(e);
}
t.inorder();
t.erase(3);
t.inorder();
t.erase(13);
t.inorder();
}
}
以上就是今天要讲的内容,本文介绍了二叉搜索树的相关概念。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!