数据结构——二叉排序树

       二叉排序树,又称为二叉查找树。它具有以下性质:1.若它的左子树不空,则左子树上所有结点的值均小于它根结点的值。2.若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。3.它的左、右子树也分别为二叉排序树。

       也就是按照数组从左到右的顺序,第一个为根节点,然后小的为左子树,大的为右子树,然后按照二叉树的性质以此类推。

(1)二叉排序树的结点结构

typedef struct BiTNode {
	int data;
	struct BiTNode* lchild, * rchild;//左右孩子指针
}BiTNode,*BiTree;

(2)二叉排序树的查找

int SearchBST(BiTree T, int key, BiTree f, BiTree& p) {
	if (!T) {
		p = f;
		return 0;
	}
	else if (key == T->data) { //查找成功
		p = T;
		return 1;
	}
	else if (key < T->data) {
		return SearchBST(T->lchild, key, T, p);//遍历左子树
	}
	else {
		return SearchBST(T->rchild, key, T, p);//遍历右子树
	}
}

1.参数的解释:

       参数T是一个二叉链表(*BiTree),key代表要查找的关键字,二叉树f一直指向T的双亲(当T为根节点时,f的初值就为NULL)。最后的参数p是为查找成功后得到的结点位置,如果查找失败,则p指向查找路径上访问的最后一个结点

调用函数的格式:SearchBST(T,93,NULL,p)

2.查找不成功的情况:

	if (!T) {
		p = f;
		return 0;
	}

       也就是T查找到叶子结点的下一层(T为NULL),说明查找失败,那么要把最后访问的结点赋给p,也就是T的双亲结点f,返回0。 

3.查找成功的情况:

	else if (key == T->data) {
		p = T;
		return 1;
	}

当key==T->data的时候,说明查找成功,这时赋给p对应的结点T,返回1。

4.关键值小于当前结点值的情况:

	else if (key < T->data) {
		return SearchBST(T->lchild, key, T, p);//遍历左子树
	}

            说明找大了,要向数值减小的方向进行,也就是对结点的左子树进行递归调用——SearchBST(T->lchild,key,T,p)。其中T修改为自己的左子树,关键值不便,f修改为T->lchild的双亲结点,也就是T,p不变。

5.关键值大于当前结点值的情况:

	else {
		return SearchBST(T->rchild, key, T, p);//遍历右子树
	}

与前一种情况刚好相反。找小了,要对右子树进行递归。

(3)二叉排序树的插入

 把关键字放到树中合适的位置

int InsertBST(BiTree& T, int key) {
	BiTree p, s;
	if (!SearchBST(T, key, NULL, p)) { //根据查找函数,p指向最后经过的叶子结点
		s = new BiTNode;
		s->data = key;
		s->lchild = s->rchild = NULL; //初始化新结点
		if (!p) { //说明这是个空树
			T = s;
		}
		else if (key < p->data) //数值小于p结点,放左边
			p->lchild = s;
		else
			p->rchild = s;  //数值大于p结点,放右边
		return 1;
	}
	else
		return 0;
}

整体思路:

       1.首先就是调用SearchBST函数,查看整个排序二叉树中是否有key值,如果没有则插入,如果有,则返回0。

        2.在没有key值的情况下,生成一个新结点s,把s的左右孩子指针都置空,然后把key值赋给s的data域。

		s = new BiTNode;
		s->data = key;
		s->lchild = s->rchild = NULL;

        3.判断是否为空树——如果调用完SearchBST函数后,p为空,说明最后一个访问的结点为空,也就是整个排序二叉树为空树。这时让s成为根节点即可。

		if (!p) {
			T = s;
		}

          4.根据key与p->data的关系进行插入。因为s结点一定是p结点的孩子——排序二叉树的定义(插入一定要插在已经有的结点后面,而不能插入到中间)。因此,如果key小于p->data,那么s成为p的左结点,如果key大于p->data,那么s成为p的右结点。

		else if (key < p->data)
			p->lchild = s;
		else
			p->rchild = s;
		return 1;

示例:

1.生成一棵排序二叉树:

	BiTree t=NULL;
	int n;
	cout << "输入元素个数:";
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		int h;
		cin >> h;
		InsertBST(t, h);
	}

2.查找示例:

int main()
{
	BiTree t=NULL;
	int n;
	cout << "输入元素个数:";
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		int h;
		cin >> h;
		InsertBST(t, h);
	}
	BiTree p=NULL,f=NULL;
	int key;
	cout << "输入查找的元素:";
	cin >> key;
	int j = SearchBST(t, key, f, p);
	if (j == 1) {
		cout << "查找成功!查找的元素为:" << p->data;
	}
	else {//j==0
		cout << "查找失败!";
	}
	return 0;
}

        注意p和f的初始化:BiTree p=NULL,f=NULL  p可以初始化也可以不初始化,因为SearchBST函数全程没有用p的值,p一直是被赋的。但是f一定要初始化为NULL,因为它的值会赋给p。 

(4)二叉排序树结点的删除

        二叉排序树的删除共有三种情况:1.删除叶子结点。2.仅有左或右子树的结点。3.左右子树都有的结点。因此代码需要对这三种情况进行分类讨论。因此二叉排序树结点的删除不是简简单单的delete,需要额外定义一个函数——DeleteTree

