广州大学数据结构实验二

实验二 二叉树的操作与实现

开课实验室:计算机科学与工程实验(电子楼)       2020年12月5日

学院

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

年级、专业、班

网络工程194

姓名

jwt

学号

实验课程

数据结构实验

成绩

实验项目

实验二 二叉树的操作与实现

指导老师

一、实验目的

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

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

3、线索二叉树的遍历

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

二、使用仪器、器材

操作系统:Win10

编程软件:C++

三、实验内容及原理

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

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

思路:字符串中有较多的括号,考虑到用上一节的链栈来处理括号匹配类的问题。

若有左括号,则表示刚创建的结点存在孩子结点,则将其压进栈作为栈顶结点;若有逗号(逗号后都是紧随字母)则表明有右子树;若有右括号,则表明以栈顶结点为根节点的子树创建完毕,将其出栈。

(2) 输出该二叉树;

思路:采用递归方式的先序遍历。

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

思路:依旧采用递归方式的先序遍历查找相应结点,找到后输出它的子树

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

思路:

结点个数:先扫描左子树,每有一个结点就+1,再扫描右子树,最后是根节点+1

叶子结点个数:递归方式的先序遍历,判断该结点是否没有左右子树,若没有则为叶子结点+1,然后递归。

二叉树的度:已知二叉树的度<=2,若树为空或者没有子树,则返回0;若二叉树的根节点有左右子树,直接返回2;若二叉树的根节点仅有左子树或右子树,则定义个变量d,初始值为1,然后递归判断,将返回的最大值赋予d,最后返回d

二叉树的高度:递归判断左右子树的高度谁大,取最大者再+1(根节点)

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

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

思路(非递归):

先序:由于在二叉链中左右子树是通过根节点的指针域指向的,在访问根节点后遍历左子树时会丢失右子树的地址,所以需要一个栈来临时保存左右子树的地址。又因为栈的特点是先进先出,所以应该先将右孩子进栈,再将左孩子进栈。

中序:将根节点及左下结点依次进栈,但还不能访问,因为它们的左子树还没有遍历。当达到根节点的最左下结点时它是中序序列的开始结点,也是栈顶结点,出栈并访问它,然后转向它的右子树,依此类推。

后序:将根节点及左下结点依次进栈,即使栈顶结点p的左子树已遍历或为空,仍还不能访问结点p,因为它们的右子树还没遍历,只有当p结点的右子树已遍历完才能访问结点p。

3、线索二叉树的遍历

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

思路:如果访问到当前节点的时候,线索化其左子树并记录下上一个访问的节点;再对上一个访问的节点的右子树进行线索化,直到所有节点都访问完。

查找前驱:如果ltag=1,直接找到前驱,如果ltag=0,则走到该结点左子树的最右边的结点,即为要寻找的结点的前驱;查找后继:如果rtag=1,直接找到后继,如果rtag=0,则走到该结点右子树的最左边的结点,即为要寻找的结点的后继。

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

统计下面一段英文的不同字符个数和每个字符的出现频率,利用统计数据构造构造哈夫曼树和哈夫曼编码。

    思路:用map容器统计字符与出现次数,以出现次数作为权值;用一个函数找出双亲值为0,且权重最小的子树分别作为左右子树构造一棵新的二叉树,并且置新的二叉树的根节点的权值为左右子树上根的权值之和;在森林中,用新得到的二叉树代替这两棵树;重复上述步骤,直到只含有一棵树为止,这棵树就是哈夫曼树。

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

1、2题代码

