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

开课实验室:     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二叉树的基本操作算法实现+二叉树的各种遍历算法实现

BiTree.h

#pragma once
#include<iostream>
using namespace std;
#define MaxSize 50
typedef char ElemType;
typedef struct node
{
	ElemType data;                    //数据元素
	struct node* lchild;              //指向左孩子结点
	struct node* rchild;              //指向右孩子结点
}BTNode;


//初始化空二叉树
void BTreeInit(BTNode*& root);

//建立二叉树
void CreateBTree(BTNode*& root, ElemType* str);

//销毁二叉树
void DestroyBTree(BTNode*& root);

//输出二叉树
void DispBTree(BTNode* root);

//查找结点
BTNode* FindNode(BTNode* root, ElemType e);

//找左孩子结点
BTNode* LchildNode(BTNode* root);

//找右孩子结点
BTNode* RchildNode(BTNode* root);

//计算二叉树所有结点的个数
int NodesCount(BTNode* root);

//计算二叉树叶子结点个数
int LeafCount(BTNode* root);

//计算二叉树高度
int BTreeHeight(BTNode* root);

//计算二叉树的度
int BTreeDegree(BTNode* root);

//先序遍历二叉树(递归)
void PreOrderRecur(BTNode* root);

//中序遍历二叉树(递归)
void InOrderRecur(BTNode* root);

//后序遍历二叉树(递归)
void PostOrderRecur(BTNode* root);

//先序遍历二叉树(非递归)
void PreOrder(BTNode* root);

//中序遍历二叉树(非递归)
void InOrder(BTNode* root);

//后序遍历二叉树(非递归)
void PostOrder(BTNode* root);

SequentialStack.h

#pragma once
#include<iostream>
#include"BiTree.h"
using namespace std;
#define MaxSize 50
typedef BTNode* EleType;
typedef struct
{
	EleType data[MaxSize];  //存放栈中的数据元素
	int top;  //存放栈顶指针
}SqStack;  //顺序栈类型

//初始化栈
void InitStack(SqStack*& s);

//销毁栈
void DestroyStack(SqStack*& s);

//判断栈是否为空
bool StackEmpty(SqStack* s);

//进栈
bool Push(SqStack*& s, EleType e);

//出栈
bool Pop(SqStack*& s, EleType& e);

//取栈顶元素
bool GetTop(SqStack* s, EleType& e);

 BiTree.cpp

#include"BiTree.h"
#include"SequentialStack.h"
//初始化空二叉树
void BTreeInit(BTNode*& root)
{
	root = NULL;
}


//建立二叉树
void CreateBTree(BTNode*& root, ElemType*str)
{
	BTNode* St[MaxSize], * p;  //St数组为顺序栈(指针数组)
	p = NULL;  //初始化指针p
	int top = -1, k = 0, j = 0;  //top为栈顶指针,k指定其后处理的是双亲结点(栈顶结点)的左孩子还是右孩子,j字符串数组下标
	char ch = '\0';
	//root = NULL;  //初始时,二叉链为空
	BTreeInit(root);  //初始化空二叉链
	ch = str[j];
	while (ch != '\0')
	{
		switch (ch)
		{
		case'(':    //开始处理栈顶结点的左孩子结点
			top++;
			St[top] = p;  //结点的指针(地址)入栈
			k = 1;  //标志:处理左孩子
			break;
		case')':    //栈顶结点的子树处理完毕
			top--;
			break;
		case',':    //开始处理栈顶结点的右孩子结点
			k = 2;  //标志:处理右孩子
			break;
		default:
			p = (BTNode*)malloc(sizeof(BTNode));  //创建一个结点,由p指向它
			p->data = ch;
			p->lchild = p->rchild = NULL;
			if (root == NULL)  //若尚未建立根节点
				root = p;
			else
			{
				switch (k)
				{
				case 1:    //新建结点作为栈顶结点的左孩子
					St[top]->lchild = p;
					break;
				case 2:    //新建结点作为栈顶结点的右孩子
					St[top]->rchild = p;
					break;
				}
			}
		}
		j++;  //继续遍历str
		ch = str[j];
	}
}


//销毁二叉树
void DestroyBTree(BTNode*& root)
{
	if (root != NULL)
	{
		DestroyBTree(root->lchild);
		DestroyBTree(root->rchild);
		free(root);
	}
}


//输出二叉树
void DispBTree(BTNode* root)
{
	if (root != NULL)
	{
		printf("%c", root->data);
		if (root->lchild || root->rchild != NULL)
		{
			printf("(");   //有孩子结点时才输出"("
			DispBTree(root->lchild);  //递归输出左子树
			if (root->rchild != NULL)  //有右孩子结点时才输出","
				printf(",");
			DispBTree(root->rchild);  //递归输出右子树
			printf(")");  //有孩子结点时才输出")"
		}
	}
}


//查找结点
BTNode* FindNode(BTNode* root, ElemType e)
{
	BTNode* p;
	if (root == NULL)
		return NULL;
	else if (root->data == e)
		return root;
	else
	{
		p = FindNode(root->lchild, e);
		if (p != NULL)
			return p;
		else
			return FindNode(root->rchild, e);
	}
}


//找左孩子结点
BTNode* LchildNode(BTNode* root)
{
	return root->lchild;
}


//找右孩子结点
BTNode* RchildNode(BTNode* root)
{
	return root->rchild;
}


//计算二叉树所有结点的个数
int NodesCount(BTNode* root)
{
	if (root == NULL)
		return 0;
	else
		return NodesCount(root->lchild) + NodesCount(root->rchild) + 1;
}


//计算二叉树叶子结点个数
int LeafCount(BTNode* root)
{
	int counter = 0;  //叶子结点个数
	if (root == NULL)
		return 0;    //空树的叶子结点为零
	else
	{
		if (root->lchild == NULL && root->rchild == NULL)  //找到叶子结点,函数返回1
		{
			return 1;
		}
		counter += LeafCount(root->lchild);  //求左子树的叶子结点个数   关键:累加
		counter += LeafCount(root->rchild);  //求右子树的叶子结点个数
		return counter;
	}
}


//计算二叉树高度         
int BTreeHeight(BTNode* root)  //先求左子树高度,再求右子树高度,比较二者大小,取最大的
{
	int lchildh, rchildh;  //左子树高度、右子树高度
	lchildh = rchildh = 0;    //误以为:使用递归操作时,不能初始化变量,否则这个变量无法累加;实际不影响
	if (root == NULL)
		return 0;               //空树的高度为零
	else
	{
		lchildh = BTreeHeight(root->lchild);   //求左子树的高度
		rchildh = BTreeHeight(root->rchild);   //求右子树的高度
		return (lchildh > rchildh) ? (lchildh + 1) : (rchildh + 1);
	}
}


