二叉搜索树(Binary Search Tree)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树树。
节点设置:
typedef int DataType;
typedef struct BSTreeNode
{
struct BSTreeNode* _left;
struct BSTreeNode* _right;
DataType _data;
}BSTreeNode;
1. 插入节点
我们知道二叉搜索树的性质是:根节点左边的节点值均小于根节点;根节点右边的值都大于根节点,并且所有子树也满足这个性质。那么当我们插入一个新节点时也必须保证这个性质成立。
1.非递归插入
如果是一棵空树,那就直接根节点指针指向这个插入节点。
不是空树,就要找到这个节点的正确插入位置。
拿待插入的节点值和根节点比较,比根节点小,就往左子树寻找插入位置;比根节点大,就往右子树寻找插入位置。
找到确定位置后比较待插入节点和它父亲节点的大小,比之小,就插入到左孩子位置;比之大,就插入到右孩子位置。
BSTreeNode* BuyBSTreeNode(DataType x) //生成新节点
{
BSTreeNode* NewNode = (BSTreeNode*)malloc(sizeof(BSTreeNode));
assert(NewNode != NULL);
NewNode->_data = x;
NewNode->_left = NULL;
NewNode->_right = NULL;
return NewNode;
}
/* 成功返回0,失败返回-1 */
int BSTreeInsert(BSTreeNode** ppTree, DataType x) //插入
{
assert(ppTree != NULL);
if (NULL == *ppTree)
{
*ppTree = BuyBSTreeNode(x);
return 0;
}
BSTreeNode* parent = NULL;
BSTreeNode* cur = *ppTree;
while (cur != NULL)
{
if (cur->_data < x) //插入的数大于根节点,往右子树找位置
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > x)//插入的数小于根节点,往左子树找位置
{
parent = cur;
cur = cur->_left;
}
else //相等就不能插入
{
return -1;
}
}
if (x > parent->_data) //根据大小选择插入的左节点还是右节点
{
parent->_right = BuyBSTreeNode(x);
}
else
{
parent->_left = BuyBSTreeNode(x);
}
return 0;
}
2. 递归插入
上面介绍的是非递归实现方法,代码比较冗余,接下来介绍递归实现插入操作。
int BSTreeInsertR(BSTreeNode** ppTree, DataType x) //递归插入
{
if (NULL == *ppTree)
{
// *ppTree代表一个节点的左或右子树的地址
*ppTree = BuyBSTreeNode(x);
return 0;
}
/*这里传参传的是当前节点的左右子树的地址*/
if ((*ppTree)->_data > x)
{
return BSTreeInsertR(&(*ppTree)->_left, x);
}
else if ((*ppTree)->_data < x)
{
return BSTreeInsertR(&(*ppTree)->_right, x);
}
else
{
return -1; //插入失败
}
}
我们发现递归实现的代码量比非递归的少了很多,这段代码只有插入节点,但是二叉树的节点之间不是要链接起来吗?这里面没有链接代码啊?
return BSTreeInsertR(&(*ppTree)->_left, x);
return BSTreeInsertR(&(*ppTree)->_right, x);
注意这两句代码,递归时是把根节点的左节点指针或右节点指针的地址传过去了。在下层递归中,解引用根节点得到的就是上层递归的根节点的左节点指针或右节点指针。
此时让本层根节点指向插入节点,实际上就是上层根节点的左节点指针或右节点指针指向了待插入节点。这样待插入节点就和树中其他节点链接起来了。
2. 查找节点
BSTreeNode* BSTreeFind(BSTreeNode* pTree, DataType x) //查找
{
BSTreeNode* cur = pTree;
while (cur != NULL)
{
if (cur->_data > x)
{
cur = cur->_left;
}
else if (cur->_data < x)
{
cur = cur->_right;
}
else
{
return cur;
}
}
return NULL;
}
3. 删除节点
1. 非递归删除
删除节点的情况比较复杂,因为我们删了一个节点后也要保证搜索树的性质不被改变。为了更好处理,我们把删除分为三种情况:
1. 被删除节点的左子树为空;
2. 被删除节点的右子树为空;
3. 被删除节点的左右子树均不为空。
情况1和情况2还是挺好处理的,找到待删除节点和他的父亲节点,如果是情况1就让它的父亲节点指向它的右子树;如果是情况2就让它的父亲节点指向它的左子树;然后free了这个节点。
情况3是比较麻烦的,应为要删除的节点有两个孩子,处理地不好就会破坏二叉树的结构,这里给出两种解决方案。
方案一:找到待删除的节点cur的右子树的最左节点sub,把sub节点的值赋给cur节点,然后删除sub节点。
方案二:找到待删除的节点cur的左子树的最右节点sub,把sub节点的值赋给cur节点,然后删除sub节点。
要注意的是我们也要对sub进行分析,根据sub的情况把删除分为两种:
1. sub就是cur的左孩子或右孩子,直接让cur->_right = sub->_right;
如下图所示(红色节点cur, 蓝色节点sub):
- sub不是cur的孩子,让sub的父亲节点
parent->_left = sub->_right;
如下图所示(红色节点cur, 蓝色节点sub):
int BSTreeRemove(BSTreeNode** ppTree, DataType x) //删除
{
assert(ppTree != NULL);
BSTreeNode* parent = NULL;
BSTreeNode* cur = *ppTree;
while (cur != NULL)
{
if (cur->_data < x)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > x)
{
parent = cur;
cur = cur->_left;
}
else //找到了
{
BSTreeNode* del = NULL; //被删除的节点
if (cur->_left == NULL)// 1.左孩子为空
{
if (parent == NULL) //1.1被删除节点无父节点,表示要删除的是根节点
{
*ppTree = cur->_right;
del = cur;
}
else //1.2 被删除的节点有父节点,parent
{
if (parent->_left == cur) //1.2.1被删除节点是它父节点的左孩子
{
parent->_left = cur->_right;
}
else//1.2.2被删除节点是它父节点的右孩子
{
parent->_right = cur->_right;
}
del = cur;
}
}
else if (cur->_right == NULL)// 2.右孩子为空
{
if (parent == NULL) //2.1被删除节点无父节点,表示要删除的是根节点
{
*ppTree = cur->_left;
del = cur;
}
else//2.2被删除节点有父节点,parent
{
if (parent->_left == cur)//2.2.1被删除节点是它父节点的左孩子
{
parent->_left = cur->_left;
}
else //2.2.2被删除节点是它父节点的左孩子
{
parent->_right = cur->_left;
}
del = cur;
}
}
else // 3.左右孩子都不为空
{
//方法一:找到要删除的节点的右子树的最左节点,把这个节点的值赋给要删除的节点,然后删除这个节点
parent = cur;
BSTreeNode* sub = cur->_right;
while (sub->_left != NULL) //找到cur右子树的最左节点
{
parent = sub;
sub = sub->_left;
}
del = sub;
cur->_data = sub->_data;
if (parent->_left == sub) //3.1找到的sub不是cur的左孩子,此时sub是parent的左孩子
{
parent->_left = sub->_right;
}
else //3.2找到的sub就是cur的右孩子(sub可能有右子树,但左子树必定为空)
{
parent->_right = sub->_right;
}
//方法二:找左子树的最右节点
//BSTreeNode* parent = cur;
//BSTreeNode* sub = cur->_left;
//while (sub->_right)
//{
// parent = sub;
// sub = sub->_right;
//}
//del = sub;
//cur->_data = sub->_data;
//if (parent->_left == sub)
//{
// parent->_left = sub->_left;
//}
//else
//{
// parent->_right = sub->_left;
//}
}
free(del);
return 0;
}
}
}
2. 递归删除
int BSTreeRemoveR(BSTreeNode** ppTree, DataType x)
{
if ((*ppTree) == NULL)
{
return -1;
}
if ((*ppTree)->_data > x)
{
return BSTreeRemoveR(&(*ppTree)->_left, x);
}
else if ((*ppTree)->_data < x)
{
return BSTreeRemoveR(&(*ppTree)->_right, x);
}
else
{
/*找到要删除的节点*/
BSTreeNode* del = NULL;
if ((*ppTree)->_left == NULL)
{
/*左子树为空*/
del = *ppTree;
/* *ppTree 是要删除的节点的地址,也是他父亲节点的左孩子的地址*/
*ppTree = (*ppTree)->_right;
}
else if ((*ppTree)->_right == NULL)
{
/*右子树为空*/
del = *ppTree;
*ppTree = (*ppTree)->_left;
}
else
{
/*左右子树都不为空*/
BSTreeNode* cur = *ppTree;
BSTreeNode* parent = *ppTree;
BSTreeNode* sub = cur->_right;
while (sub->_left != NULL)
{
parent = sub;
sub = sub->_left;
}
cur->_data = sub->_data;
del = sub;
if (parent->_left == sub)
{
parent->_left = sub->_right;
}
else
{
parent->_right = sub->_right;
}
}
free(del);
return 0;
}
}
4. 销毁二叉树
void BSTreeDestoryR(BSTreeNode** ppTree)
{
if (NULL == *ppTree)
return;
BSTreeDestoryR(&((*ppTree)->_left));
BSTreeDestoryR(&((*ppTree)->_right));
free(*ppTree);
*ppTree = NULL;
}