typedef struct BiNode //二叉链表定义
{
	char data;//数据域
	struct BiNode *lchild, *rchild;//左右孩子
}BiTNode, *BiTree;
typedef struct
{
	BiNode *data[MAX];//存放栈中的数据
	int top;//存放栈顶指针
}SNode,*LinkStack;
void createBiTree(BiTree &bt, char *str)//创建二叉树
{
	BiTree stack[MAX], p;//数组作为顺序栈
	int index = -1;//index负责记录下标,此时为-1代表栈为空
	int i = 0, j = 0;//i为0时,创建左子树,i为1时,创建右子树;j记录字符下标
	bt = NULL;//初始二叉链为空
	char ch;
	ch = str[j];
	while (ch != '\0')//循环扫描str的每个字符
	{
		switch (ch)
		{
		case '(':
		{
					index++;
					stack[index] = p;//p进栈
					i = 0;//可能有左孩子
					break;
		}
		case ')'://栈顶结点的子树处理完毕
			index--;
			break;
		case ',':
			i = 1;//逗号后边是右孩子
			break;
		default:
		{
				   p = new BiNode;//创建一个新结点
				   p->data = ch;//存放节点值
				   p->lchild = NULL;
				   p->rchild = NULL;
				   if (bt == NULL)//如果是空树
					   bt = p;
				   else  //若已经存在根节点
				   {
					   if (i == 0)
						   stack[index]->lchild = p;
					   if (i == 1)
						   stack[index]->rchild = p;
				   }
				   break;
		}
		}
		j++;
		ch = str[j];
	}
}
void preOrder(BiTree bt)//先序遍历(非递归)
{
	BiTNode *p=NULL;//临时结点用于存储二叉树结点
	LinkStack s;//定义栈指针
	initStack(s);//初始化栈
	if (bt != NULL)
	{
		push(s, bt);//根节点进栈
		while (!emptyStack(s))//栈非空时循环
		{
			pop(s, p);//栈顶元素出栈,用p存储它
			cout << p->data << "  ";
			if (p->rchild)//若有右孩子,根据栈的先进后出,右孩子先进栈
				push(s, p->rchild);
			if (p->lchild)//若有左孩子,左孩子后进栈
				push(s, p->lchild);
		}
		cout << endl;
	}
	destroyStack(s);//销毁临时栈
}
void preOrderBiTree(BiTree bt) //先序遍历输出二叉树(递归)
{
	if (bt)
	{
		cout << bt->data << "  ";//输出根结点
		preOrderBiTree(bt->lchild);//先序遍历左子树
		preOrderBiTree(bt->rchild);//先序遍历右子树
	}
}
void inOrder(BiTree bt)//中序遍历(非递归)
{
	BiTNode *p = NULL;
	LinkStack s;//定义栈指针
	initStack(s);//初始化栈
	p = bt;//p指向根节点
	while (!emptyStack(s) || p != NULL)
	{
		while (p != NULL)//所有左下结点进栈,并找到最左边的结点
		{
			push(s, p);//p进栈
			p = p->lchild;//p指向左孩子
		}
		if (!emptyStack(s))
		{
			pop(s, p);//栈顶元素出栈,用p存储它
			cout << p->data << "  ";
			p = p->rchild;//p指向右子树
		}
	}
	cout << endl;
	destroyStack(s);//销毁临时栈
}
void inOrderBiTree(BiTree bt) //中序遍历输出二叉树(递归)
{
	if (bt)
	{
		inOrderBiTree(bt->lchild);//输出左子树的叶子结点
		cout << bt->data << "  ";//输出根结点
		inOrderBiTree(bt->rchild);//中序遍历右子树
	}
}
void postOrder(BiTree bt)//后序遍历(非递归)
{
	BiTNode *p = NULL,*r=NULL;
	bool temp;
	LinkStack s;//定义栈指针
	initStack(s);//初始化栈
	p = bt;//p指向根节点
	do
	{
		while (p != NULL)//所有左下结点进栈,并找到最左边的结点
		{
			push(s, p);//p进栈
			p = p->lchild;//p指向左孩子
		}
		r = NULL;//r指向刚访问的结点,初始为空
		temp = true;//temp为真表示正在处理栈顶结点
		while (!emptyStack(s) && temp)
		{
			getTop(s, p);//取出栈顶元素
			if (p->rchild == r)//若右孩子为空或者为刚访问过的结点
			{
				cout << p->data << "  ";
				pop(s, p);
				r = p;//r指向刚访问过的结点
			}
			else
			{
				p = p->rchild;//p指向右子树
				temp = false;//表示当前不是处理栈顶结点
			}
		}
	} while (!emptyStack(s));
	cout << endl;
	destroyStack(s);
}
void postOrderBiTree(BiTree bt) //后序遍历输出二叉树(递归)
{
	if (bt)
	{
		postOrderBiTree(bt->lchild);//输出左子树的叶子结点
		postOrderBiTree(bt->rchild);//后序遍历右子树
		cout << bt->data << "  ";//输出根结点
	}
}
void findBiTree(BiTree bt,char c) //寻找结点,并输出它的孩子
{
	if (bt != NULL)
	{
		if (bt->data == c)//匹配到相应的结点
		{
			if (bt->lchild&&bt->rchild)
				cout << "左孩子: " << bt->lchild->data << "\t右孩子: " << bt->rchild->data << endl;
			else if (bt->lchild&&bt->rchild == NULL)
				cout << "左孩子: " << bt->lchild->data << endl;
			else if (bt->rchild&&bt->lchild == NULL)
				cout << "右孩子: " << bt->rchild->data << endl;
			else
				cout << "该结点没有子树" << endl;
			return;
		}
		findBiTree(bt->lchild,c);
		findBiTree(bt->rchild,c);
	}
}
int nodesBiTree(BiTree bt)//计算二叉树的结点个数
{
	if (bt == NULL)
		return 0;
	else
		return nodesBiTree(bt->lchild) + nodesBiTree(bt->rchild) + 1;//左+右+根节点
}
int leafBiTree(BiTree bt)//计算二叉树的叶子结点个数
{
	if (bt != NULL)
	{
		if (bt->lchild == NULL&&bt->rchild == NULL)//找叶子结点
			return 1;
		else
			return leafBiTree(bt->lchild) + leafBiTree(bt->rchild);
	}
	else
		return 0;
}
int duBiTree(BiTree bt)//计算二叉树的度
{
	if (bt == NULL || (bt->lchild == NULL&&bt->rchild == NULL))//若为空树或只有根结点
		return 0;
	else if (bt->lchild&&bt->rchild)//若根结点有左右子树,直接返回2,因为二叉树的度<=2
		return 2;
	else
	{
		int d = 1;
		if (bt->lchild)//只有左子树
		{
			if(!(bt->lchild->lchild == NULL&&bt->lchild->rchild == NULL))//若该结点不是叶子结点
			d = (duBiTree(bt->lchild));
		}
		else
		{
			if (!(bt->rchild->lchild == NULL&&bt->rchild->rchild == NULL))//若该结点不是叶子结点
			d = (duBiTree(bt->rchild));
		}
		return d;
	}
}
int DepthBiTree(BiTree bt)//计算二叉树的高度
{
	int l, r;
	if (bt == NULL) return 0;        //如果是空树,高度为0,递归结束
	else
	{
		l = DepthBiTree(bt->lchild);			//递归计算左子树的高度记为m
		r = DepthBiTree(bt->rchild);			//递归计算右子树的高度记为n
		if (l>r) return(l + 1);		//二叉树的深度为m 与n的较大者加1
		else return (r + 1);
	}
}

