广州大学数据结构实验报告二《二叉树的操作与实现》

开课实验室:     20221126

学院

计算机科学与网络工程学院

年级、专业、班

姓名

学号

实验课程名称

数据结构实验

成绩

实验项目名称

二叉树的操作与实现

指导老师

一、实验目的

掌握线性的定义及基本操作,用链表实现:遍历、查找、插入、删除、翻转。

二、使用仪器、器材

微机一台

操作系统:

编程软件:

三、实验内容

利用二叉树的二叉链式存储结构设计并实现各种操作算法。

1.二叉树的基本操作算法实现

(1)利用二叉树字符串“A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))”创建二叉树的二叉链式存储结构;

(2)输出该二叉树;

(3)输出‘H’节点的左、右孩子结点值;

(4)输出该二叉树的结点个数、叶子结点个数、二叉树的度和高度;

2.二叉树的各种遍历算法实现

实现上述二叉树的先序、中序和后序遍历的递归和非递归算法;

3.线索二叉树的遍历

中序线索化上述二叉树并找出根结点的前驱和后继。

4.构造哈夫曼树和哈夫曼编码的算法实现

统计下面一段英文的不同字符个数和每个字符的出现频率,利用统计数据构造构造哈夫曼树和哈夫曼编码。要求:利用构造的哈夫曼编码对下文进行压缩和解压后,与原文一样。

The Chinese official said he viewed the Trump Presidency not as an aberration but as the product of a failing political system. This jibes with other accounts. The Chinese leadership believes that the United States, and Western democracies in general, haven’t risen to the challenge of a globalized economy, which necessitates big changes in production patterns, as well as major upgrades in education and public infrastructure. In Trump and Trumpism, the Chinese see an inevitable backlash to this failure.

四、实验原理

填入自己的内容(思路或算法流程图或伪代码说明等)

1、二叉树的基本操作算法实现

二叉树的每一个结点用链表中的一个结点来储存。其中data域用于存储对应的数据元素,lchild和rchild分别表示左指针域和右指针域,分别用于存储左孩子结点和右孩子结点的存储地址。

创建一个BiTree.h文件,在此文件中对二叉树的结点类型BTNode进行声明,并在此文件中声明二叉树的基本操作算法,包括:初始化空二叉树、建立二叉树、销毁二叉树、输出二叉树、查找结点、找左孩子结点、找右孩子结点,计算二叉树叶子结点个数、计算二叉树高度、计算二叉树的度。

创建一个BiTree.cpp文件,在此文件中对BiTree.h文件中声明的函数进行具体实现。

创建一个main.cpp文件,包含BiTree.h,在此文件中利用二叉树字符串“A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))”创建二叉树的二叉链式存储结构;输出二叉树;查找给定结点的左右孩子并输出结点值;输出该二叉树的结点个数、叶子结点个数、二叉树的度和高度。

计算二叉树叶子结点个数

度为零的结点称为叶子结点。

步骤:函数采用递归的思路,先声明一个变量int counter = 0; 分别表示叶子结点的个数。若是空树,则函数返回0;否则:若一开始的结点即为叶子结点,函数返回1,接着求左子树叶子结点的个数,然后累加到counter上,接着再求右子树叶子结点的个数,求完后累加到counter上,完成上述操作后(递归结束后),返回counter的值,即二叉树叶子结点的个数。

计算二叉树的度

树中所有结点的度中的最大值称为树的度。

步骤:函数采用递归的思路,先声明三个变量int degree = 0;int ldegree = 0;int rdegree = 0;分别表示树的度数、左子树的度数、右子树的度数。若一开始的结点无左右孩子(叶子结点),函数返回0;若一开始的结点为双分支结点时,函数返回2;否则(一开始为单分支结点),先让degree取1,接着求该结点左子树的度数,函数返回值赋值给ldegree,接着再求该结点的右子树的度数,函数返回值赋值给rchild,左右子树的度数求解后(递归结束后)取degree、ldegree、rdegree的最大值,即树的度数。

其他函数也与上述的两个函数类型,大量采用递归的方式进行求解。

2、二叉树的各种遍历算法实现

二叉树的遍历是指按照一定的次序访问二叉树中的所有结点,并且每个结点仅被访问一次的过程。它是二叉树最基本的运算,是二叉树中所有其他运算实现的基础。

步骤:在上述程序的基础上加上几个函数的实现:先序遍历二叉树(递归)、中序遍历二叉树(递归)、后序遍历二叉树(递归)、先序遍历二叉树(非递归)、中序遍历二叉树(非递归)、后序遍历二叉树(非递归)。

先序遍历,即按照先访问根结点,再先序遍历左子树,最后再先序遍历右子树。中序遍历,即先中序遍历左子树,再访问根结点,最后再中序遍历右子树。后序遍历,即先后序遍历左子树,再后序遍历右子树,最后再访问根结点。由上述遍历的定义可知,这些遍历的实现都需要采用递归操作,其算法设计相对简单。

采用非递归的方式进行二叉树的遍历需要使用到顺序栈。为此,需要在上述程序中增加顺序栈的存储结构、顺序栈的基本算法实现。

先序遍历非递归算法伪代码:

         将根结点root入栈

         While(栈不为空)

{  出栈结点p并访问它;

   若p结点有右孩子,则将其右孩子入栈;

   若p结点有左孩子,则将其左孩子入栈;

}

中序遍历非递归算法伪代码:

         p=b;

         while(栈不为空或者p!=NULL)

{  while(结点p不空)

{

   将p入栈;

   p=p->lchild;

}

//此时栈顶结点没有左孩子或者左子树已遍历完

   while(栈不空)

{

   将p出栈并访问p;

   p=p->rchild;

}

}

