树( Tree)是n(n≥0)个结点的有限集,它或为空树(n=0);或为非空树,对于非空树T:
1. 有且仅有一个称之为根的结点;
2. 除根结点以外的其余结点可分为m (m>0)个互不相交的有限集T1,T2,…,Tm,其中每一个集合本身又是一棵树,并且称为根的子树( SubTree)。
在下图中,(a)是只有一个根结点的树;(b)是有13个结点的树,其中A是根,其余结点分成3个互不相交的子集:T1={B,E,F,K,L},T2 ={C,G},T3(D,H,I,J,M})。T1、T2和T3都是根A 的子树,且本身也是一棵树。例如T1,其根为B,其余结点分为两个互不相交的子集:T11={E,K,L},T12={F}。T11和T12都是B的子树。而T11中E是根,{K}和(L}是E的两棵互不相交的子树,其本身又是只有一个根结点的树。
树的结构定义是一个递归的定义,即在树的定义中又用到树的定义,它道出了树的固有特性。
线性结构 | 树型结构 |
---|---|
第一个数据元素(无前驱) | 根结点(无前驱) |
最后一个数据元素(无后继) | 多个叶子结点(无后继) |
其他数据元素(一个前驱,一个后继) | 树中其它结点(一个前驱,一个后继) |
术语 | 解释 |
---|---|
结点 | 树的数据元素;如A、B、C、D等 |
根 | 根节点(没有前驱);如A |
叶子 | 终端结点(没有后继);结点K、L、F、G、M、I、J都是树的叶子 |
森林 | m棵不相交的树的集合(例如删除A后的子树个数) |
有序树 | 结点各子树从左到右有序,不能互换(左为第一) |
无序树 | 结点各子树可互换位置 |
双亲 | 上层的结点(直接前驱);B的双亲为A |
孩子 | 下层结点子树的根(直接后继);B的孩子有E和F |
兄弟 | 同一双亲下的同层结点(孩子之间互称兄弟);H、I和J互为兄弟 |
堂兄弟 | 双亲位于同一层的结点(并非同一双亲);结点G与E、F和H、I、J互为堂兄弟 |
祖先 | 根到该结点所经分支的所有结点;M的祖先为A、D和H |
子孙 | 该结点下层子树中任一结点;B的子孙为E、K、L和F |
结点的度 | 结点拥有的子树数;A的度为3,C的度为1,F的度为0 |
树的度 | 所有结点度中的最大值;该树的度为3 |
结点的层次 | 根到该结点的层数(根结点算第一层) |
终端结点 | 度为0的结点,即叶子 |
分支结点 | 度不为0的结点 |
树的深度 | 所有结点中最大的层数;该树的深度为4 |
二叉树(Binary Tree)是n ( n≥0)个结点所构成的集合,它或为空树(n=0);或为非空树,对于非空树T:
1. 有且仅有一个称之为根的结点;
2. 除根结点以外的其余结点分为两个互不相交的子集T1,和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树。
二叉树与树—样具有递归性质,二叉树与树的区别主要有以下两点:
1. 二叉树每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点);
2. 二叉树的子树有左右之分,其次序不能任意颠倒。
二叉树的5种基本形态:
树的抽象数据类型定义:
性质1:在二叉树的第i层上至多有2i-1个结点(i≥1)
证明:i=1时,只有一个根结点,2i-1=1成立﹔假设第i-1层至多有2i-2个结点,又二叉树每个结点的度至多为2;所以,第i层上最大结点数是第i-1层的2倍,即2i-1个结点。
性质2:深度为k的二叉树至多有2k-1个结点(k≥1)
证明:由性质1,可得深度为k的二叉树最大结点数是2k-1
性质3:对任何一棵二叉树T,如果其终端(叶子)结点个数为n0,度为2的结点数为n2,则n0=n2+1。
证明:n1为二叉树T中度为1的结点数,二叉树中所有结点的度均小于或等于2,其结点总数n=n0+n1+n2;二叉树中,除根结点外,其余结点都只有一个分支进入,设: B为分支总数,则n=B+1;分支由度为1和度为2的结点射出,B=n1+2n2于是n=B+1=n1+2n2+1=n0+n1+n2,所以no=n2+1
这里引入两个概念:满二叉树和完全二叉树
满二叉树:深度为k且含有2k-1个结点的二叉树。
满二叉树的特点是:每一层上的结点数都是最大结点数,即每一层i的结点数都具有最大值2i-1。
完全二叉树:深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点—一对应时,称之为完全二叉树。
完全二叉树的特点是:叶子结点只可能在层次最大的两层上出现;对任一结点,若其右分支下的子孙的最大层次为l,则其左分支下的子孙的最大层次必为l或l+1。
性质4:具有n个结点的完全二叉树的深度为⌊log2n⌋+1
证明:2k-1-1
性质5:如果对一棵有n个结点的完全二叉树从上至下、从左至右编号,则编号为i (1≤ i≤ n)的结点:
若i=1,则该结点是二叉树的根,无双亲,否则,其双亲结点编号为⌊i/2⌋;
若2i > n,则该结点无左孩子,否则,其左孩子结点编号为2i ;
若2i+1 >n,则该结点无右孩子结点,否则,其右孩子结点编号为2i+1。
证明:
顺序存储结构
//二叉树的顺序存储表示
#define MAXTSIZE 100
typedef TElemType SqBiTree[MAXTSIZE];
SqBiTree bt;
顺序存储结构使用一组地址连续的存储单元来存储数据元素,为了能够在存储结构中反映出结点之间的逻辑关系,必须将二叉树中的结点依照―定的规律安排在这组单元中。
对于完全二叉树,只要从根起按层序存储即可,依次自上而下、自左至右存储结点元素,即将完全二叉树上编号为i的结点元素存储在如上定义的一维数组中下标为i-1的分量中。
对于一般二叉树,则应将其每个结点与完全二叉树上的结点相对照,存储在一维数组的相应分量中,图中以“0”表示不存在此结点。
这种顺序结构只适合完全二叉树,所以对于一般二叉树,更适合采用链式存储结构。
链式存储结构
由二叉树的定义可知,二叉树的结点有一个数据元素和分别指向其左、右子树的两个分支构成,则表示二叉树的链表中的结点至少包含3个域:数据域和左、右指针域。 有时为了方便找到结点的双亲,还可以在结点结构中增加一个指向其双亲结点的指针域。利用这两种结点结构所得二叉树的存储结构分别称之为二叉链表和三叉链表。
//二叉树的二叉链表存储表示
typedef struct BiTNode
{
TElemType data; //结点数据域
struct BiTNode *lchild, *rchild; //左右孩子指针
} BiTNode, *BiTree; //二叉树结点
遍历二叉树算法描述
遍历二叉树( traversing binary tree)是指按某条搜索路径巡访树中每个结点,使得每个结点均被访问一次,而且仅被访问一次。
二叉树是由3个基本单元组成:根结点、左子树和右子树。因此,若能依次遍历这三部分,便是遍历了整个二叉树。假如从L、D、R分别表示遍历左子树、访问根结点和遍历右子树,则可有DLR、LDR、LRD、DRL、RDL、RLD这6种遍历二叉树的方案。若限定先左后右,则只有前3种情况,分别称之为先(根)序遍历、中(根)序遍历和后(根)序遍历。
void PreOrderTraverse(BiTree T)
{
if (T) //非空二叉树
{
cout << T->data; //访问根结点
PreOrderTraverse(T->lchild); //递归遍历左子树
PreOrderTraverse(T->rchild); //递归遍历右子树
}
}
void InOrderTraverse(BiTree T)
{
if (T) //非空二叉树
{
InOrderTraverse(T->lchild); //递归遍历左子树
cout << T->data; //访问根结点
InOrderTraverse(T->rchild); //递归遍历右子树
}
}
void PostOrderTraverse(BiTree T)
{
if (T) //非空二叉树
{
PostOrderTraverse(T->lchild); //递归遍历左子树
PostOrderTraverse(T->rchild); //递归遍历右子树
cout << T->data; //访问根结点
}
}
用二叉树表示算术表达式
根据遍历序列确定二叉树
由二叉树的先序序列和中序序列,或由其后序序列和中序序列均能唯一地确定一棵二叉树。但是由一颗二叉树的先序序列和后序序列不能唯一确定一颗二叉树。
例:已知一棵二叉树的中序序列和后序序列分别是 BDCEAFHG和DECBHGFA,请画出这棵二叉树。
例:
先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T)
{
cin >> ch;
if (ch ==’#’)
T = NULL; //递归结束,建空树
else
{
T = new BiTNode;
T - >data = ch; //生成根结点
CreateBiTree(T - >lchild); //递归创建左子树
CreateBiTree(T - >rchild); //递归创建右子树
}
}
复制二叉树
如果是空树,递归结束,否则执行以下操作:
void Copy(BiTree T, BiTree &NewT)
{
if (T = NULL)
{
NewT = NULL;
return;
}
else
{
NewT = new BiTNode;
NewT->data = T->data;
Copy(T->lchild, NewT->lchild);
Copy(T->rchild, NewT->rchild);
}
}
计算二叉树的深度
如果是空树,递归结束,深度为0,否则执行以下操作:
int Depth(BiTree T)
{ // 返回二叉树的深度
if (!T)
return 0;
else
{
m = Depth(T->lchild);
n = Depth(T->rchild);
if (m > n)
return (m + 1);
else return (n+1);
}
}
统计二叉树中结点的个数
int NodeCount(BiTree T)
{
if (T == NULL)
return 0;
else
return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
}
统计二叉树中叶子结点个数
int CountLeaf(BiTree T)
{ // 返回指针T所指二叉树中的叶子结点的个数
if (!T)
return 0;
if (!T->lchild && !T->rchild)
return 1;
else
{
m = CountLeaf(T->lchild);
n = CountLeaf(T->rchild);
return (m + n);
} // else
} // CountLeaf
当以二叉链表作为存储结构时,只能找到结点的左、右孩子信息,而不能直接得到结点在任一序列中的前驱和后继信息,这种信息只有在遍历的动态过程中才能得到,为此引人线索二叉树来保存这些在动态过程中得到的有关前驱和后继的信息。
线索:指向前驱或后继结点的指针称为线索
线索二叉树:加上线索的二叉链表表示的二叉树叫线索二叉树
线索化:对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化
试做如下规定:若结点有左子树,则其lchild域指示其左孩子,否则令lchild域指示其前驱;若结点有右子树,则其rchild域指示其右孩子,否则令rchild域指示其后继。为了避免混淆,尚需改变结点结构,增加两个标志域。
二叉树的二叉线索存储表示
typedef struct BiThrNode
{
TElemType data;
struct BiThrNode *lchild, *rchild;//左右孩子标志
int LTag, RTag;//左右标志
} BiThrNode, *BiThrTree;
以结点p为根的子树中序线索化
算法中有一全局变量:pre,在主调程序中初值为空,在整个线索化算法中pre始终指向当前结点p的前驱。
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); //右子树递归线索化
}
}
带头结点的二叉树中序线索化
void InOrderThreading(BiThrTree &Thrt, BiThrTree T)
{ //中序遍历二叉树T,并将其中序线索化,Thrt指向头结点
Thrt = new BiThrNode; //建头结点
Thrt->LTag = 0; //头结点有左孩子,若树非空,则其左孩子为树根
Thrt->RTag = 1; //头结点的右孩子指针为右线索
Thrt->rchild = Thrt; //初始化时右指针指向自己
if (!T)
Thrt->lchild = Thrt; //若树为空,则左指针也指向自己
else
{
Thrt->lchild = T;
pre = Thrt; //头结点的左孩子指向根,pre初值指向头结点
InThreading(T); //对以T为根的二叉树进行中序线索化
pre->rchild = Thrt; //算法5.7结束后,pre为最右结点,pre的右线索指向头结点
pre->RTag = 1;
Thrt->rchild = pre; //头结点的右线索指向pre
}
}
遍历中序线索二叉树
void InOrderTraverse_Thr(BiThrTree T)
{ // T指向头结点,头结点的左链lchild指向根结点,可参见线索化算法5.8。
//中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出
p = T->lchild; // p指向根结点
while (p != T) //空树或遍历结束时,p==T
{
while (p->LTag == 0) //沿左孩子向下
p = p->lchild; //访问其左子树为空的结点
cout << p->data;
while (p->RTag == 1 && p->rchild != T)
{
p = p->rchild; //沿右线索访问后继结点
cout << p->data;
}
p = p->rchild;
}
}
双亲表示法
这种表示方法中,以一组连续的存储单元存储树的结点,每个结点除了数据域data外,还附设一个parent域用以指示其双亲结点的位置。
#defin MAX_TREE_SIZE 100
typedef struct PTNode
{ //结点结构
ElemType data;
int parent; //保存双亲位置
} PTNode;
typedef struct
{ //树结构
PTNode nodes[MAX_TREE_SIZE];
int r, n; //根的位置和结点数
} PTree;
孩子表示法
由于树中每个结点可能有多棵子树,则可用多重链表,即每个结点有多个指针域,其中每个指针指向一棵子树的根结点。
//树结构
typedef struct
{
CTBox nodes[MAX_TREE_SIZE];
int n, r; // 结点数和根结点的位置
} CTree;
//孩子链表表示法的类型描述
typedef struct CTNode
{
int child;
struct CTNode *nextchild;
} * ChildPtr;
//双亲结点结构
typedef struct
{
ElemType data;
int parent; //保存双亲位置
ChildPtr firstchild;
// 孩子链的头指针
} CTBox;
孩子兄弟法
又称二叉树表示法,或二叉链表表示法,即以二叉链表做树的存储结构。链表中结点的两个链域分别指向该结点的第一个孩子结点和下一个兄弟结点,分别命名为firstchild域和nextsibling域。
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild, *nextsibling;
} CSNode, *CSTree;
从树的二叉链表表示的定义可知,任何一棵和树对应的二叉树,其根结点的右子树必空。若把森林中第二棵树的根结点看成是第一棵树的根结点的兄弟,则同样可导出森林和二叉树的对应关系。
树转换成二叉树的方法
二叉树转换为树的方法
森林转换成二叉树的方法
二叉树转换成森林的方法
例:
树的遍历:
先根(序)遍历:先访问树的根结点,然后依次先根遍历根的每棵子树后根(序)遍历:先依次后根遍历每棵子树,然后访问根结点。
按层次遍历:先访问第一层上的结点,然后依次遍历第二层,…,第n层的结点。
先序遍历森林
- 访问森林中第一棵树的根结点
- 先序遍历第一棵树中根结点的子树森林
- 先序遍历除去第一棵树之后剩余的树构成森林
中序遍历森林
- 中序遍历森林中第一棵树的根结点的子树森林
- 访问第一棵树的根结点
- 中序遍历除去第一棵树之后剩余的树构成的森林
哈夫曼(Huffman )树又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用途。
路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
路径长度:路径上的分支数目称作路径长度。
树的路径长度:从树根到每一结点的路径长度之和。
权:赋予某个实体的一个量,是对实体的某个或某些属性的数值化描述。
结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长度之和。
哈夫曼树:假设有m个权值{w1,w2…,wm},可以构造一棵含n个叶子结点的二叉树,每个叶子结点的权为wi,则其中带权路径长度WPL最小的二叉树称做最优二叉树或哈夫曼树。
哈夫曼树的构造过程
核心思想:使权值大的结点靠近根。
哈夫曼算法的实现
哈夫曼树是―种二叉树,当然可以采用前面介绍过的通用存储方法,而由于哈夫曼树中没有度为1的结点,则一棵有n个叶子结点的哈夫曼树共有2n-1个结点,可以存储在一个大小为2n-1的一维数组中。
typedef struct
{
int weight; //结点的权值
int parent, lchild, rchild; //双亲、左孩子、右孩子的下标
} HTnode, *HuffmanTree;
构造哈夫曼树
void CreatHuffmanTree(HuffmanTree HT, int n)
{ //构造哈夫曼树HT
if (n <= 1)
return;
m = 2 * n - 1;
HT = new HTNode[m + 1]; // 0号单元未用,HT[m]表示根结点
for (i = 1; i <= m; ++i) //初始化:双亲、左孩子,右孩子的下标都为0
{
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
for (i = 1; i <= n; ++i) //输入前n个单元中叶子结点的权值
cin >> HT[i].weight;
/*―――初始化工作结束,下面开始创建哈夫曼树――――*/
for (i = n + 1; i <= m; ++i)
{ //通过n-1次的选择、删除、合并来创建哈夫曼树
Select(HT, i - 1, s1, s2); //选择两个其双亲域为0且权值最小的结点,
HT[s1].parent = i;
HT[s2].parent = i; //得到新结点i, 将s1和s2的双亲域由0改为i
HT[i].lchild = s1;
HT[i].rchild = s2; // s1,s2分别作为i的左右孩子
HT[i].weight = HT[s1].weight + HT[s2].weight; // i 的权值为左右孩子权值之和
} // for
}
例:
下面给出有关编码的两个概念。
前缀编码: 如果在一个编码方案中,任一个编码都不是其他任何编码的前缀(最左子串),则称编码是前缀编码。
哈夫曼编码: 对一棵具有n个叶子的哈夫曼树,若对树中的每个左分支赋予0,右分支赋予1,则从根到每个叶子的路径上,各分支的赋值分别构成一个二进制串,该二进制串就称为哈夫曼编码。
哈夫曼编码满足下面的两个性质:
哈夫曼编码是前缀码
哈夫曼编码是最优前缀编码
哈夫曼编码的算法实现
在构造哈夫曼树之后,求哈夫曼编码的主要思想是:依次以叶子为出发点,向上回溯至根结点为止。回溯时走左分支则生成代码0,走右分支则生成代码1。由于每个哈夫曼编码是变长编码,因此使用一个指针数组来存放每个字符编码串的首地址。
void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n)
{ //从叶子到根逆向求每个字符的赫夫曼编码,存储在编码表HC中
HC = new char *[n + 1]; //分配n个字符编码的头指针矢量
cd = new char[n]; //分配临时存放编码的动态数组空间
cd[n - 1] = '\0'; //编码结束符
for (i = 1; i <= n; ++i)
{ // 逐个字符求赫夫曼编码
start = n - 1; // start开始时指向最后,即编码结束符位置
c = i;
f = HT[i].parent; // f指向结点c的双亲结点
while (f != 0)
{ //从叶子结点开始向上回溯,直到根结点
--start; //回溯一次start向前指一个位置
if (HT[f].lchild == c)
cd[start] =’0’; //结点c是f的左孩子,则生成代码0
else
cd[start] =‘1’; //结点c是f的右孩子,则生成代码1
c = f;
f = HT[f].parent; //继续向上回溯
} //求出第i个字符的编码
HC[i] = new char[n - start]; // 为第i 个字符编码分配空间
strcpy(HC[i], &cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
}
delete cd; //释放临时空间
}
例:
测试样例:ABC##DE#G##F###
#include
using namespace std;
//二叉树的二叉线索存储表示
typedef struct BiNode
{
char data;
struct BiNode *lchild, *rchild;
} BiTNode, *BiTree;
//先序遍历的的顺序建立二叉链表
void CreateBiTree(BiTree &T)
{
char ch;
cin >> ch;
if (ch == '#')
{
T = NULL;
}
else
{
T = new BiTNode;
T->data = ch;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
}
//先序遍历的递归算法
void PreOrderTraverse(BiTree T)
{
if (T)
{
cout << T->data;
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
//中序遍历的递归算法
void InOrderTraverse(BiTree T)
{
if (T)
{
InOrderTraverse(T->lchild);
cout << T->data;
InOrderTraverse(T->rchild);
}
}
//后序遍历的递归算法
void PostOrderTraverse(BiTree T)
{
if (T)
{
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
cout << T->data;
}
}
int main(void)
{
BiTree tree;
int select;
while (1)
{
cout << "1、建立二叉树\n";
cout << "2、先序遍历\n";
cout << "3、中序遍历\n";
cout << "4、后序遍历\n";
cout << "0、退出程序\n";
cout << "\n请选择:";
cin >> select;
switch (select)
{
case 1:
cout << "请输入建立二叉链表的序列:\n";
CreateBiTree(tree);
cout << endl;
break;
case 2:
cout << "所建立的二叉链表先序序列:\n";
PreOrderTraverse(tree);
cout << endl
<< endl;
break;
case 3:
cout << "所建立的二叉链表中序序列:\n";
InOrderTraverse(tree);
cout << endl
<< endl;
break;
case 4:
cout << "所建立的二叉链表后序序列:\n";
PostOrderTraverse(tree);
cout << endl;
break;
case 0:
return 0;
break;
}
}
return 0;
}
f1.txt |
---|
TherearemomentsinlifewhenyoumisssomeonesomuchthatyoujustwanttopickthemfromyourdreamsandhugthemforrealDreamwhatyouwanttodreamgowhereyouwanttogobewhatyouwanttobebecauseyouhaveonlyonelifeandonechancetodoallthethingsyouwanttodo |
f2.txt |
---|
110000011010011111010111111111010110011100001101110110001110001001101101000010011101000111101110100111011110011000101001101001111001110011100100001101110010110111110010000110101110101101000010101111000110011000101110000000101111000001101111111011000000100110000110100111010111000010000101001100111101001110110000111100110001011110100101111010111111001111100111110110010110100101110001000101001100111101001001110111101011111101000001000111101011111100111101110101111000110011000101110111111101100000010000101111010111111001111000110011011101001111101011110011000101110111111101100000010011000110000100101111011101011110001100110001011101111111011000000100001001011001001011110101111101011110001111001100010110101111001000001110010110100011001100101101101000010011101000111111101100101100101101111010110101111101111010101100010000101100111101000010000001010011000101001001101111000111100110011000101110111111101100000010000101100 |
f3.txt |
---|
TherearemomentsinlifewhenyoumisssomeonesomuchthatyoujustwanttopickthemfromyourdreamsandhugthemforrealDreamwhatyouwanttodreamgowhereyouwanttogobewhatyouwanttobebecauseyouhaveonlyonelifeandonechancetodoallthethingsyouwanttodo |
#include
using namespace std;
typedef struct //定义哈夫曼树的结构
{
int weight;
int parent, lchild, rchild;
} HTNode, *HuffmanTree;
typedef char **HuffmanCode;
typedef struct //存储数据扫描统计结果
{
char *data; //字符
int *quantity; //次数
int length; //总长度
} TNode;
void InitList(TNode &N) //初始化TNode定义的结点
{
N.data = new char[256];
N.quantity = new int[256];
if (!N.data || !N.quantity)
exit(1);
N.length = 0;
}
int Find(TNode N, char ch)
{
for (int i = 0; i < N.length; i++)
if (ch == N.data[i])
return true;
return false;
}
void ReadFile(vector<char> &f) //读取文件
{
char ch;
ifstream infile("f1.txt", ios::in);
if (!infile) //文件不存在
{
cout << "打开文件失败!" << endl;
exit(1);
}
while (infile.peek() != EOF)
{
infile.get(ch);
f.push_back(ch); //把字符ch推入vector
}
infile.close(); //关闭文件
cout << "读取完成" << endl;
system("pause");
}
void WriteTNode(vector<char> v, TNode &N) //将vector中的数据存入TNode结构体中
{
char ch;
int len = v.size(), j = 0;
for (int i = 0; i < len; i++)
{
ch = v[i];
if (!Find(N, ch))
{
N.data[j] = ch;
N.quantity[j] = count(v.begin(), v.end(), ch);
j++;
N.length++;
}
}
cout << "写入完成" << endl;
system("pause");
}
void Select(HuffmanTree &HT, int n, int &min1, int &min2) //查找HT中未被使用的权值最小的两个点的下标
{
min1 = min2 = 0; //初始化
for (int i = 1; i < n; i++)
{
if (HT[i].parent != 0)
continue; //略过已经加入的结点
if (min1 == 0)
min1 = min2 = i; //赋初值
else if (HT[i].weight <= HT[min1].weight)
{ //min1是最小值
min2 = min1;
min1 = i;
}
else if (HT[i].weight < HT[min2].weight)
{ //min2是次小值
min2 = i;
}
else if (HT[i].weight > HT[min2].weight)
{ //防止两个值相等
if (min1 == min2)
min2 = i;
}
}
}
void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC, TNode N) //建树函数
{
int m, start, n = N.length;
char *cd;
unsigned int c, f;
if (n <= 1)
return;
m = 2 * n - 1;
HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //0号单元未用 从1开始
for (int i = 1; i <= n; i++)
{ //利用TNode将叶节点初始化
HT[i].parent = 0;
HT[i].lchild = HT[i].rchild = 0;
HT[i].weight = N.quantity[i - 1];
}
for (int i = n + 1; i <= m; i++)
{ //初始化非叶节点
HT[i].parent = HT[i].weight = 0;
HT[i].lchild = HT[i].rchild = 0;
}
for (int i = n + 1; i <= m; i++)
{ //构建赫夫曼树
int min1, min2; //选出最小的两个结点合并
Select(HT, i, min1, min2);
HT[i].weight = HT[min1].weight + HT[min2].weight;
HT[i].lchild = min1;
HT[i].rchild = min2;
HT[min1].parent = HT[min2].parent = i;
}
//从叶子到根逆向求每个字符的赫夫曼编码
HC = (HuffmanCode)malloc((n + 1) * sizeof(char *)); //分配n个字符编码的头指针向量
cd = (char *)malloc(n * sizeof(char)); //分配求编码的工作空间
cd[n - 1] = '\0'; //编码结束符
for (int i = 1; i <= n; i++)
{ //逐个字符求赫夫曼编码
start = n - 1; //编码结束符位置
for (c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)
{ //从叶子到根逆向求编码
if (HT[f].lchild == c)
cd[--start] = '0';
else
cd[--start] = '1';
}
HC[i] = (char *)malloc((n - start) * sizeof(char)); //为第i个字符编码分配空间
strcpy(HC[i], &cd[start]); //从cd复制编码(串)到HC
}
free(cd); //释放工作空间
cout << "完成哈夫曼建树!" << endl;
system("pause");
}
void ZipFile(HuffmanCode HC, vector<char> v, TNode N) //压缩文件
{
int i = 0, j = 0, k = 0;
ofstream outfile("f2.txt", ios::out);
if (!outfile)
{ //文件为空
cerr << "wrong open!!" << endl;
exit(1);
}
for (i = 0; i < v.size(); i++)
{ //遍历vector容器
for (j = 0; j < N.length; j++)
if (N.data[j] == v[i])
break;
for (k = 0; HC[j + 1][k] != '\0'; k++)
outfile << HC[j + 1][k];
}
outfile.close();
cout << "压缩完成!" << endl;
system("pause");
}
void RZipFile(HuffmanCode HC, TNode N)
{ //解压文件
int flag, flag2 = 0, m = 0, i, j;
char ch;
char ch2[55];
ofstream outfile("f3.txt", ios::out);
ifstream infile("f2.txt", ios::in);
if (!outfile)
{ //文件打开失败
cerr << "打开错误!" << endl;
exit(1); //运行错误,返回值1
}
if (!infile)
{
cerr << "打开错误!" << endl;
exit(1); //运行错误,返回值1
}
while (infile.peek() != EOF)
{
flag = 0;
char *cd = new char[N.length];
for (i = 0;; i++)
{
infile >> ch;
cd[i] = ch;
cd[i + 1] = '\0';
for (int j = 1; j <= N.length; j++)
{
if (strcmp(HC[j], cd) == 0)
{
if (flag2 == 1)
{
ch2[m] = N.data[j - 1];
flag = 1;
m++;
delete cd;
break;
}
if (flag2 == 0)
{
outfile << N.data[j - 1];
flag = 1;
delete cd;
break;
}
}
}
if (flag == 1)
break;
}
}
cout << "解压缩完成!" << endl; //提示完成解压缩
system("pause");
}
void OutPutFile(vector<char> &f)
{
char path[200];
char ch;
cout << "请输入您所要查询的文件路径" << endl;
cin >> path;
ifstream infile(path, ios::in);
if (!infile)
{ //文件不存在
cout << "打开错误!" << endl;
exit(1);
}
while (infile.peek() != EOF)
{
infile.get(ch); //读取字符赋值给ch
f.push_back(ch); //把字符ch推入vector
}
infile.close();
cout << "路径:" << path << "的文件如下" << endl;
for (int i = 0; i < f.size(); i++)
cout << f.at(i);
cout << endl;
f.clear();
system("pause");
}
int main(void)
{
int choose;
vector<char> V, Vr;
HuffmanTree HT;
HuffmanCode HC;
TNode N;
InitList(N);
while (1)
{
system("cls");
cout << "1、压缩文件" << endl;
cout << "2、解压文件" << endl;
cout << "3、输出文件" << endl;
cout << "0、 退出 " << endl;
cout << "请输入您的操作:" << endl;
cin >> choose;
switch (choose)
{
case 1:
cout << "*****进行压缩文件操作*****" << endl;
ReadFile(V); //从文件读取数据存入vector容器
WriteTNode(V, N); //将vector中的数据存储到NTode结构中
HuffmanCoding(HT, HC, N); //将TNode中的数据存到哈夫曼树中并生成哈夫曼编码
ZipFile(HC, V, N); //压缩文件
break;
case 2:
cout << "*****进行解压缩文件操作*****" << endl;
RZipFile(HC, N); //对文件内容进行解码
break;
case 3:
cout << "*****进行输出文件操作*****" << endl;
OutPutFile(Vr); //将文件内容借助vector输出到终端
break;
case 0:
return 0;
default:
cout << "请正确输入!";
system("pause");
break;
}
}
return 0;
}