二叉搜索树插入和删除(递归和非递归实现)

个人博客传送门

性质

二叉搜索树是一个优化的二叉树,也称作二叉排序树、二叉查找树、BST等。一般在每个节点定义一个关键值Key。插入的时候按照一定的规则使之有序插入,方便搜索。它可以是一颗空树,或者这棵树有着以下的性质:
1、如果左子树不为空,那么左子树上的所有节点的值都小于根节点的值
2、如果右子树不为空,那么右子树上的所有节点的值都大于根节点的值
3、同时,左右子树也是二叉搜索树
4、这棵二叉树没有相同关键值的节点,也就是每一个节点的值多不相同
如果插入序列是:6,3,8,7,1,2,4,0,5,9,4
图示如下:
BSTree

节点

二叉树用二叉链的形式实现。每个节点有一个关键值_key,指向左子树的指针_left,指向右子树的指针_right。

template <class K>
struct BStreeNode{
   
    K _key;
    BStreeNode<K>* _left;
    BStreeNode<K>* _right;

    BStreeNode(const K& key)
        :_key(key)
        ,_left(NULL)
        ,_right(NULL)
    {}
};

插入

思路

分为三个步骤,查找插入位置,利用key创建节点,跟二叉树连接起来
1、插入函数接收一个关键值key,用这个key跟当前指针指向的节点(cur)的关键值(cur->_key)比较。
2、如果key大,cur向右子树走;如果key小,cur向左子树走;如果相等,那么就不需要插入。这里利用一个循环就可以实现。一直到cur指向空节点,那么这个地方就是需要插入的位置。
4、但是为了将新增节点和二叉树连接起来,还需要一个指针指向上一个节点(parent),此时要分清楚链入parent节点的左子树上还是右子树上。

图示

插入key为4的节点
1
2
3

代码实现

代码分为递归写法和非递归写法

//非递归
bool Insert(const K& key){
    //特殊处理插入空树的情况
    if(_root == NULL){
        _root = new Node(key);
        return true;
    }
    Node* cur = _root;
    Node* parent = NULL;
    //查找插入位置
    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);
    //插入到parent的右子树上
    if(parent->_key < key){
        parent->_right = cur;
    }
    else{
        parent->_left = cur;
    }
    return true;
}

//递归写法
bool InsertR(const K& key){
    return _InsertR(_root, key);
}
//root使用的是引用,解决了连接的问题
bool _InsertR(Node*& root, const K& key){
    if(root == NULL){
        root = new Node(key);
    }
    else if(root->_key < key){
        _InsertR(root->_right, key);
    }
    else if(root->_key > key){
        _InsertR(root->_left, key);
    }
    else
        return false;
    return true;
}

这里解释两个地方:1、递归为什么要调用一个内置函数,不直接递归。2、递归写法为什么不需要链接的过程。
1、由于递归函数需要多次调用本身,考虑如果不调用内置函数,为了实现递归左右子树,需要传入参数如下:

bool InsertR(Node* root, const K& key){}

但是很尴尬的是,我们没有办法将根节点_root的左子树或者右子树进行调用。因为_root是私有的,我们在类的外面是没有办法直接调用的。
2、递归写法不是不需要链接的过程,而是连接的过程在使用了引用root这个语法之后,隐式的完成了。一个例子:我们现在有一个关键值为6的节点,我们要插入关键值为3的节点。根据代码,代码会走到_InsertR(root->_left, key);这里。
InsertR
此时的root有两层含义:第一层,root是当前节点的位置,指向了NULL;第二层,root是上一层函数root->_left指向的位置。之所以会有这样的联系,是因为root参数是引用的原因,当前的root是上一层函数的root->_left的别名。这样我们就不需要考虑连接的问题了,只要将新增节点直接交给当前函数的root就已经和二叉树连接在一起了。

查找

查找的思路十分简单,可以认为是插入的弱化版本。找到返回当前节点的指针,未找到返回空指针。

代码实现

查找函数也有非递归和递归两个版本。

//Find
Node* Find(const K& key){
    if(_root){
        Node* cur = _root;
        while(cur){
            if(cur->_key < key){
                cur = cur->_right;
            }
            else if(cur->_key > key){
                cur = cur->_left;
            }
            else{
                return cur;
            }
        }
    }
    return NULL;
}
//FindR
Node* FindR(const K& key){
    return _FindR(_root, key);
}
Node* _FindR(Node* root, const K
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值