实验内容:二叉排序树。
任意给定一组数据,设计一个算法,建立一棵二叉排序树,对它进行查找、插入、删除等操作。
实验说明:
二叉排序树存储结构如下:
typedef struct BiTNode { // 结点结构
struct BiTNode *lchild, *rchild;
// 左右孩子指针
} BiTNode, *BiTree;
二叉排序树插入算法伪代码如下:
1. 若root是空树,则将结点s作为根结点插入;否则
2. 若s->data<root->data,则把结点s插入到root的左子树中;否则
3. 把结点s插入到root的右子树中。
二叉排序树中删除一个结点f的左孩子结点p算法伪代码如下:
1. 若结点p是叶子,则直接删除结点p;
2. 若结点p只有左子树,则只需重接p的左子树;
若结点p只有右子树,则只需重接p的右子树;
3. 若结点p的左右子树均不空,则
3.1 查找结点p的右子树上的最左下结点s以及结点s的双亲结点par;
3.2 将结点s数据域替换到被删结点p的数据域;
3.3 若结点p的右孩子无左子树,则将s的右子树接到par的右子树上;
否则,将s的右子树接到结点par的左子树上;
3.4 删除结点s;
一、二叉排序树的声明、插入
插入:在二叉排序树bt中插入一个关键字为k的结点,要保证插入后仍满足BST性质。其插入过程是:若bt为空,则创建一个key域为k的结点bt,将它作为根结点;否则将k和根结点的关键字比较,若k<bt->key,则将k插入bt结点的左子树中,若k>bt->key,则将k插入bt结点的右子树中,其他情况是k=bt->key,说明树中已有此关键字k,无须插入,最后返回插入后的二叉排序树的根结点bt。时间复杂度为O(1)。
#include<iostream>
using namespace std;
#define MaxSize 100
typedef int KeyType;//关键字类型
typedef char InfoType;
typedef struct BiTNode {
KeyType key;//关键字项
InfoType data;//其他类型,在实验中并没有用到
struct BiTNode* lchild, *rchild;//左右孩子指针
}BSTNode;
BSTNode* InsertBST(BSTNode* root, KeyType s) {//插入关键字为k的结点
if (root == NULL) {//原数为空时,新建结点
root = new BSTNode;
root->key = s;
root->lchild = root->rchild = NULL;
}
else if (s < root->key)//插入左子树
root->lchild = InsertBST(root->lchild, s);
else if (s > root->key)//插入右子树
root->rchild = InsertBST(root->rchild, s);
return root;
}
二、创建
创建一棵二叉排序树是从一个空树开始,每插入一个关键字,就调用一次插入算法将它插入当前已生成的二叉排序树中。时间复杂度为O(n),n为二叉树的结点个数
BSTNode* CreateBST(KeyType a[], int n) {//创建二叉排序树
BSTNode* bt = nullptr;
int i = 0;
while (i < n) {
bt = InsertBST(bt, a[i]);//将关键字a[i]插入二叉排序数中
i++;
}
return bt;
}
三、查找
因为二叉排序树可看作有序的,所以在二叉排序树上进行查找和折半查找类似,也是一个逐步缩小查找范围的过程。递归查找算法SearchBST如下(在二叉排序树bt上查找关键字为k的结点,成功时返回该结点的地址,否则返回NULL)。
一颗含有n个结点的二叉排序树的查找算法时间复杂度介于O(log2n)和O(n)之间。
BSTNode* f;//全局结点f,用于指向p的双亲结点
BSTNode* SearchBST(BSTNode* bt, KeyType k) {//非递归查找
BSTNode* p = bt;
cout << p->key << " ";
while (p != NULL) {
if (p->key == k)
break;
f = p;
if (k < p->key) {
p = p->lchild;
cout << p->key << " ";
}
else {
p = p->rchild;
cout << p->key << " ";
}
}
return p;//返回查找到的结点
}
四、删除
从二叉排序树中删除一个结点时,不能直接把以该结点为根的子树都删除,只能删除该结点本身,并且还要保证删除后所得的二叉树仍然满足BST性质。也就是说,在二叉排序树中删除一个结点就相当于删除有序序列中的一个结点。删除操作必须首先进行查找,假设在查找结束时p指向要删除的结点。删除过程分为以下几种情况:
- 若p结点是叶子结点,直接删除结点。
- 若p结点只有左子树而无右子树,根据二叉排序树的特点,可以直接用其左孩子替代结点p。
- 若p结点只有右子树而无左子树。根据二叉排序树的特点,可以直接用其右孩子替代结点p。
- 若p结点同时存在左、右子树。根据二叉排序树的特点,可以从其左子树中选择关键字最大的结点q,用结点q的值替代结点p的值,并删除结点q,其原理是用中序前驱替代被删结点。
时间复杂度为O(1)。
void DeleteBST(BSTNode*&p) {//删除查找到的结点p
BSTNode* q;
if (p->lchild == NULL && p->rchild == NULL) {//p为叶子结点时
if (f->lchild == p)
f->lchild = NULL;
else
f->rchild = NULL;
free(p);
}
else if (p->lchild == NULL) {//p只有右孩子时
if (f->lchild == p)
f->lchild = p->rchild;
else
f->rchild = p->rchild;
free(p);
}
else if (p->rchild == NULL) {//p只有左孩子时
if (f->lchild == p)
f->lchild = p->lchild;
else
f->rchild = p->lchild;
free(p);
}
else {//p左右孩子都有时
BSTNode *s = p->rchild;//指向p的右孩子结点
BSTNode *par = p;//指向s的双亲结点
while (s->lchild != NULL) {
//查找结点p的右子树上的最左下结点s以及结点s的双亲结点par
par = s;
s = s->lchild;
}
p->key = s->key;//将结点s数据域替换到被删结点p的数据域
if (p->rchild->lchild == NULL) {
//若结点p的右孩子无左子树,则将s的右子树接到par的右子树上
par->rchild = s->rchild;
}
else {//将s的右子树接到结点par的左子树上
par->rchild = s->rchild;
}
free(s);
}
}
五、输出
与二叉树的输出一样。
void DispBST(BSTNode* bt) {//以括号表示法输出二叉排序树
if (bt != NULL) {
cout<< bt->key;
if (bt->lchild != NULL || bt->rchild != NULL) {
cout<<"(";
DispBST(bt->lchild);
if (bt->rchild != NULL)
cout << ",";
DispBST(bt->rchild);
cout<<")";
}
}
}
六、主函数
int main() {
BSTNode* bt,*p;
KeyType k ;
cout << "输入想要查找删除的结点关键字的值(根结点4除外):" ;
cin >> k;
int path[MaxSize];
int a[] = { 4,9,0,1,8,6,3,5,2,7 }, n = 10;
cout << "(1)创建一颗BST树:";
bt=CreateBST(a, n);
DispBST(bt);
cout << endl;
cout << "(2)查找" << k << "关键字顺序:";
p=SearchBST(bt, k);
cout << endl;
cout << "(3)删除" << k << "关键字:";
DeleteBST(p);
DispBST(bt);
cout << endl;
return 0;
}
七、执行结果
总结
插入:由于二叉排序树中的每个结点恰好存放一个关键字,所以插入关键字k就是插入一个结点。从插入算法InsertBST看到,每个结点插入时都需要从根结点开始比较,若比根结点的key值小,当前指针移到左子树,否则当前指针移到右子树,如此这样,直到当前指针为空,再创建一个存放关键字k的结点并链接起来。因此可知,任何结点插入二叉排序树时都是作为叶子结点插入的。
创建:一个关键字集合有多个关键字序列,不同的关键字序列采用上述创建算法得到的二叉排序树可能不同。
查找:和折半查找的判定树类似,将二叉排序树中的结点作为内部结点,可以添加相应的外部结点,具有n个内部结点的二叉排序树,其外部结点的个数为n+1。显然,在二叉排序树中进行查找,若查找成功,则是走了一条从根结点到某个内部结点的路径;若查找不成功,则是走了一条从根结点到某个外部结点的路径。因此与折半查找类似,其关键字比较的次数不超过树的高度。
删除:除上面意外,还可以从其右子树中选择关键字最小的结点g,用结点q的值替代结点p的值,其原理是用中序后继替代被删结点。