第3题代码

typedef struct node
{
	char data;
	int ltag, rtag;
	struct node * lchild, *rchild;
}BTNode, *BiThrTree;
void InThreading(BiThrTree &p)
{
	//pre是全局变量,初始化时其右孩子指针为空,便于在树的最左点开始建线索
	if (p)
	{
		InThreading(p->lchild);             			//左子树递归线索化
		if (!p->lchild)
		{                   							//p的左孩子为空
			p->ltag = 1;                                 	//给p加上左线索
			p->lchild = pre; 								//p的左孩子指针指向pre(前驱)		
		}
		else
			p->ltag = 0;
		if (!pre->rchild)
		{												//pre的右孩子为空
			pre->rtag = 1;                   				//给pre加上右线索
			pre->rchild = p;                     			//pre的右孩子指针指向p(后继)
		}
		else
			pre->rtag = 0;
		pre = p;                       					//保持pre指向p的前驱
		InThreading(p->rchild);               			//右子树递归线索化
	}
}//InThreading
void InOrderThreading(BiThrTree &Thrt, BiThrTree bt)
{
	//中序遍历二叉树T,并将其中序线索化,Thrt指向头结点
	Thrt = new BTNode;          		//建头结点
	Thrt->ltag = 0;                 		//头结点有左孩子,若树非空,则其左孩子为树根
	Thrt->rtag = 1;               		//头结点的右孩子指针为右线索
	Thrt->rchild = Thrt;            		//初始化时右指针指向自己
	if (!bt)  Thrt->lchild = Thrt;      	//若树为空,则左指针也指向自己
	else
	{
		Thrt->lchild = bt;  pre = Thrt; 	 	//头结点的左孩子指向根,pre初值指向头结点
		InThreading(bt);              		//对以T为根的二叉树进行中序线索化
		pre->rchild = Thrt;          	  	//pre为最右结点,pre的右线索指向头结点
		pre->rtag = 1;
		Thrt->rchild = pre;             	//头结点的右线索指向pre
	}
}
void InOrderOutputThr(BiThrTree bt)//输出线索化二叉树
{
	//T指向头结点,头结点的左链lchild指向根结点
	//中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出
	BiThrTree p;
	p = bt->lchild;                             		//p指向根结点
	while (p != bt)										//空树或遍历结束时,p==T
	{
		while (p->ltag == 0)							//沿左孩子向下
			p = p->lchild;							//访问其左子树为空的结点
		cout << p->data;
		while (p->rtag == 1 && p->rchild != bt)
		{
			p = p->rchild;							//沿右线索访问后继结点
			cout << p->data;
		}
		p = p->rchild;
	}
	cout << endl;
}
/*中序线索二叉树找前驱*/
BiThrTree findPre(BiThrTree bt) 
{
	BiThrTree q;
	q = bt;
	if (q->ltag == 1) 
	{
		q = q->lchild;
		return q;
	}
	else
	{
		q = q->lchild;//进入左子树
		while (q->rtag == 0)//找到左子树的最右边结点
		{
			q = q->rchild;
		}
		return q;
	}
}

