非递归实现AVL树
前言
在我完成了递归实现Avl树的操作后想用非递归实现AVl树。历时许久,发现并解决了我之前存在的很多问题,比如经常需要考虑指针的运用并防止野指针的出现,也使我体验了不参考别人,完全自己独立设计程序的过程。在这个过程中我有许多构思,我需要不断比较分析优劣并找出可能出现的问题并解决,还要考虑不同的解决方案对其他部分的影响,还要根据需求运用合适的设计思想设计适合的操作,这个过程是最难的需要解决的问题也是最多的,在设计过程中我可能遇见了上百个问题,需要不断发现问题解决问题,有时候进行了大半发现前面的设计思路不合适又要推倒重来,差点给我整哭了,最后还是解决了,特此记之。
一、设计思想
刚开始时我是想采用自顶向下和分而治之的设计思想,先将树的调整操作与其他操作分离,插入删除与二叉树的非递归操作类似,单双旋转与递归实现Avl树的单双旋转类似只是多了一个调整树的高度的过程。然后重点是树的平衡调整操作,我采用自顶向下动态调整的思路,但是在后面的过程中我发现如果采用自顶向下的调整方法当较底层的结点进行调整时有小部分操作会打破平衡,遂弃之,换成自底向上不断回溯的思路,运用栈记录路径产生于类似递归的效果。
二、程序剖析
1、结点设计:
由于我不想用一个指针域来指向父结点,会使操作变得更复杂,所以我后面操作使用栈来进行回溯,这里也可以声明一个父结点后就不需要使用栈了,看个人喜好。
typedef struct Avlnode{
ElementType data=0;
struct Avlnode* Left=nullptr;
struct Avlnode* Right=nullptr;
int height=0;
}AvlNode;
typedef Avlnode* Avltree;
2、单旋转函数:
函数参数:待旋转的子树的根结点,和选择旋转类型
函数实现:左单旋转根结点的左儿子成为新的根结点,原根结点的左儿子的右儿子成为其新左儿子,原根结点成为新根结点的右儿子;右单旋转反之。然后因为旋转可能会改变结点高度所以要调整结点高度,注意要自下向上调整,因为旋转后原根结点高度低所以先调节原根结点在调整新根结点,高度为其左右儿子高度较大的加一。
Avltree SingleRotate(Avltree root,int choice){
Avltree p=nullptr;
if(choice==0){ //left single rotate
p=root->Left;
root->Left=p->Right;
p->Right=root;
}else if(choice==1){ //right single rotate
p=root->Right;
root->Right=p->Left;
p->Left=root;
}else return nullptr;
root->height=Max(Height(root->Left),Height(root->Right))+1;
p->height=Max(Height(p->Left),Height(p->Right))+1;
return p;
}
3、双旋转函数:
函数参数:待旋转根结点与选择旋转的类型
函数实现:双旋转其实就是进行两次反向的单旋转,左单旋转先对其左儿子进行一次右单旋转,再对根结点进行一次左单旋转;右双旋转反之。
Avltree DoubleRotate(Avltree root,int choice){
if(choice==0){ //left double rotate
root->Left=SingleRotate(root->Left,1);
return SingleRotate(root,0);
}else if(choice==1){ //right double rotate
root->Right=SingleRotate(root->Right,0);
return SingleRotate(root,1);
}else return nullptr;
}
4、查找函数:
函数参数:整个树的根结点,待查找元素,返回类型
函数实现:循环条件root指针非空,当root指针指向的数据不等于待查找数据时将其指向的结点赋给父指针,然后移动root指针,大于待查找元素则向左子树移动,小于则向右子树移动,若相等则根据需要返回待查找元素的结点或其父结点。
Avltree Find(Avltree root,ElementType x,int choice){
Avltree father=root;
while(root){
if(root->data!=x){
father=root;
if(root->data>x)root=root->Left;
else if(root->data<x)root=root->Right;
}else{
if(choice==1)return root;
else if(choice==2)return father;
else exit(0);
}
}
cout<<"Not found."<<endl;
return nullptr;
}
5、高度调整函数;
函数参数:根结点,插入或删除的数据
函数实现:最重要的是栈的使用,使用一个栈利用循环记录插入或删除的元素的路径,然后回溯调整高度。
void HeightChange(Avltree root,ElementType x){
stack<Avltree>str;
while(root!=nullptr&&root->data!=x){
str.push(root);
if(root->data>x)root=root->Left;
else root=root->Right;
}
while(!str.empty()){
root=str.top();
root->height=Max(Height(root->Left),Height(root->Right))+1;
str.pop();
}
}
6、平衡调整函数;
函数参数;根结点,最后一次插入或删除的数据
函数实现;这是非递归实现Avl树的最关键步骤,用一个栈记录之前插入或删除的结点的路径,同时用一个从1开始的计数变量记录次数使下一次循环多做一轮达到完全遍历的效果,开始时声明了一个空指针为父指针,每次循环时因为是回溯所以倒过来如果root不等于父指针则使其指向父指针指向的结点 ,如果栈非空则父指针为栈顶指针然后出栈,当root非空时比较它的左右子树高度差是否达到2,若达到2则针对左——左,进行左单旋转,左——右进行左双旋转,右——左进行右双旋转,右——右进行右单旋转。旋转结束后比较开始时保存的整个树的根结点是否是旋转后的root的子节点,若是则root成为新根结点;然后再判断旋转过后父指针指向的结点是否为root的子结点,若不是则根据父节点与新root结点的相对关系使父结点指向新root结点,然后调整高度,计数变量减减;最后返回新的根结点。
Avltree Adjust(Avltree root,ElementType x){
Avltree newroot=root,father=nullptr;
int count=1;
stack<Avltree>str;
while(root!=nullptr&&root->data!=x){
str.push(root);
if(root->data>x)root=root->Left;
else root=root->Right;
count++;
}
while(count){
if(root!=father)root=father;
if(!str.empty())father=str.top();
if(!str.empty())str.pop();
if(root!=nullptr){
if(Max(Height(root->Left),Height(root->Right))-
Min(Height(root->Left),Height(root->Right))==2){
if(Height(root->Left)>Height(root->Right)){
if(Height(root->Left->Left)>Height(root->Left->Right))root=SingleRotate(root,0); //left-left
else root=DoubleRotate(root,0); //left-right
}else{
if(Height(root->Right->Right)>Height(root->Right->Left))root=SingleRotate(root,1); //right-right
else root=DoubleRotate(root,1); //right-left
}
if(root->Left==newroot||root->Right==newroot)newroot=root;
if(root->Left!=father&&root->Right!=father){
if(father!=root&&father->data>root->data)father->Left=root;
else if(father!=root&&father->data<root->data)father->Right=root;
}
}
}
HeightChange(newroot,x);
count--;
}
return newroot;
}
7、插入函数:
函数参数:根结点,待插入元素
函数实现:若根结点非空,先循环每次父指针记录上一个结点,然后结点移动,直到找到合适的位置。循环结束后开辟一个结点将待插入元素赋给它,然后根据父结点与新结点的关系将父结点与新结点连接起来,如果父结点的左右儿子有一个为空说明插入前父结点是树叶插入改变了高度有可能改变了平衡,返回调整后的根结点,若都有则直接返回根结点。
Avltree Insert(Avltree root,ElementType x){
Avltree p=root,parent=root;
if(root==nullptr){
root=new Avlnode;
root->data=x;
return root;
}
while(p){
parent=p;
if(p->data<x)p=p->Right;
else if(p->data>x)p=p->Left;
else{
cout<<"Date collision."<<endl;
return root;
}
}
p=new Avlnode;
p->data=x;
if(parent->data>x) parent->Left=p;
else parent->Right=p;
if(parent->Left==nullptr||parent->Right==nullptr){
HeightChange(root,x);
return Adjust(root,x);
}else return root;
}
8、使父结点的孙子结点代替其子结点
函数参数:父结点指针,子结点指针
void Verdict(Avltree father,Avltree son){//不考虑相等的情况
if(father->data>son->data){
if(son->Left) father->Left=son->Left;
else father->Left=son->Right;
}else if(father->data<son->data){
if(son->Left) father->Right=son->Left;
else father->Right=son->Right;
}else cout<<"abnormal condition."<<endl;
}
9、删除函数:
函数参数:根结点,待删除元素
函数实现:用Find函数定位待删除元素和它的父结点所在位置。若待删除结点左右儿子都非空,在其右子树中找到最小的结点和它的父结点利用Verdict函数调整关系,然后用其右子树中最小的结点数据代替这个结点再释放那个右子树中最小的结点;若待删除的结点只有一个儿子或为树叶,先判断其父结点是否与其相等,若相等则说明待删除结点为根结点且最多有一个儿子(因为若它有两个儿子在两边则为上一种情况,若它有两个儿子在同一边则在删除前这颗树已经不平衡,但每次操作前都是已经调整平衡后的树,故这两种情况在此处不会发生)所以若其左儿子非空则返回左儿子,否则返回其右儿子即可(因我设置的结点没有父指针所以当失去其父节点地址时连接关系逻辑上就断开了,删除便可略去),若不为根结点则用Verdict函数调整关系,删除其子结点。最后返回Adjust函数调整后的新根结点地址。
Avltree Delete(Avltree root,ElementType x){
Avltree father=Find(root,x,2),son=Find(root,x,1),min=nullptr;
if(son->Left&&son->Right){
for(min=son->Right;min->Left;min=min->Left);
father=Find(son,min->data,2);
Verdict(father,min);
son->data=min->data;
delete(min);
}else{
if(father!=son)Verdict(father,son);
else{
if(son->Left)return son->Left;
else return son->Right;
}
delete(son);
}
return Adjust(root,x);
}
10、打印函数
比较简单没什么好说的
void PrintTree(Avltree root){
Avltree p=root;
stack<Avltree>str;
while(p||!str.empty()){
for(;p;p=p->Left) str.push(p);
if(!str.empty()){
p=str.top();
str.pop();
cout<<p->data<<endl;
p=p->Right;
}
}
}
整体代码:
#include<iostream>
#include<stack>
typedef int ElementType;
typedef struct Avlnode{
ElementType data=0;
struct Avlnode* Left=nullptr;
struct Avlnode* Right=nullptr;
int height=0;
}AvlNode;
typedef Avlnode* Avltree;
using namespace std;
int Height(Avltree); //返回结点高度
ElementType Max(ElementType,ElementType); //返回其中较大的值
ElementType Min(ElementType,ElementType); //返回其中较小的值
Avltree SingleRotate(Avltree,int); //单旋转
Avltree DoubleRotate(Avltree,int); //双旋转
Avltree Find(Avltree,ElementType,int); //查找函数,可以返回这个结点或它的父结点
Avltree Insert(Avltree,ElementType); //插入函数
Avltree Delete(Avltree,ElementType); //删除函数
Avltree Adjust(Avltree,ElementType); //调整平衡函数
void Verdict(Avltree,Avltree); //用子节点的儿子代替它自己
void HeightChange(Avltree,ElementType); //调整高度
void PrintTree(Avltree); //输出函数
int main(void){
ElementType x;
Avltree root=nullptr;
while(cin>>x) root=Insert(root,x);
cin.clear();
cin.ignore();
cout<<"Plesea input the number you want to delete."<<endl;
cin>>x;
root=Delete(root,x);
PrintTree(root);
return 0;
}
int Height(Avltree root){
if(root==nullptr)return -1;
else return root->height;
}
ElementType Max(ElementType x,ElementType y){
if(x>y)return x;
else return y;
}
ElementType Min(ElementType x,ElementType y){
if(x>y) return y;
else return x;
}
Avltree SingleRotate(Avltree root,int choice){
Avltree p=nullptr;
if(choice==0){ //left single rotate
p=root->Left;
root->Left=p->Right;
p->Right=root;
}else if(choice==1){ //right single rotate
p=root->Right;
root->Right=p->Left;
p->Left=root;
}else return nullptr;
root->height=Max(Height(root->Left),Height(root->Right))+1;
p->height=Max(Height(p->Left),Height(p->Right))+1;
return p;
}
Avltree DoubleRotate(Avltree root,int choice){
if(choice==0){ //left double rotate
root->Left=SingleRotate(root->Left,1);
return SingleRotate(root,0);
}else if(choice==1){ //right double rotate
root->Right=SingleRotate(root->Right,0);
return SingleRotate(root,1);
}else return nullptr;
}
Avltree Find(Avltree root,ElementType x,int choice){
Avltree father=root;
while(root){
if(root->data!=x){
father=root;
if(root->data>x)root=root->Left;
else if(root->data<x)root=root->Right;
}else{
if(choice==1)return root;
else if(choice==2)return father;
else exit(0);
}
}
cout<<"Not found."<<endl;
return nullptr;
}
void HeightChange(Avltree root,ElementType x){
stack<Avltree>str;
while(root!=nullptr&&root->data!=x){
str.push(root);
if(root->data>x)root=root->Left;
else root=root->Right;
}
while(!str.empty()){
root=str.top();
root->height=Max(Height(root->Left),Height(root->Right))+1;
str.pop();
}
}
Avltree Adjust(Avltree root,ElementType x){
Avltree newroot=root,father=nullptr;
int count=1;
stack<Avltree>str;
while(root!=nullptr&&root->data!=x){
str.push(root);
if(root->data>x)root=root->Left;
else root=root->Right;
count++;
}
while(count){
if(root!=father)root=father;
if(!str.empty())father=str.top();
if(!str.empty())str.pop();
if(root!=nullptr){
if(Max(Height(root->Left),Height(root->Right))-
Min(Height(root->Left),Height(root->Right))==2){
if(Height(root->Left)>Height(root->Right)){
if(Height(root->Left->Left)>Height(root->Left->Right))root=SingleRotate(root,0); //left-left
else root=DoubleRotate(root,0); //left-right
}else{
if(Height(root->Right->Right)>Height(root->Right->Left))root=SingleRotate(root,1); //right-right
else root=DoubleRotate(root,1); //right-left
}
if(root->Left==newroot||root->Right==newroot)newroot=root;
if(root->Left!=father&&root->Right!=father){
if(father!=root&&father->data>root->data)father->Left=root;
else if(father!=root&&father->data<root->data)father->Right=root;
}
}
}
HeightChange(newroot,x);
count--;
}
return newroot;
}
Avltree Insert(Avltree root,ElementType x){
Avltree p=root,parent=root;
if(root==nullptr){
root=new Avlnode;
root->data=x;
return root;
}
while(p){
parent=p;
if(p->data<x)p=p->Right;
else if(p->data>x)p=p->Left;
else{
cout<<"Date collision."<<endl;
return root;
}
}
p=new Avlnode;
p->data=x;
if(parent->data>x) parent->Left=p;
else parent->Right=p;
if(parent->Left==nullptr||parent->Right==nullptr){
HeightChange(root,x);
return Adjust(root,x);
}else return root;
}
Avltree Delete(Avltree root,ElementType x){
Avltree father=Find(root,x,2),son=Find(root,x,1),min=nullptr;
if(son->Left&&son->Right){
for(min=son->Right;min->Left;min=min->Left);
father=Find(son,min->data,2);
Verdict(father,min);
son->data=min->data;
delete(min);
}else{
if(father!=son)Verdict(father,son);
else{
if(son->Left)return son->Left;
else return son->Right;
}
delete(son);
}
return Adjust(root,x);
}
void PrintTree(Avltree root){
Avltree p=root;
stack<Avltree>str;
while(p||!str.empty()){
for(;p;p=p->Left) str.push(p);
if(!str.empty()){
p=str.top();
str.pop();
cout<<p->data<<endl;
p=p->Right;
}
}
}
void Verdict(Avltree father,Avltree son){//不考虑相等的情况
if(father->data>son->data){
if(son->Left) father->Left=son->Left;
else father->Left=son->Right;
}else if(father->data<son->data){
if(son->Left) father->Right=son->Left;
else father->Right=son->Right;
}else cout<<"abnormal condition."<<endl;
}
总结
这次设计过程使我学到很多东西,设计思想的运用,设计时考虑局部代码对整体代码的影响,时时刻刻都要防止野指针的使用,思考要缜密;提升了我发现问题并解决问题的能力,有些问题看起来很简单但解决其实不简单,更重要的是为什么要这样解决,以及如何实现更好的解决方案,这次过程还使我学到了以后要记录自己想到的问题和想出的解决方案,方便以后复盘。