数据结构课程设计(六)---平衡二叉排序树操作的演示

1、任务简述:
利用平衡二叉排序树实现一个动态查找表。
要求:
(1)随机生成数据,根据随机数据创建平衡的BST
(2)以图形方式显示该平衡的BST(注意是图形,不是图像,利用画图函数)
(3)实现平衡二叉树的插入、删除、查找功能。
(4)操作方便。

2、算法描述:
1.创建BST:对于每一个随机生成的数据,我们用插入来生成BST,即每一次插入后,我们检查相应的平衡因子(左子树高度减去右子树高度),一直检查到0为止,如果出现abs(平衡高度)>=2,那么就进行一次adjust,有四种情况:RR,LL,RL,LR,下面我就这四种方式如何调整来做一个说明:
①RR型:新节点插入在A的右子树的右孩子上

先序遍历结果:
x A y B z C w

Adjust:
B

                            A         C 

                        X      y   z      w

②LL型:新节点插入在A的左子树的左孩子上

先序遍历结果:
z C w B y A x

Adjust:

                                 B
                            C         A 

                        z      w   y      x

③RL型:新节点插入在A的右子树的左孩子上

先序遍历结果:
x A z C w B y

Adjust:

                                 C
                            A         B 

                        X      z   w      y

④LR型:新节点插入在A的左子树的右孩子上

y B z C w A x
先序遍历结果:

Adjust:

                                 C
                            B         A 

                        y      z    w     x

2.删除节点:
删除节点我们可以分成4种情况加以考虑:
①删除节点为叶子节点:
直接删除,然后进行check和adjust。
②删除节点只有左孩子:
将其左孩子直接链接该节点的父节点,然后进行check和adjust。
③删除节点只有右孩子:
将其右孩子直接链接该节点的父节点,然后进行check和adjust。
④删除节点即有左孩子也有右孩子:
找出其左分支最大值,或者右分支最小值,进行替换,然后删除相应的被替换的节点,然后进行check和adjust。

3.查找节点:
递归查找就行了,判断当前节点是不是待查数据,是的话就返回该数据,否则就进入左子树和右子树进行查找。

3、源代码

#include <iostream>
#include <sstream>
#include <math.h>
#include "graphpainter.h"
#include "graphpainter.cpp"
using namespace std;

typedef int number;

char* int2str(int a, char* buff)
{
	sprintf(buff, "%d", a);
	return buff;
}

class BSTNode
{
public:
	BSTNode(number data=0, int cnt=1):data(data), count(cnt), left(NULL), right(NULL), pa(NULL), ban(0) {};
	number data; //值
	int count; //个数
	BSTNode *left; //左孩子
	BSTNode *right; //右孩子
	BSTNode *pa; //父亲
	int ban; //平衡因子
	//设置左孩子,并给左孩子的父亲赋值
	void setleft(BSTNode *p);
	//设置右孩子,并给右孩子的父亲赋值
	void setright(BSTNode *p);
	//连接结点,自动判断左右,要求p一定非空
	void setchild(BSTNode *p);
	char* toStr(char* buff);
	//递归重置一颗子树root上全部结点的平衡因子,返回值为树的高度
	static int setban(BSTNode *root);
};

