说明:本次实验分为数据结构(二叉树)的基本操作及实现、附加实验:基于哈夫曼树的数据压缩算法等两个部分。
一、实验名称
二叉树的基本算法
二、实验目的
1.理解二叉树的逻辑结构;
2.理解二叉树的存储结构特点,掌握二叉树的存储分配要点;
3.掌握二叉树的基本操作及递归实现,深刻领会二叉树遍历操作的非递归实现。
三、实验内容
实验具体内容
1. 二叉链表的存储结构
(1)二叉链表结点的存储结构:(私有)数据封装数据元素data、指向左孩子结点指针lchild、指向右孩子结点指针rchild。
(2)二叉链表的存储结构:数据(仅)封装二叉树根结点的指针root。
2. 二叉链表的基本操作
课内完成递归实现,非递归实现作为课后练习。
(1)初始化二叉链表:理解扩展二叉树及其前序遍历序列;
(2)前序遍历二叉链表(递归实现);
(3)中序遍历二叉链表(递归实现);
(4)后序遍历二叉链表(递归实现);
(5)层序遍历二叉链表;
(6)销毁二叉链表(递归实现)。
实验需求分析:
在理解二叉树、二叉链表逻辑结构,和二叉树、二叉链表的储存特点的基础上,设计、编写一段完整代码,该代码能够详细定义二叉树、二叉链表的储存结构,同时完成实验具体内容中所要求的二叉树、二叉链表基本操作。在此基础上,从算法思想及时间、空间复杂度的角度对代码进行优化,并获得关于“二叉树的基本算法”的最优化代码。
预期结果:
完成实验指导书中所要求的具体实验内容,并对其进行优化。
四、算法思想和时间复杂度
时间复杂度均为O(n)。
1. 递归实现(该部分在本实验报告中不做分析。)
2. 非递归实现:
(1)前序遍历
step1:只要当前指针不空,访问当前指针所指结点中数据元素后将当前指针入栈,然后将当前指针修改为其左孩子指针;
step2:重复step1直到遇到空指针;
step3:栈空则结束;栈不空,从栈中弹出指针,将其右孩子指针作为当前指针,转step1。
(2)中序遍历
step1:只要当前指针不空,将当前指针入栈,后然后将当前指针修改为其左孩子指针;
step2:重复step1直到遇到空指针;
step3:;栈空则结束;栈不空,从栈中弹出指针,访问该指针所指结点中数据元素,然后将其右孩子指针作为当前指针,转step1。
(3)后序遍历:栈结构中增加flag域
step1:只要当前指针不空,将当前指针入栈(flag置1),然后将当前指针修改为其左孩子指针;
step2:重复step1直到遇到空指针;
step3:栈空则结束;若栈不空
step3.1从栈中弹出指针,若flag为1,将flag改为2后将当前指针重新入栈,然后将其右孩子指针作为当前指针,转step1;
step3.2若flag为2,访问该指针所指结点中数据元素,转step3。
五、实验方法
//声明类BiTree及定义结构BiNode,文件名为bitree.h
#ifndef BITREE_H
#define BITREE_H
template <class T>
struct BiNode //二叉树的结点结构
{
T data;
BiNode<T> *lchild, *rchild;
};
template <class T>
class BiTree
{
public:
BiTree( ); //构造函数,初始化一棵二叉树,其前序序列由键盘输入
~BiTree( ); //析构函数,释放二叉链表中各结点的存储空间
void PreOrder( ){ PreOrder(root); } //前序遍历二叉树
void InOrder( ) { InOrder(root); } //中序遍历二叉树
void PostOrder( ) { PostOrder(root); } //后序遍历二叉树
void LeverOrder( ); //层序遍历二叉树
private:
BiNode<T> *root; //指向根结点的头指针
BiNode<T> *Creat( ); //有参构造函数调用
void PreOrder(BiNode<T> *bt); //前序遍历二叉树
void InOrder(BiNode<T> *bt); //中序遍历二叉树
void PostOrder(BiNode<T> *bt); //后序遍历二叉树
void Release(BiNode<T> *bt); //析构函数调用
};
#endif
//定义类中的成员函数,文件名为bitree.cpp
#include<iostream>
#include<string>
#include"bitree.h"
using namespace std;
/*
*前置条件:二叉树不存在
*输 入:无
*功 能:构造一棵二叉树
*输 出:无
*后置条件:产生一棵二叉树
*/
template<class T>
BiTree<T>::BiTree( )
{
root = Creat( );
}
/*
*前置条件:二叉树已存在
*输 入:无
*功 能:释放二叉链表中各结点的存储空间
*输 出:无
*后置条件:二叉树不存在
*/
template<class T>
BiTree<T>::~BiTree( )
{
Release(root);
}
/*
*前置条件:二叉树已存在
*输 入:无
*功 能:前序遍历二叉树
*输 出:二叉树中结点的一个线性排列
*后置条件:二叉树不变
*/
template<class T>
void BiTree<T>::PreOrder(BiNode<T> *bt)
{
if(bt==NULL) return;
else{
cout<<bt->data<<" ";
PreOrder(bt->lchild);
PreOrder(bt->rchild);
}
}
/*
*前置条件:二叉树已存在
*输 入:无
*功 能:中序遍历二叉树
*输 出:二叉树中结点的一个线性排列
*后置条件:二叉树不变
*/
template <class T>
void BiTree<T>::InOrder (BiNode<T> *bt)
{
if (bt==NULL) return; //递归调用的结束条件
else{
InOrder(bt->lchild); //中序递归遍历bt的左子树
cout<<bt->data<<" "; //访问根结点的数据域
InOrder(bt->rchild); //中序递归遍历bt的右子树
}
}
/*
*前置条件:二叉树已存在
*输 入:无
*功 能:后序遍历二叉树
*输 出:二叉树中结点的一个线性排列
*后置条件:二叉树不变
*/
template <class T>
void BiTree<T>::PostOrder(BiNode<T> *bt)
{
if (bt==NULL) return; //递归调用的结束条件
else{
PostOrder(bt->lchild); //后序递归遍历bt的左子树
PostOrder(bt->rchild); //后序递归遍历bt的右子树
cout<<bt->data<<" "; //访问根结点的数据域
}
}
/*
*前置条件:二叉树已存在
*输 入:无
*功 能:层序遍历二叉树
*输 出:二叉树中结点的一个线性排列
*后置条件:二叉树不变
*/
template <class T>
void BiTree<T>::LeverOrder()
{
const int MaxSize = 100;
int front = -1;
int rear = -1; //采用顺序队列,并假定不会发生上溢
BiNode<T>* Q[MaxSize];
BiNode<T>* q;
if (root==NULL) return;
else{
Q[++rear] = root;
while (front != rear)
{
q = Q[++front];
cout<<q->data<<" ";
if (q->lchild != NULL) Q[++rear] = q->lchild;
if (q->rchild != NULL) Q[++rear] = q->rchild;
}
}
}
/*
*前置条件:空二叉树
*输 入:数据ch;
*功 能:初始化一棵二叉树,构造函数调用
*输 出:无
*后置条件:产生一棵二叉树
*/
template <class T>
BiNode<T>* BiTree<T>::Creat( )
{
BiNode<T>* bt;
T ch;
cout<<"请输入创建一棵二叉树的结点数据"<<endl;
cin>>ch;
if (ch==’#’) bt = NULL;
else{
bt = new BiNode<T>; //生成一个结点
bt->data=ch;
bt->lchild = Creat( ); //递归建立左子树
bt->rchild = Creat( ); //递归建立右子树
}
return bt;
}
/*
*前置条件:二叉树已经存在
*输 入:无
*功 能:释放二叉树的存储空间,析构函数调用
*输 出:无
*后置条件:二叉树不存在
*/
template<class T>
void BiTree<T>::Release(BiNode<T>* bt)
{
if (bt != NULL){
Release(bt->lchild); //释放左子树
Release(bt->rchild); //释放右子树
delete bt;
}
}
void main()
{
BiTree<char> bt; //创建一棵树
cout<<"------前序遍历------ "<<endl;
bt.PreOrder( );
cout<<endl;
cout<<"------中序遍历------ "<<endl;
bt.InOrder( );
cout<<endl;
cout<<"------后序遍历------ "<<endl;
bt.PostOrder( );
cout<<endl;
cout<<"------层序遍历------ "<<endl;
bt.LeverOrder( );
cout<<endl;
}
六、附:程序代码
menu.h
#pragma once
#ifndef MENU_H_INCLUDED
#define MENU_H_INCLUDED
void menu() {
std::cout << "\n";
std::cout << " **********************二叉树的应用**********************\n";
std::cout << " * *\n";
std::cout << " * a:(先中)创建二叉树 b:先序创建二叉树 *\n";
std::cout << " * c:先序遍历 d:中序遍历 *\n";
std::cout << " * e:后序遍历 f:二叉树的节点数 *\n";
std::cout << " * g:打印二叉树(凹入型) h:退出程序 *\n";
std::cout << " * *\n";
std::cout << " ********************************************************\n";
}
#endif // MENU_H_INCLUDED
tree.h
#pragma once
typedef struct node
{
datatype data;
struct node* lchild, * rchild;
}treenode, * treelist;
//树初始化
void treeinit(treelist& bt)
{
bt = NULL;
}
treelist treecreate4(char* p, char* m, int len) //根据先序序列和中序序列创建二叉树;
{
//*P存放先序序列,*m存放中序序列,len为m中字符个数;
treelist s;
char* q;
int k;
if (len <= 0)
return NULL;
s = new treenode(); //创建二叉树结点s;
s->data = *p;
figure++;
for (q = m; q < m + len; q++) //在中序序列中找等于*p的位置k;
{
if (*q == *p)
break;
}
k = q - m;
s->lchild = treecreate4(p + 1, m, k); //递归构造左子树;
s->rchild = treecreate4(p + k + 1, q + 1, len - k - 1); //递归构造右子树;
return s;
}
treelist treecreate() //先序创建二叉树
{
treelist s;
char c;
std::cin >> c;
if (c == '-') {
s = NULL;
}
else {
s = new treenode();
s->data = c;
figure++; //构造根结点
s->lchild = treecreate(); //构造左子树
s->rchild = treecreate(); //构造右子树
}
return s;
}
//先序遍历算法
void DLR(node* root)
{
if (root != NULL) //非空二叉树
{
cout << root->data; //访问D
DLR(root->lchild); //递归遍历左子树
DLR(root->rchild); //递归遍历右子树
}
}
//中序遍历算法
void LDR(node* root)
{
if (root != NULL)
{
LDR(root->lchild);
cout << root->data;
LDR(root->rchild);
}
}
//后序遍历算法
void LRD(node* root)
{
if (root != NULL)
{
LRD(root->lchild);
LRD(root->rchild);
cout << root->data;
}
}
void printInorder(node* root, int height, string to, int len)
{
if (NULL == root)
return;
printInorder(root->rchild, height + 1, "_", len);//先打印右子树
//打印跟节点
stringstream ss;
ss << root->data;
string val = to + ss.str() + to;
int lenM = val.length();
int lenL = (len - lenM) / 2;
int lenR = len - lenM - lenL;
val = string(lenL, ' ') + val + string(lenR, ' ');
std::cout << string(height * len, ' ') << val << std::endl;
printInorder(root->lchild, height + 1, "_", len);
return;
}
void printTree(node* root)
{
cout << "Binary Tree:" << endl;
printInorder(root, 0, "_", 5);
cout << endl;
}
main.cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sstream>
using namespace std;
#define maxsize 100
#define infinity 10000 /*定义一个无限大的值*/
typedef char datatype;
int figure = 0;//所含结点个数
#include "menu.h"
#include "tree.h"
typedef treelist elem;
char str1[100], str2[100];
int i, j, length;//lever表示层次,h高度
treelist bt;
int main()
{
treeinit(bt);
void execute();
execute();
return 0;
}
void execute()
{
char a;
for (;;)
{
menu();
cin >> a;
switch (a)
{
case 'a':
cout << "请输入先序序列和中序序列:" << endl;
cin >> str1 >> str2;
length = strlen(str1);
bt = treecreate4(str1, str2, length);
break;
case 'b':
cout << "请输入先序序列:" << endl;
bt = treecreate();
break;
case 'c':
cout << "先序序列为:" << endl;
DLR(bt);
break;
case 'd':
cout << "中序序列为:" << endl;
LDR(bt);
break;
case 'e':
cout << "后序序列为:" << endl;
LRD(bt);
break;
case 'f':
cout << "二叉树的节点数为:" << endl;
cout << figure << endl;
break;
case 'g':
cout << "打印树(凹入式):" << endl;
printTree(bt);
break;
case 'h':
cout << " 再见!" << endl;
return;
default:
cout << "输入的字符有误,请重新输入!!" << endl;
break;
}
}
}
七、附加实验三:基于哈夫曼树的数据压缩算法
(一)实验名称
基于哈夫曼树的数据压缩算法
(二)实验目的
1.掌握哈夫曼树的构造算法;
2.掌握哈夫曼编码的构造算法。
(三)实验内容
问题描述
输入一串字符串,根据给定的字符串中字符出现的频率建立相应的哈夫曼树,构造哈夫曼编码表,在此基础上可以对压缩文件进行压缩(即编码),同时可以对压缩后的二进制编码文件进行解压(即译码)。
输入要求
多组数据,每组数据1行,为一个字符串(只考虑26个小写字母即可)。当输人字符串为“0”时,输入结束。
输出要求
每组数据输出2n+3行(n为输入串中字符类别的个数)。第1行为统计出来的字符出现频率(只输出存在的字符,格式为:字符:频度),每两组字符之间用一个空格分隔,字符按照ASCII码 从小到大的顺序排列。第2行至第2n行为哈夫曼树的存储结构的终态(如主教材139页表5.2(b), 一行当中的数据用空格分隔)。第2n+1行为每个字符的哈夫曼编码(只输出存在的字符,格式为:字符:编码),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第2n+2行为编码后的字符串,第2n+3行为解码后的字符串(与输人的字符串相同)。
(四)实验结论
(五)附:程序源代码
#include<iostream>
#include<map>
#include<string>
#include<stdio.h>
#include<memory.h>
using namespace std;
typedef struct {
char c;
int weight;
int lchild, rchild, parent;
}HuffmanNode, * HuffmanTree;
typedef struct {
char ch;
char* pHC;
}HuffCodeNode;
typedef HuffCodeNode* HuffmanCode;
void Select(HuffmanTree& HT, int index, int& s1, int& s2)
{
int i;
for (i = 1; i <= index; i++)
if (HT[i].parent == 0)
break;
s1 = i;
for (int j = s1 + 1; j <= index; j++)
if (HT[j].parent == 0 && HT[j].weight < HT[s1].weight)
s1 = j;
int k;
for (k = 1; k <= index; k++)
if (HT[k].parent == 0 && k != s1)
break;
s2 = k;
for (int j = s2 + 1; j <= index; j++)
if (HT[j].parent == 0 && HT[j].weight < HT[s2].weight && j != s1)
s2 = j;
}
int CreateHuffmanTree(HuffmanTree& HT, string str)
{
map<char, int> mymap;
for (int i = 0; i < str.length(); i++)
mymap.find(str[i]) != mymap.end() ? mymap[str[i]]++ : mymap[str[i]] = 1;
for (auto it = mymap.begin(); it != mymap.end(); it++)
cout << it->first << ":" << it->second << " ";
cout << endl;
int n = mymap.size();
HT = new HuffmanNode[2 * n];
for (int i = 0; i < 2 * n; i++)
{
HT[i].c = '\0';
HT[i].weight = 0;
HT[i].lchild = HT[i].rchild = 0;
HT[i].parent = 0;
}
map<char, int>::iterator it = mymap.begin();
for (int i = 1; i <= n, it != mymap.end(); i++, it++)
{
HT[i].c = it->first;
HT[i].weight = it->second;
}
for (int i = n + 1; i < 2 * n; i++)
{
int s1, s2;
Select(HT, i - 1, s1, s2);
HT[i].lchild = s1; HT[i].rchild = s2;
HT[s2].parent = i;
HT[s1].parent = i;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
return n;
}
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int n)
{
HC = new HuffCodeNode[n + 1];
char* dc = new char[n];
dc[n - 1] = '\0';
int start;
for (int i = 1; i <= n; i++)
{
HC[i].ch = HT[i].c;
start = n - 1;
int c = i, f = HT[c].parent;
while (f)
{
if (HT[f].lchild == c)
dc[--start] = '0';
else
dc[--start] = '1';
c = f;
f = HT[f].parent;
}
int m = n - start;
HC[i].pHC = new char[m];
memcpy(HC[i].pHC, dc + start, m);
}
}
void HuffmanPrintInfo(HuffmanTree& HT, int n)
{
for (int i = 1; i < 2 * n; i++)
{
cout << i << ' ' << HT[i].weight << ' ' << HT[i].parent
<< ' ' << HT[i].lchild << ' ' << HT[i].rchild << endl;
}
}
void HuffmanCodePrint(HuffmanCode& HC, int n)
{
for (int i = 1; i <= n; i++)
cout << HC[i].ch << ":" << HC[i].pHC << " ";
cout << endl;
}
string EncodeStr(HuffmanCode& HC, string str, int n)
{
string res = "";
for (int i = 0; i < str.length(); i++)
{
for (int j = 1; j <= n; j++)
{
if (str[i] == HC[j].ch)
{
res += HC[j].pHC;
break;
}
}
}
return res;
}
string DeCodeStr(HuffmanTree HT, int n, string code)
{
string res = "";
char ch;
int start = 0;
int i = 0;
while ((ch = code[i++]) != '\0')
{
if (ch == '0')
start = HT[2 * n - 1].lchild;
else if (ch == '1')
start = HT[2 * n - 1].rchild;
while (HT[start].lchild != 0)
{
if ((ch = code[i++]) == '\0') return res;
if (ch == '0')
start = HT[start].lchild;
else if (ch == '1')
start = HT[start].rchild;
}
res += HT[start].c;
}
return res;
}
int main()
{
string str;
cin >> str;
while (str != "0")
{
HuffmanTree HT;
HuffmanCode HC;
int n = CreateHuffmanTree(HT, str);
CreateHuffmanCode(HT, HC, n);
HuffmanPrintInfo(HT, n);
HuffmanCodePrint(HC, n);
string encode = EncodeStr(HC, str, n);
cout << encode << endl;
string decode = DeCodeStr(HT, n, encode);
cout << decode << endl;
cin >> str;
}
return 0;
}