实验内容:二叉排序树。

实验内容:二叉排序树

任意给定一组数据,设计一个算法,建立一棵二叉排序树,对它进行查找、插入、删除等操作。

实验说明:

二叉排序树存储结构如下:

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指向要删除的结点。删除过程分为以下几种情况:

  1. 若p结点是叶子结点,直接删除结点。                
  2. 若p结点只有左子树而无右子树,根据二叉排序树的特点,可以直接用其左孩子替代结点p。              
  3. 若p结点只有右子树而无左子树。根据二叉排序树的特点,可以直接用其右孩子替代结点p。                
  4. 若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的值,其原理是用中序后继替代被删结点。

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值