//平衡二叉树类
class BBST
{
public:
	BSTNode *root; //根节点
	BBST() :root(NULL) {};
	void _free(BSTNode *btn)
	{
		if (btn->left)
			_free(btn->left);
		if (btn->right)
			_free(btn->right);
		delete btn;
	}
	~BBST()
	{
		if (root)
			_free(root);
	}
	/*寻找值为data的结点,如果找到则返回该点地址,否则返回NULL, last为最后搜索地址*/
	BSTNode* Find(number data, BSTNode **last = NULL);
	/*新增一个结点*/
	void Add(number data);
	/*删除一个结点*/
	void Del(number data);
	/*辅助函数:在插入中维护平衡因子,若仍然平衡,返回true;否则返回false,导出a,b,c结点
	cur为当前出发的结点,a,b,c相连且a->b->c
	*/
	bool _checkban(BSTNode* cur, BSTNode **a, BSTNode **b, BSTNode **c);
	/*辅助函数:在删除中维护平衡因子,若仍然平衡,返回true;否则返回false,导出a,b,c结点
	cur为要删除的叶子结点,a,b,c相连且a->b->c
	*/
	bool _checkban2(BSTNode *cur, BSTNode **a, BSTNode **b, BSTNode **c);
	BSTNode* _RR(BSTNode *a, BSTNode *b, BSTNode *c);
	BSTNode* _LL(BSTNode *a, BSTNode *b, BSTNode *c);
	BSTNode* _RL(BSTNode *a, BSTNode *b, BSTNode *c);
	BSTNode* _LR(BSTNode *a, BSTNode *b, BSTNode *c);
	/*辅助函数:调节树,利用中序遍历调节指针,返回中结点*/
	BSTNode* _adjust(BSTNode *a, BSTNode *b, BSTNode *c);
	/*用graphpainter库图形显示树*/
	void show_graph();
};

void OutMenu() 
{
	cout << "\t\t---------------081810221朱林昊---------------\n";
	cout << "\n\t\t--------------------AVL---------------------\n\n";  //说明该代码的实现功能
	cout << "\t\t* * * * * * * * * * * * * * * * * * * * * * *\n"; 
	cout << "\t\t*                                           *\n"; 
	cout << "\t\t*         AVL菜单                          *\n"; 
	cout << "\t\t*                                          *\n"; 
	cout << "\t\t*          1:显示图                        *\n"; 
	cout << "\t\t*          2:插入节点p                     *\n"; 
	cout << "\t\t*          3:删除节点p                     *\n"; 
	cout << "\t\t*          4:从p到p2等差插入节点           *\n"; 
	cout << "\t\t*          5:从p到p2等差删除节点           *\n";
	cout << "\t\t*          6:随机插入p个节点               *\n";
	cout << "\t\t*          7:查找数据p                     *\n"; 
	cout << "\t\t*          0:退出整个程序                  *\n"; 
	cout << "\t\t*                                           *\n"; 
	cout << "\t\t* * * * * * * * * * * * * * * * * * * * * * *\n"; 
}

int main()
{
	BBST BT;
	char op;
	number p, p2;
	system("color 1E");
	//OutMenu();  
	while (true)
	{
		fflush(stdin);//清除键盘缓冲区 
 		system("cls"); 
 		OutMenu(); 
		printf("\n");
		printf("\t\t请您选择:");
 		op=getchar(); 
		switch(op) 
 		{ 
 		case '1': 
  			BT.show_graph();//显示图 
  			getchar();
  			getchar();
  			break; 
 		case '2': 
 			cout << "\n请输入插入的节点:"; 
  			cin >> p;  //插入节点p 
			BT.Add(p);
			cout << "\n插入成功!";
			getchar();
			getchar();
  			break; 
 		case '3': 
 			cout << "\n请删除插入的节点:"; 
  			cin >> p; //删除节点p 
			try
			{
				BT.Del(p);
				cout << "\n删除成功!";
			}
			catch (invalid_argument e)
			{
				cout << "Can not find " << p << endl;
			}
			getchar();
			getchar();
  			break; 
 		case '4': 
 			cout << "\n请输入插入的等差节点的开头和结尾:";
  			cin >> p >> p2;  //插入p到p2的等差数列 
			for (number i = p; i <= p2; i++)
			{
				BT.Add(i);
				cout << "\n插入" << i << "成功!";
			}
			getchar();
			getchar();
  			break; 
 		case '5': 
 			cout << "\n请输入删除的等差节点的开头和结尾:";
  			cin >> p >> p2;  //删除p到p2的等差数列 
			for (number i = p; i <= p2; i++)
				try
			{
				BT.Del(i);
				cout << "\n删除" << i << "成功!";
			}
			catch (invalid_argument e)
			{
				cout << "\n没有找到: " << i << endl;
			}
			getchar();
			getchar();
  			break;
  		case '6':
  			cout << "\n请输入随机插入的数据个数:";
			cin >> p;  //随机插入p个数据 
			for (int i = 0; i < p; i++)
			{
				p2 = round(rand()%p);
				BT.Add(p2);
				cout << "随机添加:" << p2 << endl;
			}
			getchar();
			getchar();
  			break;
 		case '7':
 			cout << "\n请输入待查数据:"; 
 			cin >> p; //查找数据p 
			if (BT.Find(p))
			{
				cout << "数字 " << p << " 存在。" << endl;
			}
			else
			{
				cout << "数字 " << p << " 不存在。" << endl;
			}
			getchar();
			getchar();
 			break;
		case '0':
  			break; 
 		}
	}
	system("pause");
	return 0;
}