//计算二叉树的度  :树中所有结点的度中的最大值称为树的度
int BTreeDegree(BTNode* root)
{
	int degree = 0;
	int ldegree = 0;
	int rdegree = 0;
	if (root == NULL)
		return 0;
	else
	{
		if (root->lchild == NULL && root->rchild == NULL)  //左右孩子都为空的结点
		{
			return 0;
		}
		else if (root->lchild != NULL && root->rchild != NULL)  //找到双分支(左右孩子都不为空)结点,函数返回2
		{
			return 2;
		}
		else  //找到单分支(左孩子不为空或者右孩子不为空)结点
		{
			degree = 1;
			ldegree=BTreeDegree(root->lchild);  //求左子树的度数 
			rdegree=BTreeDegree(root->rchild);  //求右子树的度数
			if (ldegree > degree || rdegree > degree)
				return (ldegree > rdegree) ? ldegree : rdegree;
			else
				return degree;
		}
	}
}


//先序遍历二叉树(递归)
void PreOrderRecur(BTNode* root)
{
	if (root != NULL)  //结点不为空时
	{
		printf("%c", root->data);  //访问根结点
		PreOrderRecur(root->lchild);  //先序遍历左子树
		PreOrderRecur(root->rchild);  //先序遍历右子树
	}
	//结点为空时什么操作都不执行
}


//中序遍历二叉树(递归)
void InOrderRecur(BTNode* root)
{
	if (root != NULL)  //结点不为空时
	{
		InOrderRecur(root->lchild);    //中序遍历左子树
		printf("%c", root->data);  //访问根结点
		InOrderRecur(root->rchild);    //中序遍历右子树
	}
	//结点为空时什么操作都不执行
}


//后序遍历二叉树(递归)
void PostOrderRecur(BTNode* root)
{
	if (root != NULL)  //结点不为空时
	{
		PostOrderRecur(root->lchild);    //后序遍历左子树
		PostOrderRecur(root->rchild);    //后序遍历右子树
		printf("%c", root->data);  //访问根结点
	}
	//结点为空时什么操作都不执行
}


//先序遍历二叉树(非递归)
void PreOrder(BTNode* root)
{
	BTNode* p;
	SqStack* st;  //定义栈指针st
	InitStack(st);  //初始化栈st
	if (root != NULL)
	{
		Push(st, root);  //根结点进栈
		while (!StackEmpty(st))  //栈不为空时循环
		{
			Pop(st, p);  //退栈结点p并访问它?
			printf("%c", p->data);
			if (p->rchild != NULL)
				Push(st, p->rchild);  //有右孩子时,将其入栈
			if (p->lchild != NULL)    //有左孩子时,将其入栈
				Push(st, p->lchild);
		}
	}
	DestroyStack(st);  //销毁栈
}


//中序遍历二叉树(非递归)
void InOrder(BTNode* root)
{
	BTNode* p;
	SqStack* st;  //定义一个顺序栈指针st
	InitStack(st);  //初始化栈st
	p = root;
	while (!StackEmpty(st) || p != NULL)
	{
		while (p != NULL)  //找结点p的所有左下结点并进栈
		{
			Push(st, p);  //结点p进栈
			p = p->lchild;  //移动到左孩子
		}
		if (!StackEmpty(st))  //若栈不为空
		{
			Pop(st, p);  //出栈结点p
			printf("%c", p->data);  //访问结点p
			p = p->rchild;  //转向处理其右子树
		}
	}
}


//后序遍历二叉树(非递归)
void PostOrder(BTNode* root)
{
	BTNode* p, * r;
	bool flag;
	SqStack* st;  //定义一个顺序栈指针st
	InitStack(st);  //初始化栈st
	p = root;
	do
	{
		while (p != NULL)  //栈结点p的所有左下结点并入栈
		{
			Push(st, p);  //结点p入栈
			p = p->lchild;  //移动到左孩子
		}
		r = NULL;  //r指向刚访问到的结点,初始时为空
		flag = true;  //flag为真表示正在处理栈顶结点
		while (!StackEmpty(st) && flag)
		{
			GetTop(st, p);  //取出当前的栈顶结点p
			if (p->rchild == r)  //若结点p的右孩子卫为空或为刚访问过的结点
			{
				printf("%c", p->data);  //访问结点p
				Pop(st, p);
				r = p;  //r指向刚访问过的结点
			}
			else
			{
				p = p->rchild;  //转向处理其右子树
				flag = false;  //表示当前不是处理栈顶结点
			}
		}
	} while (!StackEmpty(st));  //栈不空时循环
	DestroyStack(st);  //销毁栈
}

SequentialStack.cpp

#include"SequentialStack.h"
//初始化栈
void InitStack(SqStack*& s)
{
	s = (SqStack*)malloc(sizeof(SqStack));  //分配一个顺序栈空间,首地址存放在s中
	s->top = -1;  //栈顶指针置为-1
}

//销毁栈
void DestroyStack(SqStack*& s)  //释放顺序栈s占用的内存空间
{
	free(s);
}

//判断栈是否为空
bool StackEmpty(SqStack* s) 
{
	return(s->top == -1);  //栈顶指针为-1时,表示该栈为空
}

//进栈
bool Push(SqStack*& s, EleType e)
{
	if (s->top == MaxSize - 1)  //栈满的情况,即栈上溢出
		return false;
	s->top++;
	s->data[s->top] = e;
	return true;
}

//出栈
bool Pop(SqStack*& s, EleType& e)
{
	if (s->top == -1)  //栈为空的情况,即栈下溢出
		return false;
	e = s->data[s->top];  //取栈顶元素
	s->top--;  //栈顶指针减1
	return true;
}

//取栈顶元素
bool GetTop(SqStack* s, EleType& e)
{
	if (s->top == -1)  //栈为空的情况,即栈下溢出
		return false;
	e = s->data[s->top];  //取栈顶元素
	return true;
}

 Main.cpp