/*中序线索二叉树找后继*/
BiThrTree findNext(BiThrTree bt) 
{
	BiThrTree next = bt;
	if (next->rtag == 1) 
	{
		next = next->rchild;
		return next;
	}
	else 
	{
		//进入到*next的右子树
		next = next->rchild;
		while (next->ltag == 0) 
		{
			next = next->lchild;
		}
		return next;
	}
}

第4题代码

typedef struct  
{
	int weight;//权重
	int parent;//双亲结点
	int lchild;//左孩子结点
	int rchild;//右孩子结点
}Huffmantree;
void select(Huffmantree *&ht, int i, int &s1, int &s2)//找出哈夫曼树表最小的两个数
{
	int j = 1;
	int k = 1;
	while (ht[k].parent != 0)//找出双亲为0的标号
		k++;
	s1 = k;
	for (j = 1; j <= i; j++)
	{
		//找出最小值
		if (ht[j].parent == 0 && ht[j].weight <= ht[s1].weight)
			s1 = j;
	}
	k = 1;
	while (ht[k].parent != 0 || k == s1) 
		k++;
	s2 = k;
	for (j = 1; j <= i; ++j)
	{
		//找出第二小值
		if (ht[j].parent == 0 && ht[j].weight <= ht[s2].weight&&j != s1)
			s2 = j;
	}
}
void CreatHuffmantree(Huffmantree *&ht, int n, map<char, int>tmp)
{
	if (n <= 1) //空树
		 return;
	int m = 2 * n - 1;//有n个叶子结点,则总共有2*n-1个结点
	int s1;
	int s2;
	ht = new Huffmantree[m + 1];//创建哈夫曼表
	for (int i = 1; i <= m; i++)//初始化
	{
		//将哈夫曼表的双亲,左右子树均置0
		ht[i].parent = 0;
		ht[i].lchild = 0;
		ht[i].rchild = 0;
	}
	int i = 1;
	for (auto e : tmp)
	{
		//将字符出现频率作为权重输入哈夫曼表
		ht[i].weight = e.second;//e.second为字符出现频率
		i++;
	}
	for (i = n + 1; i <= m; i++)
	{
		select(ht, i - 1, s1, s2);//选取出s1,s2两个最小值
		ht[s1].parent = i;//置双亲为i
		ht[s2].parent = i;
		ht[i].lchild = s1;//输入i的左右子树
		ht[i].rchild = s2;
		ht[i].weight = ht[s1].weight + ht[s2].weight;//值i的权重
	}
}
void CreateHuffmanCode(Huffmantree *ht, vector<string> &hc, int index, int n)//创建哈夫曼编码
{
	while (ht[n].parent != 0)
	{
		//当结点有双亲时
		int temp = ht[n].parent;//记录结点双亲位置
		if (ht[temp].lchild == n)//判断结点是属于双亲的左节点还是右结点,若为左子树,则编码置0
			hc[index].insert(hc[index].begin(), '0');
		else if (ht[temp].rchild == n)//若为右子树,编码置1
			hc[index].insert(hc[index].begin(), '1');
		n = ht[n].parent;//将结点位置置为双亲位置
	}
}
int _tmain(int argc, _TCHAR* argv[])
{
	map<char, int>tmp;//map容器用于统计字符与出现次数
	string data = "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.";
	for (auto e : data)//对英文段落统计,统计所出现的字符以及相应的出现次数
	{
		tmp[e]++;
	}
	cout << "字符统计结果:" << endl;
	cout << "字符" <<  "\t出现次数" << endl;
	vector<char> letter;//字符出现次数记录到vector
	letter.push_back(' ');
	for (auto e : tmp)
	{
		cout << e.first << '\t' << e.second << endl;//e.first为字符,e.second为字符出现的次数
		letter.push_back(e.first);
	}//输出文段统计结果,并把字符记录到vector中
	Huffmantree *ht;
	CreatHuffmantree(ht, tmp.size(), tmp);//创建哈夫曼树
	cout << endl << "创建的哈夫曼树为: " << endl;
	cout << "结点i" << "\tweight" << "\tparent" << "\tlchild" << "\trchild" << endl;
	for (int i = 1; i < tmp.size() * 2; i++)//输出哈夫曼树表
	{
		if (i <= tmp.size())
			cout << i << "\t" << ht[i].weight << "\t" << ht[i].parent << "\t" << ht[i].lchild << "\t" << ht[i].rchild << "\t" << letter[i] << endl;
		else
			cout << i << "\t" << ht[i].weight << "\t" << ht[i].parent << "\t" << ht[i].lchild << "\t" << ht[i].rchild << endl;
	}
	vector<string> hc(tmp.size() + 1, "");//建立vector存放哈夫曼编码,将string全初始化为空串
	cout << endl << "各个字符的哈夫曼编码如下:" << endl;
	for (int i = 1; i <= tmp.size(); i++)//输出哈夫曼编码
	{
		CreateHuffmanCode(ht, hc, i, i);//创建哈夫曼编码
		cout << letter[i] << ":" << hc[i] << endl;//letter[i]为出现的字符,hc[i]为字符对应的哈夫曼编码
	}
	return 0;
}