void BSTNode::setleft(BSTNode *p)
{
	left = p;
	if (p)
		p->pa = this;
}

void BSTNode::setright(BSTNode *p)
{
	right = p;
	if (p)
		p->pa = this;
}

void BSTNode::setchild(BSTNode *p)
{
	if (data < p->data)
		setright(p);
	else if (data > p->data)
		setleft(p);
	else
		cout << "ERROR Set Child: p==pa";
}

char* BSTNode::toStr(char* buff)
{
	stringstream str;
	str.str("");
	str << "\'";
	str << data;
	if (count > 1)
	{
		str << "(" << count << ")";
	}
	//str << "[" << ban << "]";
	str << "\'";
	str >> buff;
	return buff;
}

int BSTNode::setban(BSTNode *root)
{
	int h1, h2;
	if (!root)
		return 0;
	h1 = setban(root->left);
	h2 = setban(root->right);
	root->ban = h1 - h2;
	return (h1 > h2 ? h1 : h2) + 1;
}

BSTNode* BBST::Find(number data, BSTNode **last)
{
	BSTNode *p = root, *p1 = NULL;
	while (p)
	{
		if (p->data == data)
			return p;
		else if (p->data > data)
		{
			p1 = p;
			p = p->left;
		}
		else if (p->data < data)
		{
			p1 = p;
			p = p->right;
		}
	}
	if (last)
		*last = p1;
	return NULL;
}

void BBST::Add(number data)
{
	BSTNode *p, *p1, *a, *b, *c;
	p = Find(data, &p1);
	if (p)
	{
		//Condition 1: 已经存在
		p->count++;
		return;
	}
	if (!p1)
	{
		//Condition 2: p为根节点,则新建根节点
		root = new BSTNode(data);
	}
	else
	{
		p = new BSTNode(data);
		p1->setchild(p);
		if (!_checkban(p, &a, &b, &c))
			_adjust(a, b, c);
	}

}

void BBST::Del(number data)
{
	BSTNode *p, *p1, *p2, *cur, *a, *b, *c, *m;
	//搜索
	p = Find(data);
	if (!p)
		throw invalid_argument("Data not found!");
	//如果能直接减去
	if (p->count > 1)
	{
		p->count--;
		return;
	}
	//只在叶子节点开始删除:用比它稍大的替换
	p1 = p->right;
	p2 = p;
	while (p1)
	{
		p2 = p1;
		p1 = p1->left;
	}
	cur = p2;
	if (cur->right)
	{
		//特判:如果cur有一个右孩子,则因为cur没有左孩子,右孩子高度只能为1。
		//则将右孩子拿上来,高度-1。
		if (cur->pa)
			cur->pa->setchild(cur->right);
		p2 = cur->right;
	}
	else if (cur->left)
	{
		//特判:如果cur有一个左孩子,同理将左孩子拿上来,高度-1
		if (cur->pa)
			cur->pa->setchild(cur->left);
		p2 = cur->left;
	}
	else
	{
		//此时cur为叶子结点
		//特判:如果cur为根
		if (!cur->pa)
		{
			root = NULL;
			delete cur;
			return;
		}
		else if (cur->data < cur->pa->data)
			cur->pa->left = NULL;
		else
			cur->pa->right = NULL;
	}
	//向上调整

	while (!_checkban2(p2, &a, &b, &c))
	{
		m = _adjust(a, b, c);
		if (m == root)
			break;
		if (m->ban != 0)
			//若为0,则高度-1,继续向上搜索
			break;
		p2 = m;
	}
	if (cur != p)
	{
		p->data = cur->data;
		p->count = cur->count;
	}
	delete cur;
}