#include"BiTree.h"
int main()
{
	BTNode* root;
	//char str[] = "";  //空二叉树
	//char str[] = "A";  //单个结点的二叉树
	//char str[] = "A(B(,C))";  //度数为1的二叉树
	//char str[] = "A(,B(,C(D(,E))))";  //度数为1的二叉树
	//char str[] = "A(,B(C,D(E(,F))))";  //度数为2的二叉树
	//char str[] = "A(B(D(,G)),C((E,F)))";  //度数为2的二叉树
	char str[] = "A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))";  //度数为2的二叉树
	BTNode* p = NULL;
	char ch = '\0';
	cout << "*********二叉树的基本操作算法实现*********" << endl;
	cout << "创建二叉树的二叉链式存储结构" << endl;
	CreateBTree(root, str);

	if (root == NULL)
		cout << "创建了一个空二叉树";
	else
	{
		cout << "二叉树如下:" << endl;
		DispBTree(root);
	}
	cout << endl << endl;

	cout << "请输入需要在二叉树中查找的结点的结点值:";
	cin >> ch;
	p = FindNode(root, ch);
	if (p == NULL)
		cout << "二叉树中不存在结点值为:" << ch << "的结点" << endl;
	else
		cout << "二叉树中结点值为:" << ch << "的地址是:" << p << endl;

	if (p != NULL)
	{
		if (LchildNode(p))
			cout << "'" << p->data << "'" << "结点的左孩子结点值为:" << LchildNode(p)->data << endl;
		else
			cout << "'" << p->data << "'" << "结点左孩子为空" << endl;
		if (RchildNode(p))
			cout << "'" << p->data << "'" << "结点的右孩子结点值为:" << RchildNode(p)->data << endl;
		else
			cout << "'" << p->data << "'" << "结点右孩子为空" << endl;
	}
	cout << endl;

	cout << "上述二叉树的相关参数如下:" << endl;
	cout << "结点的个数为:" << NodesCount(root) << endl;
	cout << "叶子结点的个数:" << LeafCount(root) << endl;
	cout << "高度为:" << BTreeHeight(root) << endl;
	cout << "二叉树的度:" << BTreeDegree(root) << endl;
	cout << endl;

	cout << "*********二叉树的各种遍历算法实现*********" << endl;
	if (root == NULL)
	{
		PreOrderRecur(root);  //二叉树先序遍历递归
		InOrderRecur(root);  //二叉树中序遍历递归
		PostOrderRecur(root);  //二叉树后序遍历递归
		cout << "二叉树为空,无结点值,遍历结果为空" << endl;
	}
	else
	{
		cout << "采用递归算法遍历输出二叉树的结点值:" << endl;
		cout << "先序遍历:";
		PreOrderRecur(root);
		cout << endl;
		cout << "中序遍历:";
		InOrderRecur(root);
		cout << endl;
		cout << "后序遍历:";
		PostOrderRecur(root);
		cout << endl;

		cout << "采用非递归算法遍历输出二叉树的结点值:" << endl;
		cout << "先序遍历:";
		PreOrder(root);
		cout << endl;
		cout << "中序遍历:";
		InOrder(root);
		cout << endl;
		cout << "后序遍历:";
		PostOrder(root);
		cout << endl;
	}
	DestroyBTree(root);  //销毁二叉树
	return 0;
}

2线索二叉树的遍历

BiTree.h

#pragma once
#include<iostream>
using namespace std;
#define MaxSize 50
typedef char ElemType;

typedef struct node                  
{
	ElemType data;                    //结点数据域
	int ltag, rtag;                   //增加的线索标记
	struct node* lchild;             //左孩子或线索指针
	struct node* rchild;             //右孩子或线索指针
}TBTNode;                             //线索二叉树中的结点类型

//初始化空二叉树
void BTreeInit(TBTNode*& root);

//建立二叉树
void CreateBTree(TBTNode*& root, ElemType* str);

//销毁二叉树
void DestroyBTree(TBTNode*& root);

//输出二叉树
void DispBTree(TBTNode* root);

//查找结点
TBTNode* FindNode(TBTNode* root, ElemType e);

//找左孩子结点
TBTNode* LchildNode(TBTNode* root);

//找右孩子结点
TBTNode* RchildNode(TBTNode* root);

//计算二叉树所有结点的个数
int NodesCount(TBTNode* root);

//计算二叉树叶子结点个数
int LeafCount(TBTNode* root);

//计算二叉树高度
int BTreeHeight(TBTNode* root);

//计算二叉树的度
int BTreeDegree(TBTNode* root);

//先序遍历二叉树(递归)
void PreOrderRecur(TBTNode* root);

//中序遍历二叉树(递归)
void InOrderRecur(TBTNode* root);

//后序遍历二叉树(递归)
void PostOrderRecur(TBTNode* root);

//先序遍历二叉树(非递归)
void PreOrder(TBTNode* root);

//中序遍历二叉树(非递归)
void InOrder(TBTNode* root);

//后序遍历二叉树(非递归)
void PostOrder(TBTNode* root);

//中序线索化二叉树
void Thread(TBTNode*& p);
TBTNode* CreateThread(TBTNode* root);

//遍历中序线索化二叉树以找根结点的前驱结点和后继结点
void ThInOrder(TBTNode * tb);

SequentialStack.h

#pragma once
#include<iostream>
#include"BiTree.h"
using namespace std;
#define MaxSize 50
typedef TBTNode* EleType;
typedef struct
{
	EleType data[MaxSize];  //存放栈中的数据元素
	int top;  //存放栈顶指针
}SqStack;  //顺序栈类型

//初始化栈
void InitStack(SqStack*& s);

//销毁栈
void DestroyStack(SqStack*& s);

//判断栈是否为空
bool StackEmpty(SqStack* s);

//进栈
bool Push(SqStack*& s, EleType e);

//出栈
bool Pop(SqStack*& s, EleType& e);

//取栈顶元素
bool GetTop(SqStack * s, EleType & e);

BiTree.cpp

#include"BiTree.h"
#include"SequentialStack.h"
//初始化空二叉树
void BTreeInit(TBTNode*& root)
{
	root = NULL;
}


//建立二叉树
void CreateBTree(TBTNode*& root, ElemType* str)
{
	TBTNode* St[MaxSize], * p;  //St数组为顺序栈(指针数组)
	p = NULL;  //初始化指针p
	int top = -1, k = 0, j = 0;  //top为栈顶指针,k指定其后处理的是双亲结点(栈顶结点)的左孩子还是右孩子,j字符串数组下标
	char ch = '\0';
	//root = NULL;  //初始时,二叉链为空
	BTreeInit(root);  //初始化空二叉链
	ch = str[j];
	while (ch != '\0')
	{
		switch (ch)
		{
		case'(':    //开始处理栈顶结点的左孩子结点
			top++;
			St[top] = p;  //结点的指针(地址)入栈
			k = 1;  //标志:处理左孩子
			break;
		case')':    //栈顶结点的子树处理完毕
			top--;
			break;
		case',':    //开始处理栈顶结点的右孩子结点
			k = 2;  //标志:处理右孩子
			break;
		default:
			p = (TBTNode*)malloc(sizeof(TBTNode));  //创建一个结点,由p指向它
			p->data = ch;
			p->lchild = p->rchild = NULL;
			if (root == NULL)  //若尚未建立根节点
				root = p;
			else
			{
				switch (k)
				{
				case 1:    //新建结点作为栈顶结点的左孩子
					St[top]->lchild = p;
					break;
				case 2:    //新建结点作为栈顶结点的右孩子
					St[top]->rchild = p;
					break;
				}
			}
		}
		j++;  //继续遍历str
		ch = str[j];
	}
}


//销毁二叉树
void DestroyBTree(TBTNode*& root)
{
	if (root != NULL)
	{
		DestroyBTree(root->lchild);
		DestroyBTree(root->rchild);
		free(root);
	}
}


