二叉树的封装
一. 源代码 BiTree.h
1 #include<iostream> 2 typedef char TElemType; // 方便移植,方便控制树节点元素的数据类型 3 typedef TElemType Tree_Data;//提高程序可读性 4 typedef struct BiTNode{ //定义存储结构, 5 Tree_Data data; 6 struct BiTNode *lchild,*rchild; 7 }BiTNode, *Tree; //树的存储结构,在类的内部嵌套 8 typedef Tree SElemType;//确定栈的数据元素 9 typedef Tree QElemType; 10 #include "F:\A_Programer\DataStructure\Class\Stack.h"//包含栈的操作 11 #include "F:\A_Programer\DataStructure\Class\Queue_1.h"//包含队列操作 12 #include "F:\A_Programer\DataStructure\Class\Queue.h"//包含队列操作 13 using namespace std; 14 class BiTree 15 { 16 private: 17 Tree T; 18 Tree T1; 19 public: 20 BiTree();//初始化一棵树 21 void CreateBiTree_BF();//以广度优先建立一棵树 22 bool ClearBiTree();//主动销毁一棵树 23 void PreOrderTraverse(Tree t);//前序遍历 24 void InOrderTraverse(Tree t);//中序遍历 25 void PostOrderTraverse(Tree t);//后序遍历 26 void PreOrderTraverse_Thr(Tree t);//前序遍历,线性 27 void InOrderTraverse_Thr(Tree t);//中序遍历,线性 28 void PostOrderTraverse_Thr(Tree t);//后序遍历,线性 29 void LevelOrderTraverse();//广度优先遍历 BFS 层序遍历 30 void LevelOrderTraverseT1(); 31 Tree GetTree(); 32 Tree GetTree1(); 33 ~BiTree();//被动销毁一棵树 34 }; 35 BiTree::BiTree() 36 { 37 T=(Tree)new BiTNode; //申请一个树节点,并强制类型转换,作为树的头结点 38 T->lchild=NULL;//左子树置空 39 T->rchild=NULL;//右子树置空 40 } 41 void BiTree::LevelOrderTraverse() 42 { 43 /* 44 思路:(参考书目ISB 978-7-118-05852) 45 1. 访问元素所指结点 46 2. 若该元素所指结点左右子树不空时,则将左右子树依次入队 47 3. 不断循环,直到队列为空 48 */ 49 Queue Q;//初始化队列,这个队列用来存放树的节点; 50 Queue1 Q1;//初始化队列,这个队列用来存放节点编号,方便建立树; 51 int lab=1;//第一个节点编号 52 Q.EnterQueue(T);//将树根放入队列 53 Q1.EnterQueue(lab);//赋予其编号 54 while(!Q.IsEmpty())//结束条件 55 { 56 Tree t;//临时变量,树节点 57 int num;//临时变量,节点编号 58 Q1.DeleteQueue(&num);//出队操作 59 Q.DeleteQueue(&t);//出队操作 60 cout<<num<<"节点的值"<<t->data<<endl;//输出节点的值 61 if(t->lchild!=NULL)//队列先进先出,左子树先入队 62 { 63 Q.EnterQueue(t->lchild);//入队操作 64 num=Q1.GetRear();//获取队位元素的值 65 num++;//++便是此节点的编号 66 Q1.EnterQueue(num);//入队 67 } 68 if(t->rchild!=NULL)//队列先进先出,右子树后入队 69 { 70 Q.EnterQueue(t->rchild);//入队操作 71 num=Q1.GetRear();//获取队位元素的值 72 num++;//++便是此节点的编号 73 Q1.EnterQueue(num);//入队 74 } 75 } 76 } 77 void BiTree::CreateBiTree_BF() 78 { 79 /* 80 关键注意 81 1. 左子树先入队,右子树后入队 82 2. 注意入队与出队时机 83 */ 84 Queue Q;//初始化队列,这个队列用来存放树的节点 85 Queue1 Q1;//初始化队列,这个队列用来存放节点编号,方便建立树 86 int num=1;//第一个节点编号 87 Q.EnterQueue(T);//将树根放入队列 88 Q1.EnterQueue(num);//赋予其编号 89 bool b;//用来判断是否具有左或右子树,由用户输入 90 Tree t;//临时变量 91 while(!Q.IsEmpty())//结束条件 92 { 93 Q1.DeleteQueue(&num);//出对操作 94 cout<<"请输入"<<num<<"节点的值"<<endl;//提示信息 95 Q.DeleteQueue(&t);//出对操作 96 cin>>t->data;//赋值 97 cout<<"此节点是否具有左子树"<<endl;//提示信息 98 cout<<"是 1"<<endl; 99 cout<<"否 0"<<endl; 100 cin>>b;//输入布尔量 101 if(b)//如果有左子树,则先将其入队 102 { 103 Tree p=new BiTNode;//新建节点 104 t->lchild=p;//赋值 105 Q.EnterQueue(p);//入队 106 num=Q1.GetRear();//获取队尾元素 107 num++;//++便是该节点编号 108 Q1.EnterQueue(num);//入队 109 } 110 else 111 t->lchild=NULL;//赋值为空 112 cout<<"是否具有右子树"<<endl;//提示信息 113 cout<<"是 1"<<endl; 114 cout<<"否 0"<<endl; 115 cin>>b;//输入布尔量 116 if(b)//如果有有子树,则后将其入队 117 { 118 Tree p=new BiTNode;//新建节点 119 t->rchild=p;//赋值 120 Q.EnterQueue(p);//入队 121 num=Q1.GetRear();//获取队尾元素 122 num++;//++便是该节点编号 123 Q1.EnterQueue(num);//入队 124 } 125 else t->rchild=NULL;//赋值为空 126 } 127 } 128 void BiTree::PreOrderTraverse( Tree t) 129 { 130 if(t)//结束条件 131 { 132 cout<<t->data<<endl; //输出根节点的值 133 PreOrderTraverse(t->lchild); //访问左子树 134 PreOrderTraverse(t->rchild); //访问右子树 135 } 136 } 137 Tree BiTree::GetTree() 138 { 139 return this->T; 140 } 141 Tree BiTree::GetTree1() 142 { 143 return this->T1; 144 } 145 void BiTree::InOrderTraverse(Tree t) 146 { 147 if(t)//递归结束条件 148 { 149 InOrderTraverse(t->lchild);//首次寻找空树的左节点 150 cout<<t->data<<endl; //首次作为根访问 151 InOrderTraverse(t->rchild);//首次访问为空树的右节点 152 //结束时以t为根节点的树已访问完毕 153 } 154 } 155 void BiTree::PostOrderTraverse(Tree t) 156 { 157 if(t) 158 { 159 PostOrderTraverse( t->lchild); // 首次访问的树必定为一颗空树,代表此节点访问结束,在此基础上逐步递归,访问其他节点; 不管哪种递归方式,访问他的时候必定作为根,相对于空树 160 PostOrderTraverse( t->rchild); 161 cout<<t->data<<endl; 162 } 163 } 164 void BiTree::PreOrderTraverse_Thr(Tree t) 165 { 166 /* 167 先序遍历 168 非递归实现方法 169 思路如下: 170 访问思路 171 1. 不断访问根节点,并且压入栈,直到寻找第一课空树,,且这个空树树必定作为其所在树的左子树; 172 2. 访问栈顶节点,如果栈顶节点的右孩子为空,则此节点已经失去价值,弹出; 否者,将其作为一颗新树访问,重复上述过程; 173 3. 循结束环条件,栈空; 174 我们应当重点考察的位置是 ***入栈时机与出栈时机*** 175 */ 176 Stack S;// 初始化栈 177 S.Push(t);//将根节点入栈 178 S.GetTop(&t);//获取栈顶元素 179 while(!S.IsEmpty())//结束条件 180 { 181 while(t)//t==0,结束循环,即遇到左子树为空时停止循环 182 { 183 cout<<t->data<<endl;//访问结点 184 S.Push(t->lchild);//将左节点入栈 185 S.GetTop(&t);//更新t 186 } 187 S.Pop(&t);//最后一次入栈的是一个空指针,出栈,当然这个语句还有其他巧妙的作用 188 if(!S.IsEmpty())//栈不空,就出栈 189 { 190 S.Pop(&t);//出栈 191 S.Push(t->rchild);//将其右孩子入栈,如果这值为NULL,S.Pop(&t);" //最后一次入栈的是一个空指针,出栈,当然这个语句还有其他巧妙的作用 " ,这就是巧妙之处 192 S.GetTop(&t);//更新t 193 } 194 //S.Display(); 195 } 196 } 197 void BiTree::InOrderTraverse_Thr(Tree t) 198 { 199 /* 200 思路: 201 1.不断沿左子树深入,并且入栈; 直到遇到空左子树为止; 202 2.访问此节点; 203 3.接着将此节点右子树入栈,并且将此节点出栈 204 4.重复上述过程,直到栈空 205 */ 206 Stack S;//初始化话栈 207 S.Push(t);//根节点入栈 208 while(!S.IsEmpty())//栈空为结束条件 209 { 210 S.GetTop(&t);//获取栈首元素 211 while(t)//不断深入左子树,直到左子树为空树 212 { 213 t=t->lchild;//更新t 214 S.Push(t);//将左子树入栈 215 } 216 S.Pop(&t);//最后一次入栈的是一个空指针,出栈,当然这个语句还有其他巧妙的作用 217 if(!S.IsEmpty())//栈不空,就出栈 218 { 219 S.Pop(&t);//出栈 220 cout<<t->data<<endl;//访问该节点 221 S.Push(t->rchild);//将其右孩子入栈,如果这值为NULL,“S.Pop(&t); //最后一次入栈的是一个空指针,出栈,当然这个语句还有其他巧妙的作用 " ,这就是巧妙之处 222 } 223 } 224 } 225 void BiTree::PostOrderTraverse_Thr(Tree t) 226 { 227 /* 228 关键技术: 229 采用节点保留技术,来判断是否访问当前节点 230 在这个程序中采用pre来保留节点 231 pre==NULL 这是其初值 232 在此之后其的值都将为先前前访问过的节点 233 思路: 234 1.对于首个节点,现将自己入栈(根) 235 2.处理栈顶元素 236 (1)访问该节点的条件 237 【1】 当前节点的左右子树都为空;(t->lchild==NULL&&t->rchild==NULL) ,这个条件将用来第一次更新pre的值 238 【2】 pre!=NULL&&(t->rchild==pre||t->lchild==pre) 239 解释 240 *1 其中pre!=NULL ,主要作用起在第一次深入过程中,只有这样才能将所有元素入栈 241 *2 在第一次pre被更新后,这语句的作用更新为(或者说等同于,因为pre不可能再为NULL)t->rchild==pre||t->lchild==pre 242 第一层: t->rchild==pre 243 在t的右子树且左子树存在的情况下,此语句起作用 244 在t的右子树且左子树不存在的情况下,此语句也起作用(因为这俩种情况最先弹出的元素必定为节点的右子树) 245 第二层: t->lchild==pre 246 当且仅当t只有左子树是时,语句起作用 247 248 (2)将右子树入栈(当然如果是空树就不入栈)!!!! 很重要,后将左子树入栈 (当然如果是空树就不入栈) 249 250 */ 251 Tree pre=NULL; //初始化为空值 252 Stack S; //初始化栈 253 S.Push(t); //将根节点入栈 254 while(!S.IsEmpty()) 255 { 256 S.GetTop(&t);//更新t,处理栈首元素 257 if((t->lchild==NULL&&t->rchild==NULL)||(pre!=NULL&&(t->rchild==pre||t->lchild==pre)))//出栈的情况,即是否可访问该节点的条件,否则继续深入 258 { 259 cout<<t->data<<endl;//访问该节点 260 S.Pop(&t);//该节点失去存在的意义,出栈 261 pre=t;//更新pre 262 } 263 else//继续入栈的情况 264 { 265 if(t->rchild!=NULL)//先将右子树入栈,空树不如栈 266 { 267 S.Push(t->rchild); 268 } 269 if(t->lchild!=NULL)//再将右子树入栈,空树不如栈,这个很关键 270 { 271 S.Push(t->lchild); 272 } 273 } 274 } 275 } 276 BiTree::~BiTree() 277 { 278 /* 279 宽度优先删除节点 280 */ 281 Tree t;//临时变量 282 t=T;//将根节点赋值 283 Queue Q;//初始化队列 284 Q.EnterQueue(t);//入队 285 while(!Q.IsEmpty())//结束条件 286 { 287 Q.DeleteQueue(&t);//处理队首元素 288 if(t->lchild!=NULL)//榨取节点利用价值 289 { 290 Q.EnterQueue(t->lchild);//保存有效信息 291 } 292 if(t->rchild!=NULL)//榨取节点利用价值 293 { 294 Q.EnterQueue(t->rchild);//保存有效信息 295 } 296 free(t);//抛弃无用节点 297 } 298 }
几点 说明
1.#include "F:\A_Programer\DataStructure\Class\Stack.h"//包含栈的操作 可参考我的其他随笔,有详细解释
代码如下:
#pragma once #include<iostream> #include<cstring> typedef SElemType Stack_Data; //栈的数据类型 const int Stack_Size=50; //栈的最大存储量 using namespace std; class Stack { private: Stack_Data Data[Stack_Size]; int Top; public: Stack(); //初始化栈 bool IsEmpty();//判空 bool IsFull();//判满 void Push(Stack_Data e);// 入栈 bool Pop(Stack_Data *e);//出栈 void GetTop(Stack_Data *e);//取栈顶元素 void Display();//展示函数 int TopLocation();//获取Top }; Stack::Stack() { Top=0; } bool Stack:: IsEmpty() { if(Top==0) { return true; } else return false; } bool Stack::IsFull() { if(Top==Stack_Size-1) { return true; } else return false; } void Stack::Push(Stack_Data e) { if(!IsFull()) { Top++; Data[Top]=e; } else cout<<"Stack OverFlow"<<endl; } bool Stack::Pop(Stack_Data *e) { if(!IsEmpty()) { *e=Data[Top]; Top--; return true; } else cout<<"Defeat"<<endl; return false; } void Stack::GetTop(Stack_Data *e) { if(!IsEmpty()) { *e=Data[Top]; } else cout<<"Defeat"<<endl; } void Stack::Display() { if(!IsEmpty()) { int temp=Top; while(temp) { cout<<"Data["<<temp<<"] "<<Data[temp]->data<<endl; temp--; } } else cout<<"False"<<endl; } int Stack::TopLocation() { return Top; }
2. #include "F:\A_Programer\DataStructure\Class\Queue.h"//包含队列操作 可参考我的其他随笔,有详细解释
1 #pragma once 2 #include<iostream> 3 using namespace std; 4 #define QMaxSize 100 //队列最大容量 5 typedef QElemType Queue_Data; //队列存储元素类型 6 typedef struct QueueNode //存储结构 7 { 8 Queue_Data data[QMaxSize]; 9 int front; 10 int rear; 11 int count; 12 }QueueNode,*Que; 13 class Queue 14 { 15 private: 16 Que Q; 17 public: 18 Queue();//初始化空的队列 19 bool EnterQueue(Queue_Data e);// 入队操作 20 bool DeleteQueue(Queue_Data *e);//出队操作; 21 bool IsEmpty();//判空 22 bool IsFull();//判满 23 void Display(); 24 }; 25 Queue::Queue() 26 { 27 Q=new QueueNode; 28 Q->front=0; 29 Q->rear=0; 30 Q->count=0; 31 } 32 bool Queue::EnterQueue(Queue_Data e) 33 { 34 if(!IsFull())//如果队列不满的话,增加元素 35 { 36 Q->count++;//计数器示数即为队列元素个数 37 Q->data[Q->rear]=e;//队尾指针所指空间即为下一个入队元素的存放位置 38 Q->rear=(Q->rear+1)%QMaxSize;//队列的精华 39 return true; 40 } 41 else return false; 42 } 43 bool Queue::DeleteQueue(Queue_Data *e) 44 { 45 if(!IsEmpty())//如果队列不为空,操作合法 46 { 47 *e=Q->data[Q->front];//队首指针指示位置即为要出队元素的位置 48 Q->front=(Q->front+1)%QMaxSize;//这里容易出错,切记是加一 49 Q->count--; 50 return true; 51 } 52 else return false; 53 } 54 bool Queue::IsEmpty() 55 { 56 if(Q->count==0)//判空最简单的一种计数器方法 57 return true; 58 else 59 return false; 60 } 61 bool Queue::IsFull() 62 { 63 if(Q->count==QMaxSize)//判满,计数器 64 return true; 65 else 66 return false; 67 } 68 void Queue::Display() 69 { 70 int b; 71 int p; 72 b=Q->count; 73 p=Q->front; 74 while(b) 75 { 76 cout<<Q->data[p]<<endl; 77 p=(p+1)%QMaxSize; 78 b--; 79 } 80 }
3.#include "F:\A_Programer\DataStructure\Class\Queue_1.h"//包含队列操作
#include<iostream> using namespace std; #define QMaxSize_1 100 //队列最大容量 typedef int QElemType_1; typedef QElemType_1 Queue_Data_1; //队列存储元素类型 typedef struct QueueNode1 //存储结构 { Queue_Data_1 data[QMaxSize_1]; int front; int rear; int count; }QueueNode_1,*Que_1; class Queue1 { private: Que_1 Q; public: Queue1();//初始化空的队列 bool EnterQueue(Queue_Data_1 e);// 入队操作 bool DeleteQueue(Queue_Data_1 *e);//出队操作; bool IsEmpty();//判空 bool IsFull();//判满 Queue_Data_1 GetRear(); void Display(); }; Queue1::Queue1() { Q=new QueueNode1; Q->front=0; Q->rear=0; Q->count=0; } bool Queue1::EnterQueue(Queue_Data_1 e) { if(!IsFull())//如果队列不满的话,增加元素 { Q->count++;//计数器示数即为队列元素个数 Q->data[Q->rear]=e;//队尾指针所指空间即为下一个入队元素的存放位置 Q->rear=(Q->rear+1)%QMaxSize_1;//队列的精华 return true; } else return false; } bool Queue1::DeleteQueue(Queue_Data_1 *e) { if(!IsEmpty())//如果队列不为空,操作合法 { *e=Q->data[Q->front];//队首指针指示位置即为要出队元素的位置 Q->front=(Q->front+1)%QMaxSize_1;//这里容易出错,切记是加一 Q->count--; return true; } else return false; } bool Queue1::IsEmpty() { if(Q->count==0)//判空最简单的一种计数器方法 return true; else return false; } bool Queue1::IsFull() { if(Q->count==QMaxSize_1)//判满,计数器 return true; else return false; } void Queue1::Display() { int b; int p; b=Q->count; p=Q->front; while(b) { cout<<Q->data[p]<<endl; p=(p+1)%QMaxSize_1; b--; } } Queue_Data_1 Queue1::GetRear() { return this->Q->data[this->Q->rear-1]; }
二.测试代码及输入文件
1. 测试代码(主函数)
int main() { freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); BiTree T; T.CreateBiTree_BF(); T.LevelOrderTraverse(); T.PreOrderTraverse(T.GetTree()); cout<<endl; T.InOrderTraverse(T.GetTree()); cout<<endl; T.PostOrderTraverse(T.GetTree()); cout<<endl; T.PreOrderTraverse_Thr(T.GetTree()); cout<<endl; T.InOrderTraverse_Thr(T.GetTree()); cout<<endl; T.PostOrderTraverse_Thr(T.GetTree()); cout<<endl; return 0; }
2. in.txt
1
1
1
2
1
0
3
1
1
4
0
1
5
0
0
6
0
0
7
0
0
3. 测试树的结构
三. 测试结果
out.txt
请输入1节点的值
此节点是否具有左子树
是 1
否 0
是否具有右子树
是 1
否 0
请输入2节点的值
此节点是否具有左子树
是 1
否 0
是否具有右子树
是 1
否 0
请输入3节点的值
此节点是否具有左子树
是 1
否 0
是否具有右子树
是 1
否 0
请输入4节点的值
此节点是否具有左子树
是 1
否 0
是否具有右子树
是 1
否 0
请输入5节点的值
此节点是否具有左子树
是 1
否 0
是否具有右子树
是 1
否 0
请输入6节点的值
此节点是否具有左子树
是 1
否 0
是否具有右子树
是 1
否 0
请输入7节点的值
此节点是否具有左子树
是 1
否 0
是否具有右子树
是 1
否 0
1节点的值1
2节点的值2
3节点的值3
4节点的值4
5节点的值5
6节点的值6
7节点的值7
1
2
4
7
3
5
6
4
7
2
1
5
3
6
7
4
2
5
6
3
1