bool BBST::_checkban(BSTNode* cur, BSTNode **a, BSTNode **b, BSTNode **c)
{
	*b = cur;
	*a = (*b)->pa; //新插入的b肯定平衡,从父亲开始判断
	while (*a)
	{
		if ((*a)->data < (*b)->data)
			//b是a的右孩子
			(*a)->ban--;
		else
			(*a)->ban++;
		if ((*a)->ban == 0)
			//结论:0平衡因子将不会对上造成影响,不用调节
			return true;
		else if ((*a)->ban == 1 || (*a)->ban == -1)
		{
			//继续向上回溯
			*c = *b;
			*b = *a;
			*a = (*a)->pa;
		}
		else
			//肯定需要调节
			return false;
	}
	return true;
}

bool BBST::_checkban2(BSTNode *cur, BSTNode **a, BSTNode **b, BSTNode **c)
{
	BSTNode *p = cur, *pp = cur->pa;
	int oriban; //父亲结点原来的平衡因子
	while (pp)
	{
		oriban = pp->ban;
		if (p->data < pp->data)
			//左子树高度-1
			pp->ban--;
		else
			//右子树高度-1
			pp->ban++;
		if (oriban == 0)
		{
			//父亲结点高度不变,不需处理
			return true;
		}
		else if (pp->ban == 0)
		{
			//父亲结点的高度-1,向上传递
			p = pp;
			pp = pp->pa;
		}
		else
		{
			//父亲结点需要进行调整
			(*a) = pp;
			if (pp->ban == -2)
				(*b) = pp->right;
			else
				(*b) = pp->left;
			if ((*b)->ban <= 0)
				//对于ban==0,则既可以L调整也可以R调整
				(*c) = (*b)->right;
			else
				(*c) = (*b)->left;
			return false;
		}
	}
	//全部搜索结束,不需调整
	return true;

}

BSTNode* BBST::_RR(BSTNode *a, BSTNode *b, BSTNode *c)
{
	BSTNode *pa; //上面的根节点
	BSTNode *x, *y, *z, *w;
	pa = a->pa;
	x = a->left;
	y = b->left;
	z = c->left;
	w = c->right;
	a->setright(y);
	b->setleft(a);
	return b;
}

BSTNode* BBST::_LL(BSTNode *a, BSTNode *b, BSTNode *c)
{
	BSTNode *pa; //上面的根节点
	BSTNode *x, *y, *z, *w;
	pa = a->pa;
	x = a->right;
	y = b->right;
	z = c->left;
	w = c->right;
	a->setleft(y);
	b->setright(a);
	return b;
}

BSTNode* BBST::_RL(BSTNode *a, BSTNode *b, BSTNode *c)
{
	BSTNode *pa; //上面的根节点
	BSTNode *x, *y, *z, *w;
	pa = a->pa;
	x = a->left;
	y = b->right;
	z = c->left;
	w = c->right;
	a->setright(z);
	b->setleft(w);
	c->setleft(a);
	c->setright(b);
	return c;
}