//输出二叉树
void DispBTree(TBTNode* root)
{
	if (root != NULL)
	{
		printf("%c", root->data);
		if (root->lchild || root->rchild != NULL)
		{
			printf("(");   //有孩子结点时才输出"("
			DispBTree(root->lchild);  //递归输出左子树
			if (root->rchild != NULL)  //有右孩子结点时才输出","
				printf(",");
			DispBTree(root->rchild);  //递归输出右子树
			printf(")");  //有孩子结点时才输出")"
		}
	}
}


//查找结点
TBTNode* FindNode(TBTNode* root, ElemType e)
{
	TBTNode* p;
	if (root == NULL)
		return NULL;
	else if (root->data == e)
		return root;
	else
	{
		p = FindNode(root->lchild, e);
		if (p != NULL)
			return p;
		else
			return FindNode(root->rchild, e);
	}
}


//找左孩子结点
TBTNode* LchildNode(TBTNode* root)
{
	return root->lchild;
}


//找右孩子结点
TBTNode* RchildNode(TBTNode* root)
{
	return root->rchild;
}


//计算二叉树所有结点的个数
int NodesCount(TBTNode* root)
{
	if (root == NULL)
		return 0;
	else
		return NodesCount(root->lchild) + NodesCount(root->rchild) + 1;
}


//计算二叉树叶子结点个数
int LeafCount(TBTNode* root)
{
	int counter = 0;  //叶子结点个数
	if (root == NULL)
		return 0;    //空树的叶子结点为零
	else
	{
		if (root->lchild == NULL && root->rchild == NULL)  //找到叶子结点,函数返回1
		{
			return 1;
		}
		counter += LeafCount(root->lchild);  //求左子树的叶子结点个数   关键:累加
		counter += LeafCount(root->rchild);  //求右子树的叶子结点个数
		return counter;
	}
}


//计算二叉树高度         
int BTreeHeight(TBTNode* root)  //先求左子树高度,再求右子树高度,比较二者大小,取最大的
{
	int lchildh, rchildh;  //左子树高度、右子树高度
	lchildh = rchildh = 0;    //误以为:使用递归操作时,不能初始化变量,否则这个变量无法累加;实际不影响
	if (root == NULL)
		return 0;               //空树的高度为零
	else
	{
		lchildh = BTreeHeight(root->lchild);   //求左子树的高度
		rchildh = BTreeHeight(root->rchild);   //求右子树的高度
		return (lchildh > rchildh) ? (lchildh + 1) : (rchildh + 1);
	}
}


//计算二叉树的度  :树中所有结点的度中的最大值称为树的度
int BTreeDegree(TBTNode* root)
{
	int degree = 0;
	int ldegree = 0;
	int rdegree = 0;
	if (root == NULL)
		return 0;
	else
	{
		if (root->lchild == NULL && root->rchild == NULL)  //左右孩子都为空的结点
		{
			return 0;
		}
		else if (root->lchild != NULL && root->rchild != NULL)  //找到双分支(左右孩子都不为空)结点,函数返回2
		{
			return 2;
		}
		else  //找到单分支(左孩子不为空或者右孩子不为空)结点
		{
			degree = 1;
			ldegree = BTreeDegree(root->lchild);  //求左子树的度数 
			rdegree = BTreeDegree(root->rchild);  //求右子树的度数
			if (ldegree > degree || rdegree > degree)
				return (ldegree > rdegree) ? ldegree : rdegree;
			else
				return degree;
		}
	}
}


//先序遍历二叉树(递归)
void PreOrderRecur(TBTNode* root)
{
	if (root != NULL)  //结点不为空时
	{
		printf("%c", root->data);  //访问根结点
		PreOrderRecur(root->lchild);  //先序遍历左子树
		PreOrderRecur(root->rchild);  //先序遍历右子树
	}
	//结点为空时什么操作都不执行
}


//中序遍历二叉树(递归)
void InOrderRecur(TBTNode* root)
{
	if (root != NULL)  //结点不为空时
	{
		InOrderRecur(root->lchild);    //中序遍历左子树
		printf("%c", root->data);      //访问根结点
		InOrderRecur(root->rchild);  //中序遍历右子树
	}
	//结点为空时什么操作都不执行
}


//后序遍历二叉树(递归)
void PostOrderRecur(TBTNode* root)
{
	if (root != NULL)  //结点不为空时
	{
		PostOrderRecur(root->lchild);    //后序遍历左子树
		PostOrderRecur(root->rchild);    //后序遍历右子树
		printf("%c", root->data);  //访问根结点
	}
	//结点为空时什么操作都不执行
}


//先序遍历二叉树(非递归)
void PreOrder(TBTNode* root)
{
	TBTNode* p;
	SqStack* st;  //定义栈指针st
	InitStack(st);  //初始化栈st
	if (root != NULL)
	{
		Push(st, root);  //根结点进栈
		while (!StackEmpty(st))  //栈不为空时循环
		{
			Pop(st, p);  //退栈结点p并访问它?
			printf("%c", p->data);
			if (p->rchild != NULL)
				Push(st, p->rchild);  //有右孩子时,将其入栈
			if (p->lchild != NULL)    //有左孩子时,将其入栈
				Push(st, p->lchild);
		}
	}
	DestroyStack(st);  //销毁栈
}


//中序遍历二叉树(非递归)
void InOrder(TBTNode* root)
{
	TBTNode* p;
	SqStack* st;  //定义一个顺序栈指针st
	InitStack(st);  //初始化栈st
	p = root;
	while (!StackEmpty(st) || p != NULL)
	{
		while (p != NULL)  //找结点p的所有左下结点并进栈
		{
			Push(st, p);  //结点p进栈
			p = p->lchild;  //移动到左孩子
		}
		if (!StackEmpty(st))  //若栈不为空
		{
			Pop(st, p);  //出栈结点p
			printf("%c", p->data);  //访问结点p
			p = p->rchild;  //转向处理其右子树
		}
	}
}


//后序遍历二叉树(非递归)
void PostOrder(TBTNode* root)
{
	TBTNode* p, * r;
	bool flag;
	SqStack* st;  //定义一个顺序栈指针st
	InitStack(st);  //初始化栈st
	p = root;
	do
	{
		while (p != NULL)  //栈结点p的所有左下结点并入栈
		{
			Push(st, p);  //结点p入栈
			p = p->lchild;  //移动到左孩子
		}
		r = NULL;  //r指向刚访问到的结点,初始时为空
		flag = true;  //flag为真表示正在处理栈顶结点
		while (!StackEmpty(st) && flag)
		{
			GetTop(st, p);  //取出当前的栈顶结点p
			if (p->rchild == r)  //若结点p的右孩子卫为空或为刚访问过的结点
			{
				printf("%c", p->data);  //访问结点p
				Pop(st, p);
				r = p;  //r指向刚访问过的结点
			}
			else
			{
				p = p->rchild;  //转向处理其右子树
				flag = false;  //表示当前不是处理栈顶结点
			}
		}
	} while (!StackEmpty(st));  //栈不空时循环
	DestroyStack(st);  //销毁栈
}