int DeleteTree(BiTree& p) {
	BiTree q, s;
	if (p->rchild == NULL) {//结点的右子树为空
		q = p; //保存结点
		p = p->lchild;  //修改p的双亲结点指向p的左子树(因为双亲结点的孩子是p指向的结点)
		delete q;
	}
	else if (p->lchild == NULL) {
		q = p;
		p = p->rchild;
		delete q;
	}
	else { //左右子树都不为空
		q = p; //保存结点
		s = p->lchild;  //s左转
		while (s->rchild) { //然后右转到尽头
			q = s;
			s = s->rchild;
		} //找到中序遍历的直接前驱
		p->data = s->data; //修改p
		if (q != p) { //说明s左转后有右子树
			q->rchild = s->lchild;
		}
		else { //说明s左转后没有右转(没有右子树)
			q->lchild = s->lchild;
		}
		delete s;
	}
	return 1;
}

 1.右子树为空的情况:

	if (p->rchild == NULL) {//结点的右子树为空
		q = p;
		p = p->lchild;
		delete q;
	}

  若右子树为空,则删除结点之后,把左子树嫁接到删除结点的双亲结点即可,也即p=p->lchild,这里对这句代码进行解释:插入结点时,双亲结点的左子树或右子树指针是指向p的,也就是不管p自身发生什么样的变化,指针一直指向p。因此要完成嫁接的操作,只需要p变成自己的左子树即可,这样双亲结点的指针就指向了p的左子树。 注意p已经发生变化,因此需要提前准备另外一个指针q指向原先的p结点,然后删除

2.左子树为空的情况:

	else if (p->lchild == NULL) {
		q = p;
		p = p->rchild;
		delete q;
	}

  与右子树为空的情况基本相同,只不过p改变的位置换成了自己的右子树。

3.叶子结点的情况: 

   这里没有对叶子结点特别给出代码,这是因为叶子结点符合左子树为空同时也符合右子树为空,因此上面给出的代码对叶子结点同样有效,只不过是把NULL嫁接给了双亲结点。

4.左子树和右子树都不为空的情况:

	else {
		q = p;
		s = p->lchild;
		while (s->rchild) {
			q = s;
			s = s->rchild;
		}
		p->data = s->data;
		if (q != p) {
			q->rchild = s->lchild;
		}
		else {
			q->lchild = s->lchild;
		}
		delete s;
	}

      这是最复杂情况,这时我们的方法是——找到需要删除的结点p的直接前驱(或直接后继)s,用s的数据来替换结点p的数据,然后删除结点s即可。所谓直接前驱(直接后继),其实就是二叉排序树经过中序遍历后得到的数组中,p数据的直接前驱(直接后继)。

      这里的做法是使用直接前驱。

		q = p;
		s = p->lchild;
		while (s->rchild) {
			q = s;
			s = s->rchild;
		}

   先将要删除的结点p赋值给临时变量q,然后s先指向p的左子树,然后一直转到右子树的尽头先指向左子树是因为要小于p,然后转到右子树的尽头是因为要找到p的直接前驱)。q始终为s的双亲结点。

	    p->data = s->data;
        if (q != p) {
			q->rchild = s->lchild;
		}
		else {
			q->lchild = s->lchild;
		}
		delete s;

  找到直接前驱后,把p的数据修改为直接前驱s的数据。关于嫁接则有两种情况:q不等于p和q等于p。如果p等于q,也就是p的直接前驱s的双亲结点就是p,说明左转之后就没有右子树了,不能右转。这时要把s的左子树嫁接给q(也就是p)的左子树。如果p不等于q,说明已经右转过了,这时要把s的左子树嫁接给q的右子树。(s嫁接的都是左子树)

   嫁接完毕后删除结点s即可。

 (5)二叉排序树的查找删除函数

  这个函数主要的作用是找到要删除的结点,然后调用结点删除函数。 

int DeleteBST(BiTree& T, int key) {
	if (!T) {
		return 0;
	}//找到了叶子结点的下一层还没有找到,查找失败,返回0
	else {//还没有到叶子结点的下一层
		if (key == T->data) {//查找到了
			DeleteTree(T);//删除该结点
			return 1;
		}
		else if (key < T->data) {//结点的数据较大
			return DeleteBST(T->lchild, key);//需要往小的地方找(左子树)
		}
		else {//结点的数据较小
			return DeleteBST(T->rchild, key);//需要往大的地方找(右子树)
		}
	}
}

  与二叉排序树的查找函数几乎完全相同,唯一的区别就是调用了DeleteTree函数。 注意查找失败的条件是到了叶子结点的下一层还没有找到。

示例:

int main()
{
	BiTree t=NULL;
	int n;
	cout << "输入元素个数:";
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		int h;
		cin >> h;
		InsertBST(t, h);
	}
	BiTree p=NULL,f=NULL;
	int key1,key2;
	cout << "输入删除的元素:";
	cin >> key1;
	DeleteBST(t, key1);
	cout << "输入查找的元素:";
	cin >> key2;
	int j = SearchBST(t, key2, f, p);
	if (j == 1) {
		cout << "查找成功!查找的元素为:" << p->data;
	}
	else {//j==0
		cout << "查找失败!";
	}
	return 0;
}

int main()
{
	BiTree t=NULL;
	int n;
	cout << "输入元素个数:";
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		int h;
		cin >> h;
		InsertBST(t, h);
	}
	int key;
	cout << "输入要删除的元素:";
	cin >> key;
	if (DeleteBST(t, key)) {
		cout << "删除成功!";
	}
	else
		cout << "删除失败!";
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值