本文对二叉排序树给出了自己的了解,针对插入算法,查找算法和删除算法给出代码。
这里面最难的代码实现就是删除算法,因此对删除算法进行解读。
删除结点不能简单地把该结点为根地子树都删去,只能删除该结点本身,并且还要保证删除后的结点仍然满足BST的性质
- (1)要删除的是叶子结点,可以直接删除该结点(将父节点指向该结点的指针置空)
- (2)若结点p只有左孩子没有右孩子(结点p的度为1),根据二叉排序树的特点,可以用结点p的左子树替代结点p的子树,也就是直接用其左孩子替代它(结点替代)
- (3) 若结点p只有右孩子没有左孩子(结点p的度为1),根据二叉排序树的特点,可以用结点p的右子树替代结点p的子树,也就是直接用其右孩子替代它(结点替代)
- (4)若结点p既有左孩子又有右孩子:只能间接删除, 找到其左孩子的最右下结点q,置p结点值为q结点值(值替代),再删除q结点(q结点没有右孩子,最多只有左孩子)(这里还可以找右子树的最左的结点)
根据以上分类进行代码的编写:
typedef struct BSTNode {
int data;
BSTNode* left;
BSTNode* right;
BSTNode() {
data = 0;
left = nullptr;
right = nullptr;
}
BSTNode(int data) {
this->data = data;
left = NULL;
right = NULL;
}
}Node;
class BSTree {
public:
BSTNode* roof;
BSTree() {
roof = NULL;
}
void InsertBst(int data) {
this->roof = Insert(this->roof, data);
}
BSTNode* Insert(BSTNode* p, int data) {
if (p == NULL) {
p = new BSTNode(data);
}
else if (p->data > data) {
p->left = Insert(p->left, data);
}
else if (p->data < data) {
p->right = Insert(p->right, data);
}
else {
p->data = data;
}
return p;
}
void insert(int val) {//往树中插入结点
Node* node = new Node;
node->data = val;
if (this->roof == NULL) {//空树
this->roof = node;
}
else {
Node* temp = this->roof;
while (temp != NULL) {
if (val < temp->data) {
if (temp->left == NULL) {
temp->left = node;
return;
}
else {
temp = temp->left;
}
}
else {
if (temp->right == NULL) {
temp->right = node;
return;
}
else {
temp = temp->right;
}
}
}
}
}
void creat(std::vector<int>arr) {
for (int i = 0; i < arr.size(); i++) {
InsertBst(arr[i]);
//insert(arr[i]);
}
}
BSTNode* find(BSTNode* b, int key) {
if (b == NULL) {
return NULL;
}
if (b->data == key) {
return b;
}
else if (b->data < key) {
return find(b->right, key);
}
else {
return find(b->left, key);
}
//非递归形式:
//Node* temp = this->roof;
//while (temp != NULL) {
// if (temp->data == key) {
// return temp;
// }
// else if(temp->data<key) {//在右子树里面寻
// if (temp->right == NULL) {
// return NULL;
// }
// else {
// temp = temp->right;
// }
// }
// else {
// if (temp->left == NULL) {
// return NULL;
// }
// else {
// temp = temp->left;
// }
// }
//}
}
Node* findFather(int data) {
Node* temp = this->roof;
if (temp->data == data) {
return NULL;//根节点没有父节点
}
while (temp != NULL) {
if (data > temp->data) {//右子树
if (temp->right != NULL) {
if (temp->right->data == data) {
return temp;
}
else {
temp = temp->right;
}
}
else {
return NULL;
}
}
else {//左子树
if (temp->left == NULL) {
return NULL;
}
else {
if (temp->left->data == data) {
return temp;
}
else {
temp = temp->left;
}
}
}
}
}
Node* getParant(Node* node, Node* find) {
if (node == NULL || node->left == find || node->right == find) {
return node;
}
if (node->data > find->data) {
Node* left = getParant(node->left, find);
if (left != NULL) {
return left;
}
}
else {
Node* right = getParant(node->right, find);
if (right) {
return right;
}
}
return NULL;
}
void Delete(Node* node) {
Node* temp = getParant(this->roof, node);
if (node->left == NULL && node->right == NULL) {
if (temp->data > node->data) {//左子树
temp->left = NULL;
}
else {
temp->right = NULL;
}
}
else if (node->left && !(node->right)) {//左子树不为空,但是右子树为空
if (temp->data > node->data) {//父节点的值比子结点大
temp->left = node->left;
}
else {
temp->right = node->left;
}
}
else if (!(node->left) && node->right) {
if (temp->data > node->data) {
temp->left = node->right;
}
else
{
temp->right = node->right;
}
}
else {//左右子树均不为空,找左孩子的最右结点
Node* t = node->left;//临时结点
while (t->right != NULL) {
t = t->right;
}
int m = t->data;
if (t->left) {
if (getParant(node, t) != node) {
getParant(node, t)->right = t->left;
}
else {
node->left = t->left;
}
}
else {
if (getParant(node, t) != node) {
getParant(node, t)->right = NULL;
}
else {
node->left = NULL;
}
}
node->data = m;
}
node = NULL;
}
};
删除算法的实现个人认为必须在二叉排序树的基础上操作才可以真正的实现删除操作。因此本人就使用找父节点的函数来实现在二叉排序树的基础上进行删除操作。而最后一种情况其实还可以细分。有直接前继和没有直接前继,还可以继续分为前继的左子树为空或者存在左子树。
这是什么意思呢,来看我在csdn上看到的一个删除的函数,这是我在看这个博主的文章的时候发现的,咋一看好像很简单,我当时也这样认为,但是实操之后发现不可行。原因在于c++的指针操作。这个作者利用一个中间结点q来删除结点p,但问题是q这个结点本来就不在二叉排序树上,计数我有q=p这个操作,只是多出来一个指向和p相同的指针罢了,其实并没有对二叉排序树进行删除操作(即使删除了也是编译器优化的结果,这时候你再去遍历一下这个二叉树就会发现问题了)
bool Delete(BSTNode *p){
//在二叉排序树中删除结点p, 并重新连接它的左右子树
BSTNode *q, *s;
//1.p为叶子结点
if(p->left==NULL && p->right==NULL){
p = NULL;
}
//2.1 p左子树为空, 重接右子树
else if(p->left == NULL){
q = p;
p = p->right;
free(q);
}
//2.2 p右子树为空, 重接左子树
else if(p->right == NULL){
q = p;
p = p->left;
free(q);
}
//3. p左右子树均不为空
else{
q = p;
s = p->right; //找到p的右子树的最左端(中序直接后继)
while(s->left != NULL){
q = s;
s = s->left;
}
p->data = s->data;
if(q != p) //判断是否执行上述while循环
q->left = s->right; //执行上述while循环,重接*q的左子树
else
q->right = s->right; //未执行上述while循环,重接*q的右子树,对于这个情况,可以参考代码后给出的示例图
free(s);
}
return true;
}
————————————————
版权声明:本文为CSDN博主「薛定谔的猫ovo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44162361/article/details/119112155
所以要对二叉排序树本身进行操作,就要利用父节点这个关键信息.