概念
二叉搜索树:其实本质上还是二叉树,只不过其在二叉树的基础之上增加了一些新的约束,从而变成了了一颗有规律的二叉树。
要想弄明白二叉搜索树,我们就必须牢牢把握住这三点规则:
- 每个节点都有一个作为搜索依据的关键码,每个关键码都不相同,树中没有关键码相同的两个节点;
- 左子树上所有的节点的关键码都小于根节点的关键码;
- 右子树上的所有节点的关键码都大于根节点的关键码。
二叉搜索树的左右子树也都是二叉搜索树**!!!**
二叉搜索树节点的结构
typedef int KeyType;
typedef struct BstNode
{
KeyType key;//关键码
BstNode* parent; // 双亲指针
BstNode* leftchild; //左孩子指针
BstNode* rightchild; //右孩子指针
}BstNode,*BSTree;
每个节点都包含四个域,除了关键码域,其余三个域串联起了整个树,二叉搜索树按照中序遍历的方法,遍历出来的应该是一个从小到大的数组
注意:根节点没有双亲,这一点在后面有很重要的应用
//申请节点
BstNode* BuyNode()
{
BstNode* s = (BstNode*)malloc(sizeof(BstNode));
if(s == nullptr) return nullptr;
memset(s , 0 , sizeof(BstNode));
return s;
}
构建二叉搜索树
在构建二叉树搜索树的时候,要把握好左子树大于根节点大于右子树的规则。
在插入节点的时候,要考虑几种情况:
- 如果根节点为空:根节点为空说明这是一个空树,插入的节点应该作为根节点;
- 如果插入的值小于根节点的关键码,依次用根节点的左子树与之比较;
- 如果插入的值大于根节点得关键码,依次用根节点的右子树与之比较;
- 如果插入的值在二叉树中找到了相同的关键码,插入失败,因为二叉树终不能有相同的关键码;
//插入函数
//使用引用是因为我们在局部函数中申请了空间,如果是单纯的指针,在局部函数结束的时候,申请的空间将释放,所以需要用引用,想要通过形参改变实参只能使用引用
bool Insert(BstNode*& root,KeyType val)
{
BstNode* pa = nullptr;
BstNode* p = root;
while(p != nullptr && p->key != val)
{
pa = p;
p = val < p->key ? p->leftchild : p->rightchild;
}
if(p != nullptr && p->key == val) return false;
p = BuyNode();
p->key = val;
p->parent = pa;
//此时如果pa 还是空,说明上面的操作都没进行,既树是一颗空树,直接让申请的节点作为根节点
if(pa == nullptr)
{
root = p;
}
else
{
if(pa->key<p->key)
{
pa->rightchild = p;
}
else
{
p->leftchild = p;
}
}
retrun true;
}
//创建二叉搜索树
BSTree CreateBSTree(KeyType* ar)
{
if(ar == nullptr) return nullptr;//如果数组为空,就无法构建二叉树,返回空指针
int n = sizeof(ar)/sizefo(ar[0]);
BstNode* ptr = nullptr;
for(int i = 0;i < n; ++i)
{
Insert(ptr,ar[i]);
}
return ptr;
}
相关的API函数
既然是二叉树那么二叉树对应的前中后序遍历等函数都能够被应用,这里就不过多赘述了,因为二叉搜索树的特殊性质,可以归纳出一下几个函数,
- 找到中序遍历的最头的节点:BstNode* Fast();
- 找到树在中序遍历之后每个节点的后继节点:BstNode* Next();
- 找到中序遍历的最尾的节点:BstNode* Last();
- 找到树在中序遍历之后每个节点的前驱节点:BstNode* Prev();
//二叉搜索树 的最左侧节点肯定是最小的
BstNode* Fast(BstNode* root)
{
if(root == nullptr) return nullptr;
while(root != nullptr && root->leftchild != nullptr)
{
root = root->leftchild;
}
return root;
}
BstNode* Next(BstNode* ptr)
{
if(ptr == nullptr) return ptr;
//根据二叉搜索树的特点,左孩子都比根节点大,右孩子都比根节点小,那么当右孩子不为空的时候,根节点的中序遍历的直接后继应该就是右子树中的最小的节点
if(ptr->rightchild != nullptr)
{
return Fast(ptr->rightchild);
}
else
{
BstNode* pa = ptr->parent;
while(pa != nullptr && pa->leftchild != ptr) // pa->rightchild == ptr
{
ptr = pa ;
pa = ptr->parent;
}
return pa;
}
}
BstNode* Last(BstNode* root)
{
if(root == nullptr) return nullptr;
while(root!=nullptr && root->rightchild != nullptr)
{
root= root->rightchild;
}
return child;
}
BstNode* Prev(BStNode* ptr)
{
if(ptr == nullptr) return ptr;
if(ptr->leftchild != nullptr)
{
return Last(ptr->leftchild);
}
else
{
BstNode* pa = ptr->parent;
while(pa != nullptr && pa->leftchild != nullptr)
{
ptr = pa;
pa = ptr->parent;
}
return pa;
}
}
如下图所示:
- 当我们Fast(53)的时候,可以看到53为根节点,这棵树的中序遍历之后最小的节点的就是最左子树的节点9;
- 当我们Next(53)的时候,找53的后继,因为他有右子树,根据二叉搜索树的规则,53的右子树的最小节点就是53的后继节点。
- 当我们Next(88)的时候,88没有右子树,那么他的父节点应该改就是他的后继节点。
中序遍历
通常来说,我们对二叉树继续拧中序遍历,最常用的应该是递归的方式:
void InOrder(BstNode* root)
{
if(root != nullptr)
{
InOrder(root->leftchild);
cout<<root->key;
InOrder(root->rightchild);
}
}
但是有时要求我们使用非递归的方式进行二叉树的中序遍历,此时之前铺垫的Fast和Next函数就有作用了:
void NiceInOrder(BstNode* root)
{
if(root == nullptr) return;
while(BstNode* ptr = Fast(root);ptr!=nullptr;ptr = Next(ptr))
{
cout << ptr->key << " ";
}
}
删除节点 : bool Remove(BstNode* root ,KeyType val)
我们要删除节点,首先要判断节点的类型:
- 空节点:不删除,删除失败
- 没找到相应的节点:删除失败
- 要删除的是叶子节点:直接删除,释放节点,并指向空
- 要删除的节点是单分支节点:交换
- 要删除双分支节点:变成单分支
bool Remove(BstNode*& root ,KeyType val)
{
if(root == nullptr) return false;
BstNode* ptr = root;
while(ptr != nullptr && ptr->key != val)
{
ptr = val < ptr->key ? ptr->leftchild : ptr->rightchild;
}
if(ptr == nullptr)
{
return nullptr;
}
else
{
//如果是双分支节点,找到有分支的最小节点与要删除节点调换,释放右子树最小节点
if(ptr->leftchild != nullptr && ptr->rightchild != nullptr)
{
BstNode* last = Fast(ptr->rightchild);
ptr->key = last->key;
free(last);
last = nullptr;
}
//如果不是双分支,
BstNode* pa = ptr->parent;
BstNode* child = pa->rightchild == nullptr ? pa->leftchid:pa->rightchild;
if(pa == nullptr) root = child;
else
{
if (pa->leftchild == p)
{
pa->leftchild = child;
}
else
{
pa->rightchild = child;
}
}
free(p);
return true;
}
}