1.二叉搜索树的特点
- 二叉搜索树也可以是空树
- 如果它的左子树不为空,则左子树上的所有节点都小于根节点的值
- 如果它的右子树不为空,则右子树上的所有节点都大于根节点的值
- 它的左右子树都是二叉树搜索树
2.二叉树的操作
1.查找
bool Find(const K& key)
2.插入
bool Insert(const K& key)
插入操作和查找操作的逻辑是一样的
- 从根节点开始寻找,如果比根节点小,就向左寻找,如果比根节点大,就向右寻找。
- 查找:如果找到空还没找到目标值,就说明值不存在
- 插入:如果找到空就可以插入节点了
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 Insert(const K& key)
{
Node* pnode = new Node(key);
//如果是第一个节点直接插入
if (_root == nullptr)
{
_root = pnode;
return true;
}
else
{
//设置一个父亲节点用于连接
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
//这里需要判断一下这个节点在父节点的左边还是右边
if (key < parent->_key)
parent->_left = pnode;
else if (key > parent->_key)
parent->_right = pnode;
return true;
}
}
3 .删除
a.删除节点,首先要找到要删除的节点,此时需要使用查找的逻辑
b.如果找到需要删除的节点,就需要判断这个节点有几个孩子
- 如果该节点没有左右孩子,直接删除该节点即可
- 如果该节点只有左孩子,把这个节点的左孩子托付给该节点的父亲,然后删除该节点
- 如果该节点只有右孩子,把这个节点的右孩子托付给该节点的父亲,然后删除该节点
- 如果该节点有左右孩子,需要使用替换法,将(最大左子树节点/最小右子树)节点的值,和该节点的值交换,然后删除最大左子树节点/最小右子树)节点
bool Erase(const K& key)
{
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else//找到了要删除的数
{
//分三种情况
//1.没有孩子 2.有一个孩子 3.有两个孩子
//1和2情况可以合为一个情况
if (cur->_left == nullptr)//把cur的右孩子托付给parent
{
//cur是parent的左孩子
if (parent->_key > cur->_key)
{
parent->_left = cur->_right;
}
else//cur是parent的右孩子
{
parent->_right = cur->_right;
}
delete cur;
return true;
}
else if (cur->_right == nullptr)//把cur的左孩子托付给parent
{
//cur是parent的左孩子
if (parent->_key > cur->_key)
{
parent->_left = cur->_left;
}
else//cur是parent的右孩子
{
parent->_right = cur->_left;
}
delete cur;
return true;
}
else//删除节点有两个孩子
{
//使用替换法,使用最大左孩子的替代
Node* MinRight = cur->_left;
Node* MinParent = cur;
while (MinRight->_right)
{
MinParent = MinRight;
MinRight = MinRight->_right;
}
cur->_key = MinRight->_key;
//最大左孩子没有右孩子
if (MinParent->_left == MinRight)
{
MinParent->_left = MinRight->_left;
}
else if (MinParent->_right== MinRight)
{
MinParent->_right = MinRight->_left;
}
delete MinRight;
return true;
}
}
}
//找不到删除的数
return false;
}
4.递归写法
- 查找递归写法没有什么要注意,直接看代码即可
- 插入递归写法,函数参数要使用引用指针,这样可以直接改变指针的指向,不需要记录父节点指针了
bool FindR(const K& key) { return _FindR(_root, key); } bool _FindR(Node* root, const K& key) { if (root == nullptr) return false; else if (root->_key > key) _FindR(root->_left, key); else if(root->_key < key) _FindR(root->_right, key); else return true; } bool InsertR(const K& key) { return _InsertR(_root, key); } //这里使用引用,可以直接修改root bool _InsertR(Node*& root, const K& key) { //等于NULL在此处插入,root是引用 if(root==nullptr) { Node* NewNode = new Node(key); root = NewNode; return true; } if (root->_key > key) { return _InsertR(root->_left,key); } else if (root->_key < key) { return _InsertR(root->_right,key); } else return false; }
-
删除递归写法,也需要使用引用指针。需要注意:在交换节点的值之后,想要删除最大右孩子,需要调用_EraseR(root->_left, key),而不是 _EraseR(MinRight, key),因为需要通过引用指针去改变父亲节点的指向,如果调用_EraseR(MinRight, key)就无法改变。
bool EraseR(const K& key) { return _EraseR(_root, key); } bool _EraseR(Node*& root, const K& key) { if (root->_key > key) { return _EraseR(root->_left, key); } else if (root->_key < key) { return _EraseR(root->_right, key); } else { Node* del = root; if (root->_left == nullptr) { root = root->_right; } else if (root->_right == nullptr) { root = root->_left; } else//两个孩子 { //使用替换法,使用最大左孩子的替代 Node* MinRight = root->_left; while (MinRight->_right) { MinRight = MinRight->_right; } std::swap(root->_key, MinRight->_key); //把最大右孩子交换一下,继续递归一下(右边是空直接) return _EraseR(root->_left, key);//使用root->_left可以修改指向 //return _EraseR(MinRight, key);//不能写成这样,这样会导致内存泄露 } delete del; return true; } }