TBTNode* pre;                          //全局变量,总是指向刚访问过的结点
//对二叉树p进行中序线索化
void Thread(TBTNode*& p)
{
	if (p != NULL)
	{
		Thread(p->lchild);            //左子树线索化
		if (p->lchild == NULL)         //左子树不存在,进行前驱结点线索化
		{
			p->lchild = pre;          //建立当前结点的前驱结点线索
			p->ltag = 1;
		}
		else                          //p结点的左子树已线索化
			p->ltag = 0;
		if (pre->rchild == NULL)      //对pre的后继结点线索化
		{
			pre->rchild = p;           //建立前驱结点的后继结点线索
			pre->rtag = 1;
		}
		else
			pre->rtag = 0;
		pre = p;
		Thread(p->rchild);             //右子树线索化
	}
}


//中序线索化二叉树
TBTNode* CreateThread(TBTNode* root)
{
	TBTNode* head;
	head = (TBTNode*)malloc(sizeof(TBTNode));     //创建头结点
	head->ltag = 0;
	head->rtag = 1;
	head->rchild = root;
	if (root == NULL)                              //空二叉树
		head->lchild = head;
	else
	{
		head->lchild = root;
		pre = head;                                //pre是结点p的前驱结点,供加线索用
		Thread(root);                              //中序遍历线索化二叉树
		pre->rchild = head;                        //最后处理,加入指向头结点的线索
		pre->rtag = 1;
		head->rchild = pre;                        //头结点右线索化
	}
	return head;
}


//遍历中序线索化二叉树以找根结点的前驱结点和后继结点
void ThInOrder(TBTNode* tb)                            //tb指向中序线索二叉树的头结点
{
	TBTNode* p = tb->lchild;                           //p指向根结点
	bool flag1, flag2;                                 //判断是否找到了根结点的前驱和后继
	flag1 = flag2 = 0;
	while (p != tb)
	{
		while (p->ltag == 0)                           //寻找开始结点
			p = p->lchild;
		if (p->rchild == tb->lchild)                   //若开始结点的右线索指向根结点
		{
			printf("根结点%c的前驱结点为:%c\n", tb->lchild->data, p->data);     //访问开始结点(根结点的前驱结点)
			flag1 = 1;                                 //找到了前驱,将flag1置为1
		}
		if (p->lchild == tb->lchild)                   //若开始结点的左线索指向根结点
		{
			printf("根结点%c的后继结点为:%c\n", tb->lchild->data, p->data);     //访问开始结点(根结点的后继结点)
			flag2 = 1;                                 //找到了后继,将flag2置为1
		}
		while (p->rtag == 1 && p->rchild != tb)        //p的右指针指向后继结点
		{
			p = p->rchild;                             //p指向后继结点
			if (p->rchild == tb->lchild)                   //若开始结点的右线索指向根结点
			{
				printf("根结点%c的前驱结点为:%c\n", tb->lchild->data, p->data);     //访问开始结点(根结点的前驱结点)
				flag1 = 1;                            //找到了前驱,将flag1置为1
			}
			if (p->lchild == tb->lchild)              //若开始结点的左线索指向根结点
			{
				printf("根结点%c的后继结点为:%c\n", tb->lchild->data, p->data);     //访问开始结点(根结点的后继结点)
				flag2 = 1;                            //找到了后继,将flag2置为1
			}
		}
		if (flag1 && flag2)                           //找到了根结点的前驱和后继,终止遍历操作
			break;
		p = p->rchild;                                //处理右子树
	}
	if (tb->lchild == tb)                             //二叉树为空时
		cout << "二叉树为空,无根结点,更没有根结点的前驱结点和后继结点" << endl;
	else                                              //二叉树不为空时
	{
		if (flag1 == 0)                              //找不到根结点的前驱结点
			cout << "根结点" << tb->lchild->data << "无前驱结点" << endl;
		if (flag2 == 0)                              //找不到根结点的后继结点
			cout << "根结点" << tb->lchild->data << "无后继结点" << endl;
	}
}

 SequentialStack.cpp

#include"SequentialStack.h"
//初始化栈
void InitStack(SqStack*& s)
{
	s = (SqStack*)malloc(sizeof(SqStack));  //分配一个顺序栈空间,首地址存放在s中
	s->top = -1;  //栈顶指针置为-1
}

//销毁栈
void DestroyStack(SqStack*& s)  //释放顺序栈s占用的内存空间
{
	free(s);
}

//判断栈是否为空
bool StackEmpty(SqStack* s)
{
	return(s->top == -1);  //栈顶指针为-1时,表示该栈为空
}

//进栈
bool Push(SqStack*& s, EleType e)
{
	if (s->top == MaxSize - 1)  //栈满的情况,即栈上溢出
		return false;
	s->top++;
	s->data[s->top] = e;
	return true;
}

//出栈
bool Pop(SqStack*& s, EleType& e)
{
	if (s->top == -1)  //栈为空的情况,即栈下溢出
		return false;
	e = s->data[s->top];  //取栈顶元素
	s->top--;  //栈顶指针减1
	return true;
}

//取栈顶元素
bool GetTop(SqStack* s, EleType& e)
{
	if (s->top == -1)  //栈为空的情况,即栈下溢出
		return false;
	e = s->data[s->top];  //取栈顶元素
	return true;
}

Main.cpp

