目录
二叉树的基本操作
BinT.h(必须先要有这个头文件,实验才能成功)
//屏幕提示后,从键盘输入个数和随机数种子,在数组elem中生成指定个数的数据,供其他程序使用,0表示数据结束
void init0(int list[],int &n)//n为数组中的数据个数
{
int i;
while (1)
{
cout << "输入数据个数(0-" << max-1 << "):" << flush;
cin >> n;
if (n >= 0 && n <= max-1)
break;
cout << endl;
}
while (1)
{
cout << "输入随机数种子(0-32767):" << flush;
cin >> i;
if (i >= 0 && i <= 32767)
break;
cout << endl;
}
srand(i); //指定随机数种子,相同的种子将产生相同的数据序列
rand();
for (i = 0; i < n; i++)
{
list[i] = rand() % 999 +1;
}
list[n] = 0;
}
void getWidth(BiTNode *Tree, int depth, int shift, char map[max*2][max*2])
{
int i;
if (Tree->left != NULL)
{
getWidth(Tree->left, depth+1, shift, map);
Tree->tem1 = Tree->left->tem1 + Tree->left->tem2 + 1;
for (i=(Tree->left->tem1+shift)*2; i<shift*2; i=i+2)
{
map[depth*2+1][i]='-';
map[depth*2+1][i+1]='-';
}
}
else Tree->tem1 = 0;
if (Tree->right != NULL)
{
getWidth(Tree->right, depth+1, shift+Tree->tem1+1, map);
Tree->tem2 = Tree->right->tem1 + Tree->right->tem2 + 1;
}
else Tree->tem2 = 0;
for (i=shift*2; i<(Tree->tem1+shift)*2; i++)
map[depth*2][i]=' ';
map[depth*2][(Tree->tem1+shift)*2]=(char)(Tree->data / 1000 +48);
map[depth*2][(Tree->tem1+shift)*2+1]=(char)(Tree->data / 100 % 10 +48);
map[depth*2][(Tree->tem1+shift)*2+2]=(char)(Tree->data / 10 % 10 +48);
map[depth*2][(Tree->tem1+shift)*2+3]=(char)(Tree->data %10 +48);
if (Tree->data<1000)
{
map[depth*2][(Tree->tem1+shift)*2]=' ';
if (Tree->data<100)
{
map[depth*2][(Tree->tem1+shift)*2+1]=map[depth*2][(Tree->tem1+shift)*2+2];
map[depth*2][(Tree->tem1+shift)*2+2]=map[depth*2][(Tree->tem1+shift)*2+3];
map[depth*2][(Tree->tem1+shift)*2+3]=' ';
if (Tree->data<10)
map[depth*2][(Tree->tem1+shift)*2+1]=' ';
}
}
if (Tree->left != NULL)
{
map[depth*2+1][(Tree->left->tem1+shift)*2+1]=(char)0xa9;
map[depth*2+1][(Tree->left->tem1+shift)*2+2]=(char)0xb0;
map[depth*2+1][(Tree->tem1+shift)*2+1]=(char)0xa9;
map[depth*2+1][(Tree->tem1+shift)*2+2]=(char)0xbc;
for (i=(Tree->left->tem1+shift)*2+3; i<(Tree->tem1+shift)*2; i=i+2)
{
map[depth*2+1][i]=(char)0xa9;
map[depth*2+1][i+1]=(char)0xa4;
}
}
if (Tree->right != NULL)
{
map[depth*2+1][(Tree->tem1+shift)*2+1]=(char)0xa9;
map[depth*2+1][(Tree->tem1+shift)*2+2]=(char)0xb8;
map[depth*2+1][(Tree->tem1+Tree->right->tem1+shift)*2+3]=(char)0xa9;
map[depth*2+1][(Tree->tem1+Tree->right->tem1+shift)*2+4]=(char)0xb4;
for (i=(Tree->tem1+shift)*2+3; i<(Tree->tem1+Tree->right->tem1+shift)*2+2; i=i+2)
{
map[depth*2+1][i]=(char)0xa9;
map[depth*2+1][i+1]=(char)0xa4;
}
}
if (Tree->left != NULL && Tree->right != NULL)
{
map[depth*2+1][(Tree->tem1+shift)*2+1]=(char)0xa9;
map[depth*2+1][(Tree->tem1+shift)*2+2]=(char)0xd8;
}
}
//生成文件Map.txt,显示以Tree为根指针的二叉树
void showBinTree(BiTNode *Tree)
{
char map[max*2][max*2];
FILE *f;
int i,j,k;
f=fopen("Map.txt","w");
if (Tree == NULL)
{
fprintf(f,"空树");
fclose(f);
return;
}
for (i=0; i<max*2; i++)
for (j=0; j<max*2; j++)
map[i][j]=' ';
getWidth(Tree,0,0,map);
for (i=0; i<max*2; i++)
{
k=max*2-1;
while (k>=0 && map[i][k]==' ')
k--;
for (j=0; j<=k; j++)
fprintf(f,"%c",map[i][j]);
fprintf(f,"\n");
// if (k<0)
// break;
}
fclose(f);
}
//供init1O调用的释放函数
void release(BiTNode *Tree)
{
if (Tree==NULL)
return;
release(Tree->left);
release(Tree->right);
free(Tree);
}
//供init1调用的生成函数
BiTNode *Generate(int n)
{
int nl;
if (n==0)
return NULL;
BiTNode *P = new BiTNode;
P->data = rand() % 999 +1;
nl=rand() % (n);
P->left = Generate(nl);
P->right = Generate(n-1-nl);
return P;
}
//屏幕提示后,从键盘输入个数和随机数种子,以Tree为根指针,生成指定结点个数的二叉树,供其他程序使用,同时生成文件Map.txt,显示这课二叉树
BiTNode *init1()
{
int i,n;
while (1)
{
cout << "输入数据个数(0-" << max-1 << "):" << flush;
cin >> n;
if (n >= 0 && n <= max-1)
break;
cout << endl;
}
N=n;//保存随机生成的二叉树的结点个数
while (1)
{
cout << "输入随机数种子(0-32767):" << flush;
cin >> i;
if (i >= 0 && i <= 32767)
break;
cout << endl;
}
srand(i); //指定随机数种子,相同的种子将产生相同的数据序列
rand();
release(Tree);
return Generate (n);
}
//void init0(int list[],int &n)//n为数组中的数据个数
//修改BinT.h后,必须编译一下 BinT.h,才能使在主程序在调用BinT.h的函数时也会有相应的变化,
//如果修改后不编译一下BinT.h,主程序就可能会编译出错,或者没有相应改变,导致无法完成相应功能
/*
源文件名:P3.cpp
功能:二叉树操作
*/
//空树默认遍历成功,显示空树
#include <iostream>
using namespace std;
#include <iomanip>
#include <conio.h>
#include <stdio.h>
#include <process.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#define max 100
#define TRUE 1
#define FALSE 0
struct BiTNode //二叉树结点类型
{
int data; //数据
int tem1,tem2; //辅助数据(实习题不用)
BiTNode *left; //左子树指针
BiTNode *right; //右子树指针
};
//定义链栈结点类型
typedef struct stackNode
{
BiTNode *data;
struct stackNode *next;
}stackNode;
//定义链栈 用于非递归先序遍历 (无头结点的链栈)
typedef struct linkStack
{
stackNode *top;//栈顶指针
int size;//栈中元素个数
}linkStack;
//链栈操作函数
void initStack(linkStack* &s) //构造一个空栈s
{
s=new linkStack;
s->top=NULL;
s->size=0;
}
int Push(linkStack* &s,BiTNode* e)//入栈
{
if(s == NULL)
{
return FALSE;
}
stackNode *q;
q=new stackNode;
q->data=e;
q->next=s->top;
s->top=q;
s->size++;//入栈后,元素个数要加一
return TRUE;
}
int Pop(linkStack* &s,BiTNode* &e)//出栈
{
if(s == NULL)
{
return FALSE;
}
if(s->top == NULL)
{
return FALSE;
}
stackNode *q;
e=s->top->data;
q=s->top;
s->top=q->next;
free(q);
s->size--;
return TRUE;
}
bool emptyStack(linkStack *s)//栈是否为空
{
if(s->top)//链栈存在且非空
{
return FALSE;//非空
}
else
{
return TRUE;//空
}
}
//定义队列结点类型
typedef struct qNode
{
BiTNode *data;
struct qNode *next;
}qNode;
//定义队列 (有头结点的队列)
typedef struct
{
qNode *front;//队头
qNode *rear;//队尾
}linkQueue; //用于层序遍历
//队列操作函数
int initQueue(linkQueue &Q)//构造空队列
{
Q.front=Q.rear=new qNode;
if( !Q.front )
return FALSE;//存储分配失败
Q.front->next=NULL;
return TRUE;
}
int enQueue(linkQueue &Q,BiTNode* e)//入队
{
qNode *p;
p=new qNode;
if( !p )
return FALSE;//存储分配失败
p->data=e;
p->next=NULL;
Q.rear->next=p;//将入队的元素插到队尾
Q.rear=p;//队尾指针后移
return TRUE;
}
int deQueue(linkQueue &Q,BiTNode* &e)//出队
{
if(Q.front == Q.rear)
return FALSE;//空队列无法出队
qNode *p=Q.front->next;//p指向首元结点
e=p->data;//出队的数据保存在e中
Q.front->next=p->next;//删除首元结点
if(Q.rear == p) //如果首元结点就是最后一个结点,出了队,队列就空了
Q.rear=Q.front; //队尾对头就都指向头结点了
free(p);//释放被删除的结点
return TRUE;
}
int emptyQueue(linkQueue Q)
{
if(Q.front == Q.rear)
return TRUE;//队空
else
return FALSE;//非空
}
BiTNode *Tree; //本程序操作的二叉树根指针
int elem[max]; //存放原始数据
//从键盘输入个数和随机数种子,在数组elem中生成指定个数的数据,供其他程序使用,0表示数据结束
void init0(int list[],int &n);
//在本程序所在的位置生成文本文件Map.txt,其中显示以Tree为根指针的二叉树
void showBinTree(BiTNode *Tree);
//从键盘输入个数和随机数种子,以Tree为根指针,生成指定结点个数的二叉树,供其他程序使用
int N;//N在BinT.h中init1函数中被赋值了n,也就是生成二叉树的结点个数
BiTNode *init1();
void getWidth(BiTNode *Tree, int depth, int shift, char map[max*2][max*2]);
//供init1O调用的释放函数
void release(BiTNode *Tree);
//供init1调用的生成函数
BiTNode *Generate(int n);
int count = 0;//计算树中叶子结点数
//在 init0函数中,把数据个数n赋值给N,N就是随机数序列的数据个数了
void createBiTree(BiTNode* &T);//创建二叉树
int visit(int e);//显示元素值
int preOrder(BiTNode* T);//先序遍历
int inOrder(BiTNode* T);//中序遍历
int postOrder(BiTNode* T);//后序遍历
int leafCount(BiTNode* T);//计算叶子结点数目
int getDeep(BiTNode* T );//求二叉树深度
void exchangeLR(BiTNode* &T);//交换左右子树
int insertSortTree(BiTNode* &T,int e);//插入二叉排序树
BiTNode* searchSortTree(BiTNode* T,int e);//查找二叉排序树
void createBiSortTree(BiTNode* &T);//使用数组elem中的随机数序列(以0表示结束,不包括0),生成以Tree为根指针的二叉排序树
int delSortTree(BiTNode* &,int e);//删除二叉排序树中的结点
int noRec_preOrder(BiTNode* T);//非递归先序遍历
int levelOrder(BiTNode* T);//层序遍历,逐层输出
int Save_preOrder(BiTNode* T); //生成并保存先序输出序列
int Save_inOrder(BiTNode* T);//生成并保存中序输出序列
int i_pre,i_in; //数组pre 和in中的下标
int pre[max]={0},in[max]={0};//分别保存先序中序序列
typedef struct
{
int weight;//结点权重
int parent,lchild,rchild;//结点的双亲、孩子在数组中的位置
}HTNode,*HuffmanTree; //动态分配数组储存哈夫曼树
typedef char** HuffmanCode;//动态分配数组存储哈夫曼编码表
HuffmanTree HT;//哈夫曼树
HuffmanCode HC;//哈夫曼编码
int pathLength[max]={0};//保存哈夫曼树从根到叶子的路径长度
void Select(HuffmanTree HT,int n,int &s1,int &s2);//在HT[0..n]中选择parent为0且weight最小的两个结点,其下标分别为s1,s2
int HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n);//生成赫夫曼树,以及赫夫曼编码,计算平均带权径长度
BiTNode* recoverTree(int *pre,int *in,int size);//根据先序、中序序列恢复出二叉树
//主函数,显示功能菜单(包括生成二叉树、显示二叉树),键盘选择后执行对应功能
int main()
{
int i;//控制循环
int e;//保存输入的要查找或删除二叉排序树结点的值
int createdFlag=0;//0表示未创建二叉树,1表示创建了二叉树
int sortTreeFlag=0;//0表示未创建二叉排序树,1表示创建了二叉排序树
int n;//数组elem中随机数序列的数据个数 ,以便生成生成哈夫曼树时可以明确知道字符个数( init0(int list[],int &n))
int choice;
while (1)
{
system("cls");
cout << "\n\n\n\n";
cout << "\t 二叉树的基本操作 \n";
cout << "\t======================================";
cout << "\n\n";
cout << "\t 1:创建二叉树 \n";
cout << "\t 2:先序遍历 \n";
cout << "\t 3:中序遍历 \n";
cout << "\t 4:后序遍历 \n";
cout << "\t 5:求二叉树叶子结点数 \n";
cout << "\t 6:求二叉树的深度 \n";
cout << "\t 7:交换全部结点的左右子树 \n";
cout << "\t 8:使用数组elem中的随机数序列,生成以Tree为根指针的二叉排序树\n";
cout << "\t 9:在以Tree为根指针的二叉排序树中查找结点 \n";
cout << "\t 10:从以Tree为根指针的二叉排序树中删除结点(适用各种位置的结点) \n";
cout << "\t 11:非递归先序遍历以Tree为根指针的二叉树 \n";
cout << "\t 12:逐层从左到右输出各结点的数据(层序遍历) \n";
cout << "\t 13:使用数组elem中的随机数序列作为结点的权重,生成赫夫曼树,以及赫夫曼编码,计算平均带权径长度 \n";
cout << "\t 14:(1)随机生成二叉树\n\t (2)生成并保存先序、中序输出序列\n\t (3)按照保存的输出序列恢复出二叉树\n\t (4)生成先后序输出序列\n";
cout << "\t 0:退出 \n";
cout << "\n";
cout << "\t请选择:" << flush;
scanf( "%d",&choice );
system("cls");
switch(choice)
{
case 1:
createBiTree(Tree);
printf( "二叉树创建成功!可在Map.txt中查看创建的二叉树。\n" );
showBinTree(Tree);
createdFlag=1;//创建了二叉树
system( "pause" );
break;
case 2:
if(createdFlag == 1)
{
if(Tree == NULL)
printf( "空树!" );
preOrder(Tree);
printf( "\n先序遍历成功!\n" );
system( "pause" );
break;
}
printf( "请先创建一颗二叉树!\n" );//未创建二叉树时,有相应提示
system( "pause" );
break;
case 3:
if(createdFlag == 1)
{
if(Tree == NULL)
printf( "空树!" );
inOrder(Tree);
printf( "\n中序遍历成功!\n" );
system( "pause" );
break;
}
printf( "请先创建一颗二叉树!\n" );//未创建二叉树时,有相应提示
system( "pause" );
break;
case 4:
if(createdFlag == 1)
{
if(Tree == NULL)
printf( "空树!" );
postOrder(Tree);
printf( "\n后序遍历成功!\n" );
system( "pause" );
break;
}
printf( "请先创建一颗二叉树!\n" );//未创建二叉树时,有相应提示
system( "pause" );
break;
case 5:
if(createdFlag == 1)
{
if(Tree == NULL)
printf( "空树!\n" );
count = 0;
printf( "该树的叶子结点数为:%d\n",leafCount(Tree) );
system( "pause" );
break;
}
printf( "请先创建一颗二叉树!\n" );//未创建二叉树时,有相应提示
system( "pause" );
break;
case 6:
if(createdFlag == 1)
{
if(Tree == NULL)
printf( "空树!\n" );
printf( "该树的深度为:%d\n",getDeep(Tree) );
system( "pause") ;
break;
}
printf( "请先创建一颗二叉树!\n" );//未创建二叉树时,有相应提示
system( "pause" );
break;
case 7:
if(createdFlag == 1)
{
if(Tree)//树非空
{
exchangeLR(Tree);
printf( "左右子树交换成功!可在Map.txt中查看交换后的二叉树。\n" );
showBinTree(Tree);
}
else//树为空
printf( "树为空!无法交换左右子树!\n" );
system( "pause" );
break;
}
printf( "请先创建一颗二叉树!\n" );//未创建二叉树时,有相应提示
system( "pause" );
break;
case 8:
init0(elem,n);
if(elem[0] == 0)//elem为空表,无法生成二叉排序树
{
printf( "元素个数为0,无法生成二叉排序树!\n已经创建过的二叉排序树不会被改变\n" );
system( "pause" );
break;
}
printf( "生成的随机数序列如下:\n" );
for(i=0;elem[i];i++)
{
printf( "%d\t",elem[i] );
}
system( "pause" );
createBiSortTree(Tree);//生成以Tree为根指针的二叉排序树
sortTreeFlag=1;//创建了二叉排序树
createdFlag=1;//也就是创建了二叉树
printf( "二叉排序树生成成功!\n中序遍历显示该二叉排序树:\n" );
inOrder(Tree);
showBinTree(Tree);
printf( "可在Map.txt中查看生成的二叉排序树!\n" );
system( "pause" );
break;
case 9:
if(sortTreeFlag == 1)
{
if(Tree == NULL)
{
printf( "树为空!无法查找!\n" );
system( "pause" );
break;
}
printf( "请输入要查找的元素值:" );
scanf( "%d",&e );
if( searchSortTree(Tree,e) )
printf( "成功查找到数据:%d\n",e );
else
printf( "二叉排序树中没有该元素!查找失败!\n" );
system( "pause" );
break;
}
printf( "二叉排序树未创建!请创建后再试!\n" );//未创建二叉排序树时,有相应提示
system( "pause" );
break;
case 10:
if(sortTreeFlag == 1)
{
if(Tree == NULL)
{
printf( "树为空!无法删除!\n" );
system( "pause" );
break;
}
printf( "请输入要删除的结点值:" );
scanf( "%d",&e );
if( delSortTree(Tree,e) )
printf( "删除成功!\n" );
else
printf( "二叉排序树中没有该元素!删除失败!\n" );
printf( "中序遍历显示经过删除操作后的二叉排序树:\n" );
inOrder(Tree);
showBinTree(Tree);
printf( "可在Map.txt中查看经过删除操作后的二叉排序树!\n" );
system( "pause" );
break;
}
printf( "二叉排序树未创建!请创建后再试!\n" );//未创建二叉排序树时,有相应提示
system( "pause" );
break;
case 11:
if(createdFlag == 1)
{
noRec_preOrder(Tree);
printf("\n非递归先序遍历成功!\n");
system( "pause" );
break;
}
case 12:
if(createdFlag == 1)
{
levelOrder(Tree);
printf( "层序遍历成功!\n" );
system( "pause" );
break;
}
printf( "请先创建一颗二叉树!\n" );//未创建二叉树时,有相应提示
system( "pause" );
break;
case 13:
init0(elem,n);
printf( "随机数序列即各结点权重如下:\n" );
printf( "下标\t权重\n" );
for(i=0;elem[i];i++)
{
printf( " %2d\t%d\n",i,elem[i] );
}
system( "pause" );
HuffmanCoding(HT,HC,elem,n);
system( "pause" );
break;
case 14:
printf( "(1)随机生成二叉树\n" );
Tree=init1();
createdFlag=1;//创建了二叉树
if(N == 0)
{
showBinTree(Tree);
printf( "元素个数为0,生成的二叉树为空树!请重试!\n(Map.txt中的树也变成了空树)\n" );
system( "pause" );
break;
}
showBinTree(Tree);
printf( "二叉树生成成功!可在Map.txt中查看生成的二叉树!\n" );
system( "pause" );
i_pre=0,i_in=0;//执行一次后,再执行一次
for(i=0;pre[i];i++)//下标和数组元素都要归零
pre[i]=0;
for(i=0;in[i];i++)
in[i]=0;
printf( "(2)生成并保存先序、中序输出序列\n" );
printf( "先序序列如下:\n" );
Save_preOrder(Tree);
printf( "\n已经保存在数组pre中!\n" );
printf( "输出数组元素:\n" );
for(i=0;pre[i];i++)
printf("%d\t",pre[i]);
printf( "\n中序序列如下:\n" );
Save_inOrder(Tree);
printf( "\n已经保存在数组in中!\n" );
printf( "输出数组元素:\n" );
for(i=0;in[i];i++)
printf("%d\t",in[i]);
system( "pause" );
release(Tree);
Tree=NULL;
printf("生成的二叉树已被清空!可在Map.txt中查看清空后的树!\n");
showBinTree(Tree);
system( "pause" );
printf( "(3)按照保存的输出序列恢复出二叉树\n" );
Tree=recoverTree(pre,in,N);
showBinTree(Tree);
printf( "根据先序序列和后序序列恢复二叉树成功!\n可在Map.txt中查看恢复后的二叉树!\n" );
printf( "对恢复后的二叉树进行先序遍历:\n" );
preOrder(Tree);
printf( "\n对恢复后的二叉树进行中序遍历:\n" );
inOrder(Tree);
system( "pause" );
printf( "(4)生成后序输出序列\n" );
printf("根据恢复的二叉树生成的后序序列如下:\n");
postOrder(Tree);
system( "pause" );
break;
default :
printf ("输入有误,请重新输入!\n" );
system( "pause" );
break;
case 0:
exit(0);
}
}
return 0;
}
#include "BinT.h"
//创建二叉树,如果刚开始就输入0,就为空树
void createBiTree(BiTNode* &T)
{
int x;
printf( "请按先序次序输入二叉树的结点值(0表示空节点):" );
scanf( "%d",&x );
if(x == 0)
T = NULL;
else
{
T = new BiTNode;
T->data = x;
createBiTree(T->left);
createBiTree(T->right);
}
}
//先序、中序、后序遍历以Tree为根指针的二叉树
int visit(int e)//显示元素值
{
printf( "%d\t",e );
return TRUE;
}
int preOrder(BiTNode* T)//先序遍历
{
if(T)
{
if(visit(T->data))//访问根结点
if(preOrder(T->left))
if(preOrder(T->right))
return TRUE;
return FALSE;
}
else
return TRUE;//如果T为空树,就直接返回
}
int inOrder(BiTNode* T)//中序遍历
{
if(T)
{
if(inOrder(T->left))
if(visit(T->data))//访问根结点
if(inOrder(T->right))
return TRUE;
return FALSE;
}
else
return TRUE;//如果T为空树,就直接返回
}
int postOrder(BiTNode* T)//后序遍历
{
if(T)
{
if(postOrder(T->left))
if(postOrder(T->right))
if(visit(T->data))//访问根结点
return TRUE;
return FALSE;
}
else
return TRUE;//如果T为空树,就直接返回
}
int leafCount(BiTNode* T)//计算叶子结点数
{
if(T)
{
if( !(T->left) && !(T->right) ) //如果左右孩子为空,叶子结点加一
return ++count;
leafCount(T->left);
leafCount(T->right);
}
else
return count;//T为空,返回
}
int getDeep(BiTNode* T)//求二叉树深度
{
int a,b;
if(T)//T非空,就继续对左右子树求深度
{
a=getDeep(T->left);
b=getDeep(T->right);
return (a>b)?(a+1):(b+1);//返回(较深子树子树的深度+1)
}
else
return 0;//T空。深度为0
}
void exchangeLR(BiTNode* &T)//交换左右子树
{
if(T)//如果T非空就交换左右子树
{
BiTNode *p;//交换媒介
p=T->left;
T->left=T->right;
T->right=p;//交换左右子树
exchangeLR(T->left);
exchangeLR(T->right);//对左右子树进行相同的交换操作
return;
}
else
return;//T为空,直接返回
}
//要使用多种操作的递归时,最好把各个操作单独写一个函数,进行自身的递归,把所有操作写在一个函数里面的话,难以理解,很难写出来
//使用数组elem中的随机数序列(以0表示结束,不包括0),生成以Tree为根指针的二叉排序树
int insertSortTree(BiTNode* &T,int e)//插入二叉排序树
{
if( searchSortTree(T,e) )
return FALSE;// 如果e能在T中查到,就不用插入了
if(!T)//如果T为空,就找到了插入位置
{
T=new BiTNode;
T->data=e;
T->left=T->right=NULL;
return TRUE;
}
else//T非空
if(e < T->data)
insertSortTree(T->left,e);//比根结点小,就往左子树插入
else//比根结点大,往右子树插入
insertSortTree(T->right,e);
}
void createBiSortTree(BiTNode* &T)
{
int i=0;//数组下标
//第一个数据填入根结点
T=new BiTNode;
T->data=elem[i];
T->left=T->right=NULL;
i++;
while(1)
{
if(elem[i])//若下标为i的元素非0
{
insertSortTree(T,elem[i]); //将非零元素插入到T中
i++;
}
else
return;//elem中全部数据都插入到二叉排序树中时,函数返回
}
}
//在以Tree为根指针的二叉排序树中查找结点
BiTNode* searchSortTree(BiTNode* T,int e)//查找二叉排序树 ,返回所在结点的指针
{
if(T == NULL)
return NULL;//T为空树,查找失败
else//T非空
if(T->data == e)
return T;//查找成功
else
if(e < T->data)
return searchSortTree(T->left,e);//小于根结点,查左子树
else//大于根结点,查右子树
return searchSortTree(T->right,e);
}
//从以Tree为根指针的二叉排序树中删除结点(适用各种位置的结点)
int delSortTree(BiTNode* &T,int e)//删除二叉排序树中的结点
{
BiTNode *q,*p,*s,*f;
if( q = searchSortTree(T,e) )//如果在排序树中能找到e,就要进行删除,q就指向要删除的结点
{
if( q->left && q->right )//被删结点q左右子树均不空
{
p=q;
s=q->left;
while(s->right)//在左子树找最右的结点
{
p=s;
s=s->right;
}//循环结束后,p是最右结点s的双亲结点(s->rigth为空)
q->data=s->data;//结点s中的数据顶替被删结点q中的元素
if(q == p)//q的左子树的根结点就是左子树中最右的结点 (左子树没有右孩子)
p->left=s->left;
else //最右的结点没有右孩子
p->right=s->left;//最右结点应当被删除,它仅有的子树的左子树应该顶替它的位置
free(s);//释放s
}
else
if( q->left==NULL && q->right==NULL )//如果被删除的结点的左右孩子都为空,也就是被删除的结点是叶子节点,要删除q,就要找到q的双亲,使双亲指向q的指针置空
{
p=T;//从根结点开始找q的双亲
f=NULL;//f最终应指向q的双亲,q既为根结点也为叶子节点时,f就为空
while(p)//利用循环找q的双亲
{
if(p->data == q->data)
break;
f=p;//结点f一直为结点p的双亲结点
if(q->data < p->data)
p=p->left;//q的data比p的data小,就在p的左子树继续找
else
p=p->right;//q的data比p的data大,就在p的右子树中找
}//循结束后,f指向q的双亲
if(f == NULL)//f为空,表示q既为根结点也为叶子节点(树中只有一个结点),此时,删除后,T就该为空,也就是空树
{ //(f指向q的双亲)
T=NULL;
free(q);//释放被删除的结点
}
else//被删除的结点不是根结点时
if(f->left == q)//f的左孩子为q
{
f->left=NULL;
free(q);
}
else//f的右孩子为q
{
f->right=NULL;
free(q);
}
}
else//q的左右孩子一个为空,一个不为空
if(q->left == NULL)//左空右不空,删除就是用q的右孩子来顶替掉
{
p=q->right;//p指向q的右孩子
q->data=p->data; //删除就是用q的右孩子的结点值来顶替掉q的结点值
q->left=p->left;//相应的左右孩子也要替换
q->right=p->right;
free(p); //释放q原本的右孩子
}
else//右空左不空
{
p=q->left;//p指向q的左孩子
q->data=p->data; //删除就是用q的左孩子的结点值来顶替掉q的结点值
q->left=p->left;//相应的左右孩子也要替换
q->right=p->right;
free(p); //释放q原本的左孩子
}
return TRUE; //结束该函数
}
else
return FALSE;//找不到就无法删除
}
int noRec_preOrder(BiTNode* T)//非递归先序遍历
{
if( !T )//空树,程序退出
{
printf( "空树!\n" );
return FALSE;
}
linkStack* s;
initStack(s);
Push(s,T);
while( !emptyStack(s) )//栈非空时
{
BiTNode *q;
Pop(s,q);//指针出栈
visit(q->data);//显示数据
if(q->right != NULL)
Push(s,q->right);
if(q->left != NULL)//如果q的左孩子非空,那么下一个遍历到的结点应该时q的左孩子指向的结点
Push(s,q->left);//左孩子比右孩子后进栈,也就是左孩子比右孩子先出栈(根左右遍历)
}
return TRUE;
}
//对以Tree为根指针的二叉树,从根结点开始,逐层从左到右输出各结点的数据
//提示:用数组 BiTNode *queue[max] 构成队列,利用这个队列实现功能
int levelOrder(BiTNode* T)
{
if( !T )//空树,程序退出
{
printf( "空树!\n" );
return FALSE;
}
linkQueue currentLevel,nextLevel;//currentLevel用于存储当前层的结点,nextLevel用于存储下一层的结点
initQueue(currentLevel);
initQueue(nextLevel);
enQueue(currentLevel,T);//根指针进入当前层队列
while( !emptyQueue(currentLevel) )//当前层队列非空
{
BiTNode *currNode;
deQueue(currentLevel,currNode);//出队
if(currNode)//如果出队指针非空,就显示数据,并把它的左右孩子插到下一层队列
{ //出队指针非空,但是出队指针的左右孩子可能为空,空指针入队后,再出队,就不用显示数据了
visit(currNode->data);//显示数据
enQueue(nextLevel,currNode->left);
enQueue(nextLevel,currNode->right);
}
if( emptyQueue(currentLevel) ) //当前层队列为空时,表示当前层已经遍历完了,可以打印换行了
{
printf( "\n" );
while( !emptyQueue(nextLevel) )//把下一层队列的元素按顺序全部移到当前层队列
{
BiTNode *p;
deQueue(nextLevel,p);
enQueue(currentLevel,p);
}
}
}
return TRUE;
}
//使用数组elem中的随机数序列(以0表示结束,不包括0),生成赫夫曼树,计算平均带权路径长度
void Select(HuffmanTree HT,int n,int &s1,int &s2)//在HT[0..n]中选择parent为0且weight最小的两个结点,其下标为s1,s2
{ //利用简单选择排序的思想找出两个weight最小结点的下标
int i,j,k; //但是并不进行排序,只是选择出最小的两个
int min;
for(i=0;i<=n;i++)//循环结束后,i就是第一个parent为-1的结点
{
if(HT[i].parent == -1)
{
min=i;
break;
}
} //先假设第一个parent为-1的结点就是 weight最小的
for(j=i+1;j<=n;j++)
{
if(HT[j].parent == -1 && HT[j].weight < HT[min].weight)//遇到parent为-1,且更小的结点时,就把更小的结点的下标赋值给min
{
min=j;
}
}//循环结束后,min就是parent为-1的weight最小的结点
s1=min;//s1为parent为-1的weight最小的结点
//下面找出parent为-1的第二小的结点的下标赋给s2
for(k=i;k<=n;k++)//找出第一个parent为-1且weight不小于HT[s1].weight的结点,假设它就是第二小的结点
{
if(HT[k].parent == -1 && k != s1)//(s1已经是最小结点的下标,所以weight不小于s1的weight就是下标不等于s1)
{
min=k;
break;
}
}
for(j=i;j<=n;j++)
{
if(HT[j].parent == -1 && j != s1 && HT[j].weight < HT[min].weight)
{
min=j;
}
}//循环结束后,现在的min就是parent为-1的weight第二小的结点
s2=min;// s2为parent为-1的weight第二小的结点
}
int HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n)//生成赫夫曼树,以及赫夫曼编码,计算平均带权径长度
{//w存放n个字符的权值(均>0),构造哈夫曼树HT,并求出n个字符的哈夫曼编码HC
if(n <= 1)
{
printf( "字符数小于2,无法生成哈夫曼树!\n" );
return FALSE;
}
int m=2*n-1;//哈夫曼树中的结点个数为2*n-1
HT=(HuffmanTree)malloc( m*sizeof(HTNode) );//给哈夫曼树申请空间
HuffmanTree p;
int i;
for(p = HT,i = 1;i <= n;i++, p++, w++)//下标用到了0,所以用-1表示空
*p={ *w, -1, -1, -1 };//初始化储存
for(;i <= m;i++, p++)// 哈夫曼树的数组
*p={ -1, -1, -1, -1 };
for(i = n+1;i <= m;i++)//构建哈夫曼树
{//在HT[0..i-2]中选择parent为-1且weight最小的两个结点,其下标为s1,s2
int s1,s2;
Select(HT,i-2,s1,s2);//i==m时,即构建哈弗曼树的根结点时,仍然可以选择出两个parent为-1的结点
HT[s1].parent=i-1; //也是除根节点外的仅剩的两个parent为-1的结点,生成根结点后,这个for循环也就结束了
HT[s2].parent=i-1;//因此也就不需要在Select函数中判断是否可以进行选择(用到了这个函数,就一定能选择出两个最小的且parent为-1的结点)
HT[i-1].lchild=s1;
HT[i-1].rchild=s2;
HT[i-1].weight=HT[s1].weight+HT[s2].weight;
}
//从叶子到根逆向求每个字符的哈夫曼编码
HC=(HuffmanCode)malloc( n*sizeof(char*) );//分配n个字符编码的头指针向量
char *cd=(char*)malloc( n*sizeof(char) );//分配求编码的工作空间 ,cd是字符数组
cd[n-1]='\0'; //编码结束符 //赫夫曼编码最长长度等于n-1(赫夫曼树从根到叶子路径上的最大分支数就是n-1 )
int start; // n个空间,最后一个位置正好可以放上结束符
int f,c;
for(i=0;i<n;i++)//逐个字符求哈夫曼编码
{
start=n-1;//编码结束符位置
//求下标为i的字符的编码
for(c=i, f=HT[i].parent; f!=-1; c=f, f=HT[f].parent)//f始终是c的parent,从叶子到根逆向求编码
{
if(HT[f].lchild == c)
cd[--start]='0';
else
cd[--start]='1';
pathLength[i]++;//有一位编码,也就有一个分支,路径长度就加一
} //循环结束后,start就是储存编码的起始位置
HC[i]=(char*)malloc( (n-start)*sizeof(char) );//为下标为i的字符编码分配空间
strcpy( HC[i], &cd[start] );//从cd复制编码到HC
}
free(cd);//释放工作空间
printf( "生成的赫夫曼树如下表所示(树中作左孩子权重小于等于右孩子权重):\n" );
printf( "下标\tweight\tparent\tlchild\trchild\n" );
for(i=0;i<m;i++)
{
printf( " %2d\t%d\t%2d\t%2d\t%2d\n",i,HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild );
}
system( "pause" );
printf( "生成的赫夫曼编码如下:\n" );
printf( "下标\t编码\n" );
for(i=0;i<n;i++)
{
printf( " %2d\t%s\n",i,HC[i] );
}
system( "pause" );
double WPL=0;
for(i=0;i<n;i++)
{
WPL+=HT[i].weight*pathLength[i];
}
printf( "平均带权路径长度(保留两位小数)WPL/n=%.2lf\n",WPL/n );
return TRUE;
}
//*1、随机生成二叉树。 2、生成并保存先(后)序、中序输出序列。 3、按照保存的一对输出序列恢复出二叉树。 4、生成先(后)序、中序输出序列。 5、比较两对输出序列。
int Save_preOrder(BiTNode* T) //生成并保存先序输出序列
{
if(T)
{
if(visit(T->data))//访问根结点
{
pre[i_pre]=T->data;//保存到数组
i_pre++;
if(Save_preOrder(T->left))
if(Save_preOrder(T->right))
return TRUE;
}
return FALSE;
}
else
return TRUE;//如果T为空树,就直接返回
}
int Save_inOrder(BiTNode* T)//生成并保存中序输出遍历
{
if(T)
{
if(Save_inOrder(T->left))
if(visit(T->data))//访问根结点
{
in[i_in]=T->data;//保存到数组
i_in++;
if(Save_inOrder(T->right))
return TRUE;
}
return FALSE;
}
else
return TRUE;//如果T为空树,就直接返回
}
BiTNode* recoverTree(int *pre,int *in,int size)//根据先序、中序序列恢复出二叉树
{ //size为树中结点数目
BiTNode *T;
int i;
for(i=0;i<size;i++)
{
if(pre[0] == in[i])//先序遍历的第一个结点为根结点
{ //中序遍历中找到与先序遍历的第一个结点相等的结点,即为根结点
T=new BiTNode;//这个结点的左边的结点为它的左子树上的结点,右边为它的右子树上的结点
T->data=in[i];
T->left=recoverTree(pre+1,in,i);//对左右子树结点进行相同的操作
T->right=recoverTree(pre+i+1,in+i+1,size-i-1);
return T;
}
}
return NULL;//size==0 ,序列中没有元素,返回空指针
}
实验报告
实验三:二叉树的基本操作
一、实验三需要我们实现如下的要求:
1、实现二叉树的创建;
2、用递归方法分别先序、中序、后序遍历以Tree为根指针的二叉树;
3、编写递归算法,计算二叉树中叶子结点的数目;
4、编写递归算法,计算二叉树的深度;
5、编写递归算法,将二叉树中所有结点的左、右子树相互交换;
6、使用数组elem中的随机数序列(以0表示结束,不包括0),生成以Tree为根指针的二叉排序树;
7、在以Tree为根指针的二叉排序树中查找结点;
8、从以Tree为根指针的二叉排序树中删除结点(适用各种位置的结点);
9、用非递归算法,先序遍历以Tree为根指针的二叉树;
提示:用数组 BiTNode *stack[max] 构成堆栈,利用这个堆栈实现功能。
10、对以Tree为根指针的二叉树,从根结点开始,逐层从左到右输出各结点的数据。
提示:用数组 BiTNode *queue[max] 构成队列,利用这个队列实现功能
*11、根据Huffman编码原理,使用数组elem中的随机数序列(以0表示结束,不包括0)作为结点的权重,生成赫夫曼树,以及赫夫曼编码,计算平均带权径长度。
*12、(1)随机生成二叉树。 (2)生成并保存先(后)序、中序输出序列。 (3)按照保存的一对输出序列恢复出二叉树。(4)生成先(后)序输出序列。
只有3、5和生成赫夫曼树的操作中的选择两个权重最小结点的Select函数完全是自己独立编写,其他操作均有参考书本或者CSDN上的代码。
二、遇到的问题及其相关解决措施,独特做法
注意:对BinT.h中的一些函数进行了细微的修改,对init0的参数表进行了改变,以便生成赫夫曼树时,可以明确知道数据个数
设了一个全局变量N,在init1函数中获取数据个数,以便知晓保存序列数组中的非零元素个数
修改BinT.h后,必须编译一下 BinT.h,才能使在主程序在调用BinT.h的函数时也会有相应的变化,
如果修改后不编译一下BinT.h,主程序就可能会编译出错,或者没有相应改变,导致无法完成相应功能
1、刚开始不知道创建二叉树的具体操作是什么,后来看了书上的代码才知道就是输入先序序列来构造二叉树;
2、在写算二叉树深度的函数时,自己独立去想,死活想不出来,后来参考了CSDN上的代码,豁然开朗,在写递归时,先假设这个函数可以完成相应的操作,再根据函数的结果,进行一些简单操作,递归就可以实现功能了,例如在算深度时,假设左右子树的深度都能通过此函数算出来,而树的深度就等于较深子树的深度加一,这样就把函数写出来了
3、在生成二叉排序树时,需要查找和插入操作,而这些操作都是用递归实现的 ,把所有操作写在一个函数里面的话,难以理解,很难写出来,把各个操做单独写一个函数,各个函数进行递归,这样写起来就简单了,如果全部操作都写在一个函数里面,递归就似乎变得很复杂了,难以理解
4、查找二叉排序树的函数返回查找的的结点的指针,以便删除操作中可以轻松确定要除的结点是否存在,确定指向要删除结点的指针
5、在删除二叉排序树的结点的操作中,结点的情况分为左右孩子均不空,左右孩子均为空,左空右不空,右空左不空;其中左右孩子均为空还分为要删除的结点是否为根结点的情况:
左右只有一个子树为空的情况是最好理解的,只需用不空的孩子结点顶替掉它接可以了
(顶替是数据和左右孩子的顶替):
6、非递归先序遍历时,左孩子要比右孩子后入栈,以便根左右遍历,比右孩子先出栈
7、层序遍历,逐层输出时,用到两个队列,一个保存当前层结点,另一个保存下一层结点,可以控制打印换行符,实现逐层显示
8、在对赫夫曼树进行相关操作时,我发现书本上的代码所写的情况是下标从1开始的,0号下标不用,用0在生成的赫夫曼树的表中表示空,而随机数序列的下标是从0开始的,于是就应该不用0作为空的标记,用-1作为空标记
9、利用简单选择排序的思想写Select函数找出两个weight最小结点的下标
但是并不进行排序,只是选择出最小的两个
10、调用函数时,把返回值类型写在前面了 ,导致无法调用 ,看了好一会儿才发现这个问题,需要细心
11、利用递归根据一对序列恢复二叉树
12、生成二叉排序树时,如果数据个数为0,就无法生成二叉排序树
三、界面展示&过程说明
初始界面:
- 创建二叉树:
2、先序、中序、后序遍历
3、求二叉树叶子结点数
4、求二叉树深度
5、交换全部结点的左右子树
6、生成二叉排序树
7、在二叉排序树中查找结点(成功与失败)
8、在二叉排序树中删除结点(适用于各种位置)
删除前:
删除(删除537,它的左右子树均不为空)后:
继续删除48,它的左子树为空,右子树非空
继续删除231,它的右子树为空,左子树非空
继续删除806,它的左右子树均为空
删除失败的情况:
二叉排序树没有变化
重新初始化,测试删除只有一个结点的树的情况:
删除前:
删除后:
此时,再进行查找操作:
9、非递归先序遍历二叉树
先再次初始化:
然后非递归先序遍历
然后看看递归先序遍历
二者完全一致
10、接着以上操作进行层序遍历
与生成的文件一致;
11、2-7,9-12的操作都需要创建一个二叉树,其中9-10必须要创建一个二叉排序树,如果没有创建,就会有以下提示:
如果已经创建了一颗二叉树,但是是空树,或者是二叉排序树被删除成空树时,就会出现如下情况:
2-7,11-12
9-10
12、根据Huffman编码原理,使用数组elem中的随机数序列(以0表示结束,不包括0)作为结点的权重,生成赫夫曼树,以及赫夫曼编码,计算平均带权径长度
13、(1)随机生成二叉树。
(2)生成并保存先序、中序输出序列。
(3)按照保存的一对输出序列恢复出二叉树
先清空:
再恢复:
(4)生成后序输出序列。
四、体会与心得
某些递归的操作,如果只是一个人去硬想,还是很难写出来的,要学会去看别人的代码,学习别人的思想,也许一看,就会让人豁然开朗,二叉树的操作相比于之前两个实验要复杂,数据结构相关的知识记得也不是很牢,这就让我必须去参考书本或者网络上的代码才能完整的实现相应操作。总之,知识掌握的还是不够多,不够深。
有时候,代码打多了,难免会出现厌恶的情绪,一不留神,这种情绪就会放大,这正是我需要克服的心理问题;但是人也不是机器,也不能把自己逼得太紧,太紧了只会加深厌恶,劳逸结合才能让人更好的学习生活。