CHAPTER_9 提高篇(3)——数据结构(2)
9.4.1二叉查找树的定义
二叉查找树(BST)是一种特殊的二叉树,又称为二叉排序树、二叉搜索树。二叉查找树的定义如下:
(1)要么二叉查找树是一颗空树。
(2)要么二叉查找树由根节点、左子树、右子树构成。其中左子树和右子树都是二叉查找树,且 左子树上所有节点的数据域均小于等于根节点的数据域,右子树上所有节点的数据域均大于 根节点的数据域。
上图给出了一个BST的例子。从图中可以看出,二叉查找树实际上是一棵数据域有序的二叉树。如果对BST进行中序遍历,就可以得到一个递增的序列。
9.4.2二叉查找树的基本操作
二叉查找树的基本操作有查找、插入、建树、删除。这些操作过程是基于一般二叉树改造而来的。
查找操作
二叉查找树的性质使得二叉树可以高效的查找元素,查找过程将会是从根节点到查找节点的一条路径。最坏复杂度为O(h),h为树的高度。查找数据x的基本思路如下:
(1)如果当前根节点为空,查找失败,返回。
(2)如果x等于当前根节点的数据域,说明查找成功,访问之。
(3)如果x小于当前根节点的数据域,说明x可能在左子树,向左子树递归。
(4)如果x大于当前根节点的数据域,说明x可能在右子树,向右子树递归。
代码如下:
void search(node* root,int x) {
if(root==NULL) {
cout<<"failed"<<endl;
return;
}
if(x==root->data) {
cout<<root->data;
}
else if(x<root->data) {
search(root->lchild,x);
}
else {
search(root->rchild,x);
}
}
插入操作
与二叉树的插入类似,二叉查找树的插入同样是个查找的过程。当某个值在BST中查找成功,说明节点已经存在,如果查找失败,说明查找失败的地方一定是节点需要插入的地方。因此插入代码只需在查找的基础上稍作修改。
代码如下:
void insert(node* &root,int x) { //注意root参数一定要引用
if(root==NULL) { //查找失败,新建节点进行插入
root=newNode(x);
return;
}
if(x==root->data) { //节点已存在
return;
}
else if(x<root->data) { //插入位置在左子树
insert(root->lchild,x);
}
else(x>root->data) { //插入位置在右子树
insert(root->rchild,x);
}
}
二叉查找树的建立
建树的过程就是节点一个个的插入的过程,和一般二叉树的建立是一样的。
代码如下:
void create(int data[],int n) {
node* root=NULL;
for(int i=0;i<n;i++) {
insert(root,data[i]);
}
return;
}
二叉查找树的删除
二叉查找树的删除操作稍显复杂。如下图,我们要删除节点3,我们必须保证删除后仍是一棵二叉查找树。
在讲解删除操作之前,我们先要理解节点的前驱和后继概念。把二叉查找树中比节点权值小的最大节点称作该节点的前驱,而比节点权值小的最大节点称作该节点的后继。例如上图中,节点3的前驱为2,后继为4。可以看出,节点的前驱是该节点的左子树中的最右节点,而后继是该节点的右子树中的最左节点。
因从我们可以用下面两个函数来寻找某BST的最大节点和最小节点,依次来辅助寻找某节点的前驱和后继:
//寻找某BST中的最大权值节点
node* finMax(node* root) {
while(root->rchild) {
root=root->rchild; //不断向右,找到最右的节点
}
return root;
}
//寻找某BST中的最小权值节点
node* finMin(node* root) {
while(root->lchild) {
root=root->lchild; //不断向左,找到最左的节点
}
}
接下来可以我们来看如何在BST中删除一个节点。假设我们要在BST中删除节点N。我们可以用节点N的后继P来替换节点N,于是就把问题转换为在N的右子树中删除P,就可以递归下去了,直到递归到一个叶子节点,就可以把它删除了。同样地,我们也可以用后继节点来代替N。例如上图中,我们用后继4来代替节点3。
删除节点x操作的基本思路如下:
(1)如果当前节点root为空,说明不存在权值为x的节点,返回。
(2)如果当前节点权值为x,说明找到元素,此时进入删除处理。
(a)如果当前节点root不存在左右孩子,说明是叶子节点,直接删除。
(b)如果当前节点存在左孩子,则进入左子树中寻找前驱pre,然后让pre的数据覆盖root, 接着在左子树中删除节点pre。
(c)如果当前节点root存在右孩子,则进入右子树寻找后继next,然后让next的数据覆盖 root,接着在右子树中删除next。
(3)如果当前节点root的权值大于给定权值x,则在左子树中递归删除权值为x的节点。
(4)如果当前节点root的权值小于给定权值x,则在右子树中递归删除权值为x的节点。
代码如下:
void deleteNode(node* &root,int x) {
if(root==NULL)
return;
if(root->data==x) {
if(root->lchild==NULL&&root->rchild==NULL) {
delete root;
root==NULL;
}
else if(root->rchild) {
node *next=findMin(root->rchild);
root->data=next->data;
deleteNode(root->rchild,next->data);
}
else(root->lchild) {
node *pre=findMax(root->lchild);
root->data=pre->data;
deleteNode(root->lchild,pre->data);
}
}
else if(root->data>x) {
deleteNode(root->lchild,x);
}
else {
deleteNode(root->rchild,x);
}
}