#include"BiTree.h"
int main()
{
	TBTNode* root;
	//char str[] = "";  //空二叉树
	//char str[] = "A";  //单个结点的二叉树
	//char str[] = "A(B(,C))";  //度数为1的二叉树
	//char str[] = "A(,B(,C(D(,E))))";  //度数为1的二叉树
	//char str[] = "A(,B(C,D(E(,F))))";  //度数为2的二叉树
	//char str[] = "A(B(D(,G)),C((E,F)))";  //度数为2的二叉树
	char str[] = "A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))";  //度数为2的二叉树
	TBTNode* p = NULL;
	char ch = '\0';
	cout << "*********二叉树的基本操作算法实现*********" << endl;
	cout << "创建二叉树的二叉链式存储结构" << endl;
	CreateBTree(root, str);

	if (root == NULL)
		cout << "创建了一个空二叉树";
	else
	{
		cout << "二叉树如下:" << endl;
		DispBTree(root);
	}
	cout << endl << endl;

	cout << "请输入需要在二叉树中查找的结点的结点值:";
	cin >> ch;
	p = FindNode(root, ch);
	if (p == NULL)
		cout << "二叉树中不存在结点值为:" << ch << "的结点" << endl;
	else
		cout << "二叉树中结点值为:" << ch << "的地址是:" << p << endl;

	if (p != NULL)
	{
		if (LchildNode(p))
			cout << "'" << p->data << "'" << "结点的左孩子结点值为:" << LchildNode(p)->data << endl;
		else
			cout << "'" << p->data << "'" << "结点左孩子为空" << endl;
		if (RchildNode(p))
			cout << "'" << p->data << "'" << "结点的右孩子结点值为:" << RchildNode(p)->data << endl;
		else
			cout << "'" << p->data << "'" << "结点右孩子为空" << endl;
	}
	cout << endl;

	cout << "上述二叉树的相关参数如下:" << endl;
	cout << "结点的个数为:" << NodesCount(root) << endl;
	cout << "叶子结点的个数:" << LeafCount(root) << endl;
	cout << "高度为:" << BTreeHeight(root) << endl;
	cout << "二叉树的度:" << BTreeDegree(root) << endl;
	cout << endl;

	cout << "*********二叉树的各种遍历算法实现*********" << endl;
	if (root == NULL)
	{
		PreOrderRecur(root);  //二叉树先序遍历递归
		InOrderRecur(root);  //二叉树中序遍历递归
		PostOrderRecur(root);  //二叉树后序遍历递归
		cout << "二叉树为空,无结点值,遍历结果为空" << endl;
	}
	else
	{
		cout << "采用递归算法遍历输出二叉树的结点值:" << endl;
		cout << "先序遍历:";
		PreOrderRecur(root);
		cout << endl;
		cout << "中序遍历:";
		InOrderRecur(root);
		cout << endl;
		cout << "后序遍历:";
		PostOrderRecur(root);
		cout << endl;

		cout << "采用非递归算法遍历输出二叉树的结点值:" << endl;
		cout << "先序遍历:";
		PreOrder(root);
		cout << endl;
		cout << "中序遍历:";
		InOrder(root);
		cout << endl;
		cout << "后序遍历:";
		PostOrder(root);
		cout << endl;
	}
	cout << endl;

	cout << "*********线索二叉树的遍历********" << endl;
	cout << "中序化线索二叉树" << endl;
	TBTNode* t = CreateThread(root);  //中序化线索二叉树
	ThInOrder(t);
	return 0;
}

3)构造哈夫曼树和哈夫曼编码的算法实现

Huffman.h

#pragma once
#include<iostream>
#include<string>
using namespace std;
//Huffman树的存储结构
#define n 35			//叶子数目
#define m 2*n-1		    //树中结点总数
#define N 35            //编码的长度

typedef struct
{
	char data;          //结点值
	int weight;		    //权值
	int parent;         //双亲结点
	int lchild;         //左孩子结点
	int rchild;	        //右孩子结点
}HTNode;                //哈夫曼树中的结点类型

typedef struct
{
	char cd[N];         //存放当前结点的哈夫曼编码
	int start;          //表示cd[start..n0]部分是哈夫曼编码
}HCode;                 //存放每个结点的哈夫曼编码的类型

//初始化哈夫曼树
void InitHuffmanTree(HTNode ht[], char str_char[], int n0);            //n0表示叶子结点的个数

//设置哈夫曼树叶子结点的权值
void InputWeight(HTNode ht[], int str_weight[], int n0);

//构造哈夫曼树
void CreateHuffmanTree(HTNode ht[],int n0);

//打印哈夫曼树
void DisplayHuffmanTree(HTNode ht[], int n0);

//根据哈夫曼树构造对应的哈夫曼编码
void CreateHCode(HTNode ht[], HCode hcd[], int n0);

//根据哈夫曼树打印对应哈夫曼编码表
void DispalyHuffmanEncoding(HTNode ht[], HCode hcd[], int n0);

#include"Huffman.h"
//初始化哈夫曼树
void InitHuffmanTree(HTNode ht[], char str_char[], int n0)
{
	for (int i = 0; i <n0; i++)                             //初始化叶子结点的data域和weight域
	{
		ht[i].data = str_char[i];
		ht[i].weight = 0;
	}
	for (int i = n0; i < 2 * n0 - 1; i++)                   //初始化分支结点的data域和weight域
	{
		ht[i].data = '\0';
		ht[i].weight = 0;
	}
}

//设置哈夫曼树叶子结点的权值
void InputWeight(HTNode ht[], int str_weight[], int n0)
{
	for (int i = 0; i < n0; i++)
		ht[i].weight = str_weight[i];
}

//构造哈夫曼树
void CreateHuffmanTree(HTNode ht[], int n0)
{
	int i, k, lnode, rnode;
	int min1, min2;
	for (i = 0; i < 2 * n0 - 1; i++)                   //所有结点的相关域(parent域、lnode域和rnode域)置初值为-1
		ht[i].parent = ht[i].lchild = ht[i].rchild = -1;
	for (i = n0; i < 2 * n0 - 1; i++)                //构造哈夫曼树的n0-1个分支结点
	{
		min1 = min2 = 32767;                         //lnode和rnode指向两个权值最小的结点(下标)
		lnode = rnode = -1;
		for (k = 0; k < i; k++)                     //在ht[0..i-1]中找两个权值最小的结点
		{
			if (ht[k].parent == -1)                  //只在尚未构造二叉树的结点中查找
			{
				if (ht[k].weight < min1)             //左边的比右边的小
				{
					min2 = min1;
					rnode = lnode;
					min1 = ht[k].weight;
					lnode = k;
				}
				else if (ht[k].weight < min2)
				{
					min2 = ht[k].weight;
					rnode = k;
				}
			}
		}
		ht[i].weight = ht[lnode].weight + ht[rnode].weight;
		ht[i].lchild = lnode;                                //ht[i]作为双亲结点
		ht[i].rchild = rnode;
		ht[lnode].parent = i;
		ht[rnode].parent = i;
	}
}


//打印哈夫曼树
void DisplayHuffmanTree(HTNode ht[], int n0)
{
	for (int i = 0; i < 2 * n0 - 1; i++)
		cout << "ht[" << i << "]" << '\t' << ht[i].data << '\t' << ht[i].weight << endl;
}

//根据哈夫曼树构造对应叶子结点的哈夫曼编码
void CreateHCode(HTNode ht[], HCode hcd[], int n0)            //n0表示叶子结点的个数
{
	int i, f, c;
	i = f = c = 0;
	HCode hc;
	hc.start = 0;
	for (i = 0; i < n0; i++)
	{
		hc.start = n0;
		c = i;
		f = ht[i].parent;
		while (f != -1)                                      //循环直至无双亲结点,即到达根结点
		{
			if (ht[f].lchild == c)                          //当前结点是双亲结点的左孩子
				hc.cd[hc.start--] = '0';
			else                                            //当前结点是双亲结点的右孩子
				hc.cd[hc.start--] = '1';
			c = f;                                          //再对双亲结点进行同样的操作
			f = ht[f].parent;
		}
		hc.start++;                                         //start指向Huffman编码最开始的字符
		hcd[i] = hc;
	}
}


//根据哈夫曼树打印对应叶子结点的哈夫曼编码表
void DispalyHuffmanEncoding(HTNode ht[], HCode hcd[], int n0)
{
	for (int i = 0; i < n0; i++)
	{
		cout << ht[i].data << "\t";
		for (int k = hcd[i].start; k <= n0; k++)
		{
			cout << hcd[i].cd[k];
		}
		cout << endl;
	}
}

