山东大学数据结构实验五二叉树
包含方法:
1,二叉树的创建:根据层次遍历顺序创建;询问法创建,根据前序顺序补空法创建,根据先序顺序和中序顺序创建二叉树
2,二叉树的遍历:先序遍历,中序遍历,后序遍历,层次遍历
3,二叉树的节点计算
4,二叉树的叶子数计算
5,二叉树的深度计算
6,二叉树的节点存储空间的回收
头文件
#ifndef ERCHASHU_H
#define ERCHASHU_H
#endif
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#define ElemType char
#define Maxsize 100
using namespace std;
int count1=0;//计数用的,便于后面遍历输出的最后一个节点没有,
//二叉链表结构体
typedef struct Bnode{
ElemType data;
struct Bnode *lchild,*rchild;//*lchild左孩子指针 *rchild右孩子指针
}Bnode,*Btree;//Btree是个指针,指向节点
/*三叉链表结构体:增加了一个双亲指针,便于查找双亲节点
typedef struct Bnode{
ElemType data;
struct Bnode *lchild,*rchild,*parent ; // *lchild左孩子指针 *rchild右孩子指针 *parent 双亲指针,指向节点的双亲
}Bnode,*Btree;
*/
//队列的声明;队列从队尾入,从队头出
typedef struct SqQueue
{
Btree a[100];//Btree是个指针类型,所以这里a,是个存放指针的数组,每个指针指向不同的节点
int front,rear;//front队头 rear队尾
}SqQueue;
//队列初始化
void Initsqqueue(SqQueue &Q)
{
Q.front=Q.rear=0;
}
//指针入队,从队尾入
void push(SqQueue &Q, Btree &x)
{
Q.a[Q.rear]=x;//将指针x放入队列,Q.rear所指的空间
Q.rear=(Q.rear+1)%Maxsize;//Q.rear后移一位,当rear大于Maxsize-1时,则把元素存放到第0位(循环队列)
}
//指针出队,从队头出
void pop(SqQueue &Q)
{ //front向后移一位
Q.front=(Q.front+1)%Maxsize;
}
//询问法递归创建二叉树
void createtree (Btree &T){
char check;// 判断是否创建左右孩子
T=new Bnode;//创建根节点 ,T一开始是根节点,随着不断调用createtree (Btree &T)方法 T就是子根节点了
cout<<"请输入节点信息:"<<endl;
cin>>T->data;//输入节点数据
cout<<"是否添加"<<T->data<<"的左孩子?(Y/N)"<<endl;//询问是否创建T的左子树
cin>>check;
if(check=='Y')
//T=T->lchild;
//createtree(T);
createtree(T->lchild);//此步等价于上面的两步,递归调用createtree(T->lchild)方法创建左子树
else
T->lchild=NULL;
cout<<"是否添加"<<T->data<<"的右孩子?(Y/N)"<<endl;//询问是否创建T的右子树
cin>>check;
if(check=='Y')
createtree(T->rchild);//递归调用createtree(T->rchild)方法创建右子树
else
T->rchild=NULL;
}
//补空法递归创建二叉树
//补空法指若左子树或右子树为空时,则用特殊字符补空(如'#'),然后按照根,左子树,右子树的顺序,得到先序遍历(先访问根节点的访问顺序)序列,根据先序遍历序列创建二叉树
void Createtree2(Btree &T,char ch[])
{ //二叉树补空后,按照先序遍历序列输入字符,创建二叉树
int i=0;
if(ch[i]=='\0')
T=NULL;// if(ch=='\0')则创建一个空树
else
{
T=new Bnode; //T指向创建的节点
T->data=ch[i];
i++;
Createtree2(T->lchild, ch);//递归创建左子树
Createtree2(T->rchild, ch);//递归创建右子树
}
}
//生成节点
void Create_Node(Btree &T,char x){
//在指针T处生成一个新的节点,内容为x
T=new Bnode;
T->data=x;
下面两句话一定要加上,不要以为下面还要给他赋值,觉得加不加都行,血的教训!!
T->lchild=NULL;
T->rchild=NULL;
/*指针非法引用内存
ChainNode<T> *y1 = new ChainNode<T>;
y1->data = x;
ChainNode<T>y2
y2=y1->link; //y1->link没有赋值,y2就指向一个随机位置
cout << y2->data<< endl; //这句话就会输出一个不确定数值。
y2->data=123; //这句话就在给一个不确定位置的内存赋值,破坏内存原来数据,造成系统严重错误。
正确做法是申请到节点后,一定要给里面的指针赋值为空:
ChainNode<T> *y1 = new ChainNode<T>;
y1->link = null;*/
}
//层次顺序创建二叉树
void Create_Level(Btree &T,char b[])
{
Btree s;//定义一个指针s,因为T指针需要指向根节点,不能不断改变指向,否则就会找不到根节点
SqQueue Q;
Initsqqueue(Q);
int i=1;
if (b[0]!='\0')
{
Create_Node(T,b[0]);//先创建根节点,并把根节点的指针放入队列Q中,否则Q.front==Q.rear
push(Q,T);
}
while (Q.front!=Q.rear)
{
for(;i<strlen(b);)//把char数组中的元素放入二叉树每个节点的data中
{
s=Q.a[Q.front];//用指针s指向队列Q中的首元素
if (b[i]!='\0')
{
Create_Node(s->lchild,b[i]);//创建首元素的左孩子
push(Q,s->lchild);
i++;
}
if (b[i]!='\0')
{
Create_Node(s->rchild,b[i]);//创建首元素的右孩子
push(Q,s->rchild);
i++;
}
pop(Q);//当首元素的左右孩子创建完成,首元素出队列,首元素的左孩子,作为队首元素继续创建左右子树
}
if(i==strlen(b))//当数组b中元素不够时,而队列Q中还存放有节点,则跳出循环,否则循环会一直执行下去
break;
}
}
//二叉树先序遍历(先访问根节点或者子根节点的访问二叉树)/根在前面访问
//此方法规定访问顺序是:根节点/子根节点---左子树----右子树(也可以自己定义根节点/子根节点---右子树----左子树)
//先序遍历:访问根节点/子根节点,先序遍历左子树,左子树为空或已经全部遍历才可以遍历右子树
void preorder (Btree &T,int size){
if(T)//如果节点不为空才能不断调用preorder方法,如果节点为空则空操作
{
if(count1!=(size-1))//利用数组的长度size,和count1来限制二叉树的最后一个节点输出没有,
//因为先输出元素,后count1加1,所以最后一个元素输出时,count=size-1
cout<<T->data<<",";
else
cout<<T->data;//最后一个元素的输出没有,
count1++;//每次执行一次输出操作,count1加一
preorder (T->lchild,size);//然后访问左子树(当左子树为空或者全部遍历完再下一步访问右子树)
preorder (T->rchild,size);//当左子树为空或者全部遍历完后再访问右子树
}
}
// 二叉树中序遍历(先中序遍历左子树,然后访问根,再中序遍历右子树)/根在中间访问
//访问顺序是:左子树----根节点/子根节点---右子树
//中序遍历:中序遍历左子树,左子树为空或者已经全部遍历才可以访问根,中序遍历右子树
void inorder (Btree &T,int size)
{
if(T)//如果节点不为空才能不断调用inorder方法,如果节点为空则空操作
{
inorder(T->lchild,size);//先访问左子树
if(count1!=(size-1))//作用同上
cout<<T->data<<","; //当左子树为空或者已经全部遍历完后,再访问根
else
cout<<T->data;
count1++;
inorder(T->rchild,size);//然后访问右子树
}
}
//二叉树后序遍历(先后序遍历左子树,然后后序遍历右子树,再访问根)/根最后访问
//访问顺序:左子树---右子树 ----根节点/子根节点
//后序遍历:先后序遍历左子树,左子树为空或已全部遍历完,再遍历右子树,右子树为空或者全部遍历完,再访问根
void posorder(Btree &T,int size)
{
if(T)
{
posorder(T->lchild,size);//先后序遍历左子树
posorder(T->rchild,size);//左子树为空或已全部遍历完,再遍历右子树
if(count1!=(size-1))
cout<<T->data<<","; //左右子树都遍历完或者都为空再访问根
else
cout<<T->data;
count1++;
}
}
//层次遍历二叉树:先进先出
bool Leveltraverse(Btree &T,int size)
{
Btree p;//指针p,用来存放后面从队列中取出的指针元素
if(!T) //如果节点为空
return false;
SqQueue Q;//创建队列Q(先进先出),里面存放指针类型
Initsqqueue(Q);//初始化队列Q
push(Q,T);//将指针T(指向根节点)放入队列Q
while(Q.rear!=Q.front)//当队列不为空时
{
p= Q.a[Q.front];//取出队头元素作为当前节点
pop(Q);//队头元素出队
if(count1!=(size-1))
cout<<p->data<<",";
else
cout<<p->data;
count1++;
if(p->lchild)//如果节点P有左孩子,则左孩子入队
push(Q,p->lchild);
if(p->rchild)//如果节点P有右孩子,则右孩子入队
push(Q,p->rchild);
if(Q.rear==Q.front)
break;
}
return true;
}
//计算二叉树的深度,二叉树的深度等于二叉树左右子树的深度最大值+1
//可以用一个简单的二叉树递归计算一下
int Depth(Btree &T){
int m,n;
if(T==NULL)//如果二叉树为空则深度为0
return 0;
else{
m=Depth(T->lchild);//递归计算左子树的深度
n=Depth(T->rchild);//递归计算右子树的深度
if(m>n)//返回左右子树的深度最大值加1,
return m+1;
else
return n+1;
}
}
//计算二叉树的节点数,二叉树的节点数等于左子树节点数+右子树节点数+1(根节点)
int NodeCount(Btree &T)
{
if(T==NULL)
return 0;//如果为空树节点为0
else
return NodeCount(T->lchild)+NodeCount(T->rchild)+1;//左右子树节点数之和+1
}
//计算二叉树的叶子数(末端节点数)
int Leafcount(Btree &T)
{
if(T==NULL)//如果为空树叶子数=0
return 0;
else//即T不为空的时候
{
if(T->lchild==NULL&&T->rchild==NULL)//找到了叶子(末端节点)
return 1;
else//否则就继续递归直到找到叶子
return Leafcount(T->lchild)+Leafcount(T->rchild);//总叶子数等于左子树的叶子数+右子树的叶子数
}
}
//根据先序和中序顺序,创建二叉树
//char Preorder[]存放先序顺序,char Inorder[]存放中序顺序
// Preb,Pree分别表示前序遍历的起点和终点,
//Inb,Ine表示中续遍历的起点和终点
Btree createBTree(Btree &T,int Preb,int Pree,int Inb,int Ine,char Preorder[], char Inorder[])
{
if (Preb > Pree || Preb < 0 ||Inb >Ine||Inb<0||(Preb>=strlen(Preorder))||(Inb>=strlen(Inorder)))
//数组的起点不可以大于终点,起点也不可以小于0,起点也不可超界,否则空操作
return NULL;
T = new Bnode;
T->data = Preorder[Preb];//先序顺序的首位就是根节点,每找到一个根节点便将其数据存入需建立的树中
//T->lchild = NULL;//
//T->rchild = NULL;
int k = 0;//k记录每个根节点的下标(这里的根节点可以指整棵树的根节点,也可指左右子树的根节点)
for (int i = Inb; i < Ine + 1; i++)//通过该循环查找根节点在中序遍历中的位置,从而划分左右子树并确定左右子树元素个数
{
if (Preorder[Preb] == Inorder[i])
{
k = i;//在中序顺序中,根节点在第k个,则在Preorder先序顺序中,左子树从根节点+1,到第Preb + k - Inb个是左子树(k - Inb是左子树的节点个数)
break;//在中序中左子树对应的位置从开始位置到k-1;右子树同理
}
}
createBTree(T->lchild,Preb + 1, Preb + k - Inb, Inb, k - 1, Preorder, Inorder);//以根节点的左孩子作为新的根节点再去不断创建左右子树;
//起点和终点的作用是为了划分左子树和右子树,根据起点的中的位置,找出左子树和右子树在先序顺序和中序顺序的位置范围
createBTree(T->rchild,Preb + k - Inb + 1, Pree, k + 1, Ine, Preorder, Inorder);
//不断递归调用createBTree方法,缩小左右子树节点的范围,即把这些根节点的左右子树节点再分给子根节点的左右子树左右子树
return T;
}
//回收二叉树节点的存储空间
void DeleteNode(Btree &T){
if(T)
{
//按后序方式遍历逐一回收每一个节点
DeleteNode(T->lchild);//递归回收左子树的节点
DeleteNode(T->rchild);//递归回收右子树的节点
delete T;//不断通过递归,找到叶子节点,删除叶子节点
T=NULL;
}
}
主方法
#include<iostream>
#include"erchashu.h"
using namespace std;
int main()
{
Btree T;
//createtree (T);//询问法递归创建二叉树
cout<<"Input1"<<endl;
char b[100];
cin>>b;
int size1=strlen(b);
Create_Level(T,b);//层次顺序创建二叉树
cout<<"Output1"<<endl;
//DeleteNode(T);回收二叉树节点的存储空间
//cout<<Leafcount(T)<<endl;//输出二叉树的叶子数
preorder (T,size1);//先序遍历二叉树
count1=0;//每次遍历后要把count1恢复为0
cout<<endl;
inorder (T,size1);//中序遍历二叉树
count1=0;
cout<<endl;
posorder(T,size1);//后序遍历二叉树
//Leveltraverse(T,size1);//中序遍历二叉树
count1=0;
cout<<endl;
cout<<NodeCount(T)<<endl;//计算节点数
cout<<Depth(T)<<endl;//计算深度
cout<<"Input2"<<endl;
int size2;
count1=0;
char Preorder[100];
char Inorder[100];
cin>>Preorder;//输入先序顺序
cin>>Inorder;//输入中序顺序
size2=strlen(Inorder);
createBTree(T,0,strlen(Preorder)-1,0,strlen(Inorder)-1,Preorder, Inorder);//根据先序顺序和中序顺序创建二叉树
cout<<"Output2"<<endl;
posorder(T,size2);//后续遍历二叉树
count1=0;
cout<<endl;
Leveltraverse(T,size2);//层次遍历二叉树
count1=0;
cout<<endl;
cout<<"End"<<endl;
return 0;
}
当你代码错了,自己却一直没发现时:“一定是你错了,只是不知道自己错在哪里了,越是找不到的错误,越是你认为不会犯的错误”。(来自数据结构实验大纲)
(共勉)