性质
二叉搜索树是一个优化的二叉树,也称作二叉排序树、二叉查找树、BST等。一般在每个节点定义一个关键值Key。插入的时候按照一定的规则使之有序插入,方便搜索。它可以是一颗空树,或者这棵树有着以下的性质:
1、如果左子树不为空,那么左子树上的所有节点的值都小于根节点的值
2、如果右子树不为空,那么右子树上的所有节点的值都大于根节点的值
3、同时,左右子树也是二叉搜索树
4、这棵二叉树没有相同关键值的节点,也就是每一个节点的值多不相同
如果插入序列是:6,3,8,7,1,2,4,0,5,9,4
图示如下:
节点
二叉树用二叉链的形式实现。每个节点有一个关键值_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的节点
代码实现
代码分为递归写法和非递归写法
//非递归
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);
这里。
此时的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