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"需要的可以在评论区留言