后序遍历非递归算法伪代码:

         p=b;

Do

{   while(结点p不空)

{  将结点p入栈;

   p=p->lchild;

}

//此时栈顶结点没有左孩子或者左子树已遍历完

   while(栈不空且结点p是栈顶结点)

{

   取栈顶结点p;

   If(结点p的右子树已遍历)

{   访问结点p;

    退栈;

}

else p=p->rchild;  //转向处理其右子树

}

}while(栈不空)

3、线索二叉树的遍历

遍历二叉树的结果是一个结点的线性序列,当某结点的左指针为空时,令该指针指向这个线性序列中的该结点的前驱结点;当某结点的右指针为空时,令该指针指向这个线性序列中的该结点的后继结点。这样的指向该线性序列中“前驱结点”和“后继结点”的指针称为线索。创建线索的过程称为线索化。线索化的二叉树称为线索二叉树。

为了实现线索化二叉树,需要将前面的二叉树结点类型的声明进行修改,即在节点的存储结构上增加两个标志位:ltag和rtag。这样子就产生了一个问题,原先实现的二叉树基本操作算法的函数就具有不适用性,因为它们二叉树结点的类型不同。此时有两种解决方案:(1)使用到程序设计基础这门课程中学到的函数模板与结构模板。(2)创建多一个程序,声明新的二叉树结点类型,并将之前的程序用到旧二叉树结点类型的函数、变量统统改为新的二叉树结点类型。我在本次实验中采用了第二种方案。

在BiTree.h文件,对新的二叉树结点类型(线索二叉树)TBTNode进行声明,,并在此文件中声明线索二叉树遍历需要使用到的算法,包括:中序线索化二叉树、遍历线索化二叉树以找根结点的前驱结点和后继结点。

在BiTree.cpp文件中对新声明的函数进行具体实现。

在main.cpp文件中,进行线索二叉树的遍历。

中序线索化二叉树函数ThreadThInOrder

ThInOrder函数以结点p为根的二叉树进行中序线索化,而Thread函数以二叉链存储的二叉树root进行中序线索化,并返回线索化后头结点的指针head。

中序遍历线索化二叉树以找根结点的前驱结点和后继结点ThInOrder

该算法以中序遍历来遍历二叉树,当遍历过程中的当前结点的右线索指向根结点,则此结点为根结点的前驱结点,此时访问该结点;当遍历过程中的当前结点的左线索指向根结点,则此结点为根结点的后继结点,此时访问该结点。

值得注意的是:中序化二叉树时,若二叉树为空,则二叉树的头结点的lchild指向自己,rchild指向根结点。随后若是对空二叉树进行遍历,遍历为空。二叉树无根结点,更没有根结点的前驱结点和后继结点。二叉树也存在根结点只有前驱结点没有后继结点或根结点只有后继结点而没有前驱结点的情况。为此,在设计算法是需要使用大量的条件语句进行处理。

4、构造哈夫曼树和哈夫曼编码的算法实现

构造哈夫曼树的思路:

1:记录给定英文文段的不同字符,将其存于字符数组中;记录每个英文字符出现的次数,将其存于整型数组中。

2:利用上述两个数组构造哈夫曼树,并求哈夫曼树对应的哈夫曼编码。

  1. 构造哈夫曼树:用一个ht[ ]数组来存放哈夫曼树,对于n0个叶子结点,共有2*n0-1个结点。n0个叶子结点存放在ht[0]~ht[n0-1]中,然后处理非叶子结点ht[i](存放在ht[n0]~ht[2*n0-2]中),从ht[0]~ht[n0-1](且parent域为-1)中找出权值小的两个结点ht[lnode]和ht[rnode],并将他们作为ht[i]的左右子树,将ht[lnode]和ht[rnode]的双亲结点置为ht[i],并且ht[i].weight=ht[lnode].weight+ht[rnode].weight;如此这样,直至n0-1个分支节点(非叶子结点)处理完毕。

求哈夫曼树对应的哈夫曼编码的思路:

定义一个结构体:结构体HCode包含一个字符数组cd[n0],用于存放单个哈夫曼树叶子结点对应的哈夫曼编码;一个start标志(cd[start...n0]记录着叶子结点的哈夫曼编码)。设置一个HCode数组,数组大小为n0,用于存放所有叶子结点的哈夫曼编码。

构造算法:对于当前叶子结点ht[i],先将对应的哈夫曼编码hcd[i]的start域置为n0,找其双亲结点ht[f],若当前结点是双亲结点的左孩子结点,则在hcd[i]的cd数组中添加‘0’;

若当前结点是双亲结点的右孩子结点,则在hcd[i]的cd数组中添加‘1’,并将start域减1。再对双亲结点进行相同的操作,如此这样,直至无双亲结点(即达到根结点)。最终start指向哈夫曼编码最开始的字符。

压缩的思路:

将英文文段以字符串的形式写入文件一中,再对文件一中的每个字符逐一进行求哈夫曼编码(判断该字符与哈夫曼树的哪个根结点的data域相同,找到对应的叶子节点的即可求该结点的哈夫曼编码),将求出的哈夫曼编码写入文件二中。

解压的思路:

声明一个BTNode变量,并将哈夫曼树的根结点赋值给此变量。

1:将文件二的0、1字符逐一进行读出

2:若读出的是0,则将该根结点的左孩子设置为新的根结点;若读出的是1,则将该结点的右孩子设置为新的根结点。

3:判断该结点是否为叶子结点。如果是,则输出该结点的data域,并将哈夫曼树的根结点重新赋值给BTNode;否则,重复1、2、3的操作。

五、实验过程原始数据记录

1、实验源代码及注释

1二叉树的基本操作算法实现+二叉树的各种遍历算法实现<

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值