BSTNode* BBST::_LR(BSTNode *a, BSTNode *b, BSTNode *c)
{
	BSTNode *pa; //上面的根节点
	BSTNode *x, *y, *z, *w;
	pa = a->pa;
	x = a->right;
	y = b->left;
	z = c->left;
	w = c->right;
	b->setright(z);
	a->setleft(w);
	c->setleft(b);
	c->setright(a);
	return c;

}

BSTNode* BBST::_adjust(BSTNode *a, BSTNode *b, BSTNode *c)
{
	BSTNode *m; //中间结点
	BSTNode *pa = a->pa; //原始根
	if (a->right == b&&b->right == c)
		m = _RR(a, b, c);
	else if (a->left == b&&b->left == c)
		m = _LL(a, b, c);
	else if (a->right == b&&b->left == c)
		m = _RL(a, b, c);
	else if (a->left == b&&b->right == c)
		m = _LR(a, b, c);
	//重置平衡因子
	BSTNode::setban(m);
	//修改根
	if (pa)
		//存在原根
		pa->setchild(m);
	else
	{
		//设置新根
		root = m;
		m->pa = NULL;
	}
	return m;
}
void BBST::show_graph()
{
	graphpainter GP("graphpainter\\graphpainter.exe");
	GP.newpainter("bstgraph.txt", BT);
	GP.setfigure("figsize", "16,8");
	GP.setlayout("root", "0");
	if (root == NULL)
	{
		GP.setbtnode('L', "0", "\'空\'");
	}
	else
	{
		//宽搜
		int head, tail;
		BSTNode** Q; //搜索队列
		BSTNode* cur;
		char buff[255], buff2[255];
		Q = new BSTNode*[1000];
		Q[0] = root;
		GP.setbtnode('L', "0", root->toStr(buff));
		head = 0;
		tail = 0;
		while (head <= tail)
		{
			cur = Q[head];
			//加入自己结点
			if (cur->left)
			{
				Q[++tail] = cur->left;
				GP.setedge(int2str(head, buff), int2str(tail, buff2));
				GP.setbtnode('L', int2str(tail, buff), cur->left->toStr(buff2));
			}
			if (cur->right)
			{
				Q[++tail] = cur->right;
				GP.setedge(int2str(head, buff), int2str(tail, buff2));
				GP.setbtnode('R', int2str(tail, buff), cur->right->toStr(buff2));
			}
			head++;
		}
	}
	//输出
	GP.draw();
	GP.close();
}//581行 

4、运行结果
在这里插入图片描述
随机插入80个节点:
在这里插入图片描述
在这里插入图片描述
显示图:
在这里插入图片描述
删除节点28并显示:
在这里插入图片描述
在这里插入图片描述
插入100,并显示:
在这里插入图片描述
在这里插入图片描述
查找数据100和210:
在这里插入图片描述
在这里插入图片描述
5、总结
性能分析:
时间复杂度:由于插入,删除,查找的时间复杂度都为O(logn),所以可以认为本程序的时间复杂度为O(logn)。
空间复杂度:O©。
遇到的问题与解决方法:
1.一开始,我删除既有左孩子又有右孩子的节点的方法是,中序遍历,然后再生成新的AVL,这样操作会让代码的时间复杂度变为O(n),空间复杂度变为O(n),因为需要对树进行中序遍历,并且存储在某一个数组里面。但是复习了老师所讲的知识,还是采用了选择左子树或者右子树最值的方法
2.本题类的操作参考了周易同学,本来我是采用上课讲的二叉树的结构体来写的,但是那样操作过于麻烦,所以在周易同学的帮助下,我采用了类来写,可以在一定程度上减少重复代码的数量。
心得体会:
从结果上来看,程序代码是正确的,并且可以很好的执行插入,删除,画图等功能,不过在写删除的时候,需要进行很多次特判,所以代码有些冗长。

关于本文的画图头文件#include “graphpainter.h”,#include "graphpainter.cpp"需要的可以在评论区留言

猜你喜欢

转载自blog.csdn.net/zhulinhao/article/details/106898724
今日推荐