Main.cpp

#include"Huffman.h"
#include<fstream>   //处理文件(读或写)的头文件
int main()
{
	//英文中的不同字符,即需要编码的字符集合
	char str_char[] = {' ','\'',',','.','C','I','P','S','T','U','W','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','r','s','t','u','v','w','y','z'};
	//英文中的每个字符出现的次数
	int str_weight[] = { 78,1,5,4,3,1,1,1,6,1,1,37,9,18,14,55,7,7,23,37,2,1,17,8,31,19,10,21,31,33,13,4,4,3,1 };
	HTNode ht[m];                                 //哈夫曼树结点的数组
	HCode hcd[n];                                 //存放每个哈夫曼叶子结点的哈夫曼编码的数组
	int n0 = sizeof(str_char);                    //n0表示不同字符的个数,即哈夫曼树中叶子结点的个数
	InitHuffmanTree(ht, str_char, n0);            //初始化哈夫曼树
	InputWeight(ht, str_weight, n0);              //设置哈夫曼树叶子结点的权值
	CreateHuffmanTree(ht, n0);                    //构造哈夫曼树
	CreateHCode(ht, hcd, n0);                     //构造哈夫曼编码
	cout << "构造出的哈夫曼树结点数组及其内容如下:" << endl;
	DisplayHuffmanTree(ht, n0);
	cout << endl;
	cout << "根据哈夫曼树构造出来的哈夫曼编码表如下:" << endl;
	DispalyHuffmanEncoding(ht, hcd, n0);
	cout << endl;

	fstream  f1,f2,f3;                           //定义名为f1、f2、f3的数据流指针
	char ch;
	f1.open("C:\\vs\\file1.txt");                //以读和写方式打开C盘中的file1.txt文件
	f2.open("C:\\vs\\file2.txt");                //以读和写方式打开C盘中的writefile1.txt文件
	f3.open("C:\\vs\\file3.txt");                //以读和写方式打开C盘中的writefile1.txt文件
	char str[] = "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.";
	
	//*******************将英文文段写入文件file1.txt*************************
	cout << "需要压缩的内容如下:" << endl;
	cout << str << endl;
	f1.write(str, sizeof(str));                   //将字符串写入文件file1.txt
	f1.seekg(ios::beg);                           //重置数据流指针,将流指针指向文件头
	cout << endl;

	//*******************将英文文段对应的哈夫曼编码写入文件file2.txt(压缩)*************************
	cout << "压缩后的内容如下:" << endl;
	while(f1.get(ch))                             //一个字符一个字符地读取文件file1.txt的内容
	{
		for (int i = 0; i < n0; i++)             //在所有叶子结点中寻找与字符相等的结点
		{
			if (ch == ht[i].data)                //找到结点
			{
				for (int k = hcd[i].start; k <= n0; k++)  //将结点对应的哈夫曼编码写入文件file2.txt
				{
					cout << hcd[i].cd[k];
					f2.put(hcd[i].cd[k]);
				}
				break;
			}
		}
	}
	f2.seekg(ios::beg);  //重置数据流指针
	cout << endl << endl;

	//*******************将文件file2.txt的内容读取出来并翻译成英文文段并写入文件file3.txt(解压)*************************
	cout << "解压后的内容如下:" << endl;
	HTNode root = ht[m-1];
	while (f2.get(ch))                           //一个字符一个字符地读取文件file2.txt的内容
	{
		if (ch == '0')
		{
			root = ht[root.lchild];             //以左子树的根结点为根
		}
		else
		{
			root = ht[root.rchild];             //以右子树的根结点为根
		}
		if (root.lchild == -1 && root.rchild == -1)  //若该结点为叶子结点则输出该结点,并把该结点的data域写入文件file3.txt
		{
			cout << root.data;
			f3.put(root.data);
			root = ht[m-1];                     //关键步骤:写入字符后,根结点转变回树的哈夫曼树的根结点
		}
	}
	cout << endl;

	f1.close();                                //关闭文件
	f2.close();
	f3.close();
}

2、实验过程中的错误及原因总结

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

1、在建立二叉树的函数中,未初始化指针p

改进措施:在指针p声明后对其进行初始化:     p = NULL;  //初始化指针p

总结:声明变量后,需要对其进行初始化操作,否则容易报错

2、在设计建立二叉树的函数中,while循环的条件写成了   while (ch != '/0')  ,使得字符串遍历完成后依旧执行着循环,导致执行过程中程序中断

改进措施:改为  while (ch != '\0')

总结:代码写得不够多,犯低级错误

3、在计算二叉树的度的函数中,我在编写找到单分支结点的语句块时,忽略了当子树的度没有树的度大时,函数应该返回取树的度。

即: if (ldegree > degree || rdegree > degree)

return (ldegree > rdegree) ? ldegree : rdegree;

else  //刚开始漏了这句

return degree;

总结:在条件语句中要把握好代码之间的逻辑关系,可以在设计程序之前写一个伪代码,明确程序代码的逻辑关系。

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

1、在写二叉树的遍历的递归算法时,由于先序递归遍历、中序递归遍历、后序递归遍历仅仅是函数的内部代码顺序发生了变化,其他的内容都是相同的,所以我在写代码过程中为节省时耗而直接进行了复制粘贴,但是在这过程中,粘贴时在中序递归函数中递归调用了先序递归遍历函数。

(3)线索二叉树的遍历

1、在遍历线索化二叉树以找根结点的前驱结点和后继结点的函数中,误以为二叉树的根结点一直都由指针p指向,但其实p只是指向子树的根结点,并非是整棵二叉树的根结点。

解决措施:将整个二叉树的根结点用tb->lchild表示,将判断根结点的前驱结点的判断语句改为:

if (p->rchild == tb->lchild)               //若该结点的右线索指向根结点

   printf("根结点%c的前驱结点为:%c", tb->lchild->data, p->data);  //访问该结点(根结点的前驱结点)

总结:在程序设计过程中需要明确各变量之间的关系。

2、在遍历线索化二叉树以找根结点的前驱结点和后继结点的函数中,采用了遍历完整棵二叉树的方法来寻找根结点的前驱结点和后继结点,但这其实是不高效的。因为很有可能在整棵二叉树之前就已经找到了根结点的前驱和后继结点。

解决措施:设置两个bool类型的变量,初始时为0,若找到了根结点的前驱或后继则其中的一个变量置为1,并终止遍历的操作。

bool flag1, flag2;               //判断是否找到了根结点的前驱和后继

flag1 = flag2 = 0;

if (flag1 && flag2)         //找到了根结点的前驱和后继,终止遍历操作

break;

总结:在循环语句块中,需要考虑终止条件与目的的关系,若在循环过程中达到了目的,则可以提前跳出循环,以提高程序效率。