五、实验结果及分析

测试结果:正确

在主函数中创建了三个二叉树A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))、A(B(,E(H(,K(,M(,N))))))、A分别用于测试先序、中序、后序遍历还有各个计算结点数、叶子结点数、二叉树的度和高度的函数。均正确。图略。

测试结果:正确

在主函数中先创建普通二叉树,然后将二叉树中序线索化,并输出线索化后的二叉树。由结论得中序线索化二叉树头结点的左孩子就是树根,所以找根节点的前驱和后继传参数时直接用th->lchild,测试正确。图略。

测试结果:正确

在主函数中用了map容器来统计字符与字符的出现次数,再将容器里的内容输出;然后把字符记录到vector容器1。调用函数创建哈夫曼树并输出创建的哈夫曼树.最后再用一个vector容器2储存哈夫曼编码,并输出容器的内容,结果正确。

总结:

  1. 创建二叉树时要注意用什么条件判断新结点作为左孩子还是右孩子,在写代码时用了一个变量i,当读取到字符‘(‘时,i置为0,读取到‘,’时,i置为1,因为左括号表明该结点有孩子,并且可能是左孩子;而逗号后边跟的一定是右孩子。
  2. 计算二叉树的度时,用了递归方式。注意到二叉树的度<=2,在判断到树为空树或者树只有根节点时直接返回度为0;当根节点左右孩子都有时直接返回2;如果根节点只有一个孩子,则递归下去,判断子树中是否有更大的度,最终返回最大值。
  3. 在使用非递归算法遍历时,(如先序遍历)要注意访问根节点后遍历左子树时会丢失右子树的地址,所以应用了栈来临时保存左右子树的地址。同时要注意3种遍历的顺序,对应的进栈顺序也不同。
  4. 在第四题创建哈夫曼树中,统计字符及出现次数用了map容器,因为这样比较方便快捷,也用了vector容器来存储字符(方便新字符插入)。用一个函数找出双亲值为0,且权重最小的子树分别作为左右子树构造一棵新的二叉树,并且置新的二叉树的根节点的权值为左右子树上根的权值之和;在森林中,用新得到的二叉树代替这两棵树;重复上述步骤,直到只含有一棵树为止,这棵树就是哈夫曼树。

Pass:仅作为实验参考,有些地方可能不够完善,见谅一下。转载请注明出处!

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值