3、在调试过程中发现:二叉树A(,B(,C(D(,E))))线索化后,其根结点无前驱结点;二叉树A(B(,C))线索化后,其根结点无后继结点。但在输出结果中并未显示这些内容。

解决措施:在遍历线索化二叉树找根结点的前驱结点的函数中增加如下代码:

if (flag1 == 0)

cout << "根结点" << tb->lchild->data << "无前驱结点" << endl;

if (flag2 == 0)

cout << "根结点" << tb->lchild->data << "无后继结点" << endl;

进行输出,提示该二叉树根结点无前驱结点或无后继结点

4、在调试过程中发现,若对空二叉树进行线索化后,输出该二叉树根结点的前驱结点和后继结点时会出现乱码

原因:对空二叉树进行线索化后,其头结点的lchild指向自己,rchild指向根结点。随后若是对空二叉树进行遍历,遍历为空,随后执行:

if (flag1 == 0)

cout << "根结点" << tb->lchild->data << "无前驱结点" << endl;

if (flag2 == 0)

cout << "根结点" << tb->lchild->data << "无后继结点" << endl;

此时tb->lchild为tb,tb的data域未初始化,所以就会乱码。

解决措施:空子树没有根结点,故需对上述代码进如下改进:

if (tb->lchild == tb)                             //二叉树为空时

cout << "二叉树为空,无根结点,更没有根结点的前驱结点和后继结点" << endl;

else                                              //二叉树不为空时

{

if (flag1 == 0)                              //找不到根结点的前驱结点

cout << "根结点" << tb->lchild->data << "无前驱结点" << endl;

if (flag2 == 0)                              //找不到根结点的后继结点

cout << "根结点" << tb->lchild->data << "无后继结点" << endl;

}

总结:很多函数和方法都需要用大量的案例进行调试,若是没有使用空二叉树对遍历线索化二叉树找根结点的前驱结点和后继结点的函数进行调试,则该函数不能适用多种二叉树情形。

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

1、在声明字符型数组时,若字符为“’”时,需要在字符前添加“\”进行转义,否则程序报错。例如:’\’’。

2、进行文件读写操作时,应该区分写入文件时是一个字符一个字符写入,还是一个字符串写入。前者使用函数put(char);后者使用函数write(char*,int)

3、在根据读取到的文件的内容进行写入文件的过程中,判断读取到的字符是否与某个结点的data域相同,若是相同则输出该结点对应的哈夫曼编码。但是如果使用switch case语句,case后面加的只能是常量。这样子就会固定死了程序,程序所能应对的场景就会很有限。

解决办法:

改为for语句和if语句进行判断:在哈夫曼树的所有的叶子结点中寻找data域与字符相同的,并将该结点对应的哈夫曼编码写入文件中

4、程序无法对文件1中的内容进行转换为哈夫曼编码并输出,输出结果为空

经过设置断点后并运行程序后发现,文件1在执行写入操作后,再执行读取操作时,读取到的第一个字符并非写入的第一个字符:“T”而是“\0”。

解决办法:增加如下语句

f1.seekg(ios::beg);                           //重置数据流指针,将流指针指向文件头

原因:在一般情况下,流对象打开文件时,指针会自动指向文件头。但如果流对象是重用的,就必须确定流指针的位置。在执行完文件写入操作后,文件的流指针指向最后一个字符之后,需要使用上述语句重新设置流指针的位置,方可以读取写入文件的第一个字符。

5、在获取哈夫曼树中的根结点时写成了:   HTNode root = ht[m];

结果运行结果如下:

原因:犯了低级错误,数组越界。声明哈夫曼树结点的数组的语句为:   HTNode ht[m];

这里的m为数组内容的个数,数组的下标的范围为0~m-1

解决办法:

将获取哈夫曼树根结点的语句改为:HTNode root = ht[m-1];

修改后程序的运行结果如下:

总结:使用数组时要明确数组的下标范围,避免使用数组是越界访问!

六、实验结果及分析

截屏的实验结果和结果分析

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

实验结果截屏:

度数为2的二叉树(老师的示范案例)

空二叉树

单个结点的二叉树

度数为1的二叉树

度数为2的二叉树

实验结果分析:

  1. 上述程序根据main函数中给定的字符串创建二叉树的链式存储结构,可遍历输出二叉树,输出二叉树某一结点的地址、左右孩子结点。若给定结点值不在二叉树中,程序也会提示。
  2. 上述程序可以输出二叉树的相关参数:结点数、叶子结点数、树的高度、二叉树的度。
  3. 上述程序可以采用递归和非递归的方式遍历二叉树,递归与非递归的遍历结果相同。

对该结果的评价:

1、此程序的结果是正确的

2、在main函数中多声明几种不同类型的二叉树其中包括:空二叉树、单个结点的二叉树、左子树为空的二叉树、右子树为空的二叉树、左右字数都不空的二叉树,以检测函数功能的正确性.

(2)实现线索二叉树的遍历

实验结果截屏:

度数为2的二叉树老师给的示范案例

空二叉树

单个结点的二叉树

度数为1的二叉树

度数为2的二叉树

实验结果分析:

1、二叉树A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))中根结点A的前驱结点为:E;后继结点为:F

2、空二叉树无根结点,更没有根结点的前驱结点和后继结点。

3、有点二叉树的根结点同时具备前驱结点和后继结点,而有的二叉树的根结点只有前驱结点而没有后继结点;有的二叉树的根结点只有后继结点而没有前驱结点。

对该结果的评价:

1、此程序的结果是正确的

2、此程序考虑到了空树、单结点二叉树、度为1的二叉树的情况,并能够对不同的二叉树进行不同的处理,具有很强的容错性、兼容性。

3、改进之处:若是可以使用函数模板和结构体模板对之前的程序进行更改从而做到两种二叉树结点类型能够同时存在,并且可以设置更多的二叉树结点类型,则会使该程序的兼容性更强,并且更容易在后期增设不同类型的结点。

(3)哈夫曼树和哈夫曼编码的算法实现

实验结果截屏:

实验结果分析:

1、运行结果显示了哈夫曼树结点数组的内容,并且显示了哈夫曼编码表。显示了需要压缩的英文文段内容、压缩后的0、1字符,解压后的英文文段内容。

2、此程序能够将需要压缩的英文文段内容、压缩后的0、1字符,解压后的英文文段内容写入指定文件中。

对该结果的评价:

1、该程序正确地进行了英文文段的压缩与解压。

2、此程序中英文文段不同字符个数和每个字符的出现频率为手工计算出来的,若是可以设计一个算法,将给定任意文段的内容进行计算不同字符个数和每个字符的出现频率,则此程序会更加完美。

实验思考题

根据哈夫曼树求哈夫曼编码的解决方案,可否通过堆栈,利用二叉树的某种遍历来求得?若能,请编写程序来说明,若不能,请举例说明。

解答:

可以。可以使用二叉树中的先序遍历。(偷懒,没有给出了程序哈哈哈)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值