树与二叉树
一、认识树
- 树是
n
n
n个结点的有限集。
- 如果
n
=
0
n=0
n=0,称为空树
- 如果
n
>
0
n>0
n>0,则有且仅有一个特定的称之为根结点,它只有直接后继,没有直接前驱。除根以外的其他结点划分为
m
m
m个互不相交的有限集,其中每个集合本身又是一棵树,称为根的子树
- 树可用嵌套集合、广义表和凹入表来表示
- 结点的度指的是拥有子树的数目;树的度指的是树种结点的最大的度。
- 树的ADT
ADT Tree{
InitTree(&T);
...
Value(T,cur_e);
Assign(T,cur_e,value)
Parent(T,cur_e)
LeftChild(T,cur_e);
...
InsertChild(&T,&p,i,c);
DeleteChild(&T,&p,i);
TraverseTree(T,Visit());
}
二、认识二叉树
- 一棵二叉树是结点的一个有限集,该集合为空,或由一个根加上两棵分别成为左子树和右子树的互不相交的二叉树组成
- 每个结点至多只有两棵子树(不存在度大于2的结点)
- 二叉树的ADT
ADT BinaryTree{
InitBiTree(&T);
...
Value(T,e);
Assign(T,&e,value);
Parent(T,e);
LeftChild(T,e);
...
InsertChild(T,p,LR,c);
DeleteChild(T,p,LR);
PreOrederTraverse(T,Visit());
InOrderTraverse(T,Visit());
PostOrderTraverse(T,Visit());
LevelOrderTraverse(T,Visit());
}
- 完全二叉树:设二叉树的高度为
h
h
h,则共有
h
h
h层。除第
h
h
h层外,其它各层
(
1
h
−
1
)
(1~h-1)
(1 h−1)的结点数都达到最大个数,第
h
h
h层从右向左连续缺若干节点,这就是完全二叉树
- 满二叉树:一棵深度为
k
k
k且每层结点数都达到最大个数的二叉树称为满二叉树
- 二者特点:
- 若对结点按从上到下,自左至右连续编号,完全二叉树每个结点和相同高度满二叉树的编号结点一一对应
- 叶节点只可能在层次最大的两层上出现
- 任一结点,若其右分支下的子孙的最大层次为
l
l
l,则其左分支下的子孙的最大层次必为
l
l
l或
l
+
1
l+1
l+1
- 二叉树的性质:
- 二叉树的某一层的结点数量
- 在二叉树的第
i
i
i层上至多有
2
i
−
1
2^{i-1}
2i−1个结点
- 二叉树的所有结点数量
- 深度为
k
k
k的二叉树至多有
2
k
−
1
2^k-1
2k−1个结点
- 不同类型结点的数量关系
- 对任何一棵二叉树
T
T
T,如果其叶结点树为
n
0
n_0
n0,度为
2
2
2的结点数为
n
2
n_2
n2,则
n
0
=
n
2
+
1
n_0=n_2+1
n0=n2+1
- 完全二叉树的结点数量和深度的关系
- 具有
n
n
n个结点的完全二叉树的深度为
└
l
o
g
2
n
┘
+
1
\llcorner log_2n\lrcorner+1
└log2n┘+1
- 完全二叉树的结点间关系
- 如将一棵有
n
n
n个结点的完全二叉树自顶向下,同一层从左向右连续给结点编号
1
,
2
,
⋯
,
n
1,2,\cdots,n
1,2,⋯,n,则有以下关系
-
i
=
1
i=1
i=1,则
i
i
i无双亲
-
i
>
1
i>1
i>1,则
i
i
i的双亲为
└
i
2
┘
\llcorner \frac{i}{2}\lrcorner
└2i┘
-
2
i
>
n
2i>n
2i>n,则
i
i
i无左孩子;否则,
i
i
i的左孩子为
2
i
2i
2i
-
2
i
+
1
>
n
2i+1>n
2i+1>n,则
i
i
i无右孩子;否则,
i
i
i的右孩子为
2
i
+
1
2i+1
2i+1
三、实现二叉树
- 二叉树的存储结构
- 顺序存储:存储效率低,大量占位用的0
- 链式存储:二叉链表(lChild,rChild)
- 链式存储:三叉链表(lChild,rChild,parent)
- 二叉树的基本操作
- 核心:遍历二叉树
- 树的遍历就是按照某种次序访问树中的结点,要求每个结点访问一次且仅访问一次
- 树的遍历就是将树的结点转换为线性序列
- 中序遍历
int InOrder(BiTree T)
{
InOrder(T->lChild);
print("$d", T->data);
InOrder(T->rChild);
}
int PreOrder(BiTree T)
{
print("$d", T->data);
PreOrder(T->lChild);
PreOrder(T->rChild);
}
int PostOrder(BiTree T)
{
PostOrder(T->lChild);
PostOrder(T->rChild);
print("$d", T->data);
}
void CreateBiTree(BiTree &T)
{
scanf(&ch);
if(ch==RefValue)
{
T=NULL;
}
else
{
T=(BiTNode*)malloc(sizeof(BiTNode));
assert(T);
T->data=ch;
CreateBiTree(T->lChild);
CreateBiTree(T->rChild);
}
}
四、二叉树的非递归遍历
void InOrderTraverse(BiTree T)
{
InitStack(S);
Push(S,T);
while(!STackEmpty(S))
{
while(GetTop(S,p)&&p)
push(S,p->lChild);
Pop(S,p);
if(!StackEmpty(S))
{
Pop(S,p);
printf("%d",p->data);
Push(S, p->rChild);
}
}
}
- 先序遍历的非递归算法
- 从根节点开始,入栈;结点
p
p
p出栈,若
p
p
p非空时,访问,再将其非空右孩子入栈,然后再将其左孩子入栈,接下来出栈,并形成循环
- 后序遍历的非递归算法
- 有中序非递归遍历的思想,也有先序非递归遍历的思想。先将左孩子尽可能入栈,但是在左孩子入栈之前,先把非空的右孩子入栈,保证右子树先入栈,后访问。
- 从根节点开始,若左子树非空一直入栈,当左子树为空后,出栈,若右子树不为空则入栈形成循环,当右子树为空,出栈,检查当前栈顶元素右子树,若为空出栈,否则入栈继续循环
五、线索二叉树
- 二叉树的遍历输出一个特定的线性序列,本质上是线性化树这种非线性的结构
- 结点结构(lChild,lTag,rTag,rChlid,data)
- lTag=0,lChild指向结点的左孩子;lTag=1,lChild指向结点的前驱
- rTag=0,rChild指向结点的右孩子;rTag=1,rChild指向结点的后继
- 线索:指向前驱和后继的指针
- 线索二叉树:包含线索的二叉树
- 线索化:对二叉树进行某种次序的遍历,同时使其成为线索二叉树的过程
六、树的存储结构
- 双亲表示法
- 以一组连续空间存储树的结点,同时在结点中附设一个指针,存放双亲结点在链表中的位置
- 便于找双亲,找孩子耗时;适用于以找双亲为主的算法
- 孩子表示法
- 多重链表
- 每个结点有多个指针域分别指向子树的根结点
- 结点的结构相同,结点含的孩子指针数目等于树的度(存储效率低);或者结点的孩子指针数目等于实际需求(操作复杂性大)
- 孩子链表
- 把每个结点的孩子结点组织为一个单链表,
n
n
n个单链表的头指针组成一个线性表
- 孩子兄弟表示法
- 结点结构(data,firstChild,nextSibling)
- 可将任意树转换为二叉树
七、树的遍历
- 先根遍历和后根遍历共两类
- 树的先根遍历
- 当树非空时,先访问根结点,然后依次先根遍历根的各子树
- 可借用二叉树的先序遍历非递归算法实现
- 树的后根遍历
- 当树非空时,先依次后根遍历根的各子树,然后访问根结点
- 可借用二叉树的中序遍历非递归算法实现
- 森林的遍历:
- 先序遍历:依次先根遍历各子树
- 中序遍历;依次后根遍历各子树
八、哈夫曼树
- 数据压缩
- 无损压缩:数据文件被压缩,解压后恢复原始数据
- 有损压缩:常见,单方向,如图像、视频、语音等各种格式
- 基本原理:数据(文件)就是一长的0-1串,统计发现数据中0-1子串出现的规律,将出现斌率高得,较长的0-1子串用较短得0-1子串表示出来
- 无损压缩:哈夫曼编码
- 有损压缩:Gif图像
- 哈夫曼树(带权路径长度最小的二叉树)
- 构造Huffman树算法
- 输入:给定
n
n
n各权值
- 输出:包括
n
n
n个叶结点的二叉树,其带权路径长度最小
- 为每个权值构造一棵只有根节点的二叉树
- 任意选择根节点权值最小的两棵树
- 选择出的两棵树作为左右孩子,合并出一棵新的树,新树的根的权值为左右孩子权值的和
- 用新树替换那两棵树
- 重复上述过程,直至只有一棵树
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define HFM_CODE_LEN 20
#define TESTHUFFMANCODE 0
#define MAX 1000
struct ZiFu {
unsigned char zf;
unsigned int count;
char code[HFM_CODE_LEN];
int idx;
};
struct allZiFu {
int zf_count;
ZiFu zf_arr[256];
};
struct treeNode {
ZiFu data;
treeNode* pLeft;
treeNode* pRight;
treeNode* pParent;
};
void analysisFile(char* fileName, allZiFu* pZd);
treeNode* createNode(ZiFu* pZf);
treeNode* createHuffmanTree(allZiFu* pZd);
void createHuffmanCode(treeNode* root, allZiFu* pZd);
void writeCompressionFile(char* srcfileName, char* dstFilename, allZiFu* pZd);
void _testZd(allZiFu* pZd);
bool isLeaf(treeNode* root);
void setHuffmanCode(treeNode* root, char* pCodeStr);
void getHuffmanCode(unsigned char zf, allZiFu* pZd, char* pCodeStr);
void readCompressionFile(char* dstFileName, char* srcFilename);
int main()
{
char srcFile[256] = "1.txt";
char dstFile[256] = "2.txt";
char nsrcFile[256] = "3.txt";
treeNode* root = NULL;
allZiFu zd;
memset(&zd, 0, sizeof(struct allZiFu));
analysisFile(srcFile, &zd);
root = createHuffmanTree(&zd);
createHuffmanCode(root, &zd);
writeCompressionFile(srcFile, dstFile, &zd);
readCompressionFile(dstFile, nsrcFile);
system("pause");
return 0;
}
void analysisFile(char* fileName, allZiFu* pZd)
{
FILE* fp = fopen(fileName, "rb");
if (NULL == fp)
{
printf("analysisFile open srcfile %s error!\n", fileName);
return;
}
char c;
int r;
bool is;
int i;
while (1)
{
r = fread(&c, 1, 1, fp);
if (1 != r) break;
is = false;
for (i = 0; i < pZd->zf_count; i++)
{
if (c == pZd->zf_arr[i].zf)
{
is = true;
break;
}
}
if (!is)
{
pZd->zf_count++;
pZd->zf_arr[i].zf = c;
}
pZd->zf_arr[i].count++;
}
fclose(fp);
}
treeNode* createNode(ZiFu* pZf)
{
treeNode* pNew = new treeNode;
if (pNew == NULL) return NULL;
memset(pNew, 0, sizeof(treeNode));
memcpy(pNew->data.code, pZf->code, 20);
pNew->data.count = pZf->count;
pNew->data.zf = pZf->zf;
pNew->data.idx = pZf->idx;
return pNew;
}
treeNode* createHuffmanTree(allZiFu* pZd)
{
treeNode** pArray = new treeNode * [pZd->zf_count];
treeNode* pTemp = NULL;
for (int i = 0; i < pZd->zf_count; i++)
{
pArray[i] = createNode(&(pZd->zf_arr[i]));
pArray[i]->data.idx = i;
}
int minIdx, secMinIdx;
int j;
for (int i = 0; i < pZd->zf_count - 1; i++)
{
minIdx = 0;
j = 0;
while (pArray[j] == NULL) j++;
minIdx = j;
for (j = 0; j < pZd->zf_count; j++)
{
if (pArray[j] && pArray[j]->data.count < pArray[minIdx]->data.count)
minIdx = j;
}
j = 0;
while (pArray[j] == NULL || j == minIdx) j++;
secMinIdx = j;
for (j = 0; j < pZd->zf_count; j++)
{
if (pArray[j] && j != minIdx && pArray[j]->data.count < pArray[secMinIdx]->data.count)
secMinIdx = j;
}
ZiFu tempZf = { 0, pArray[minIdx]->data.count + pArray[secMinIdx]->data.count };
tempZf.idx = -1;
pTemp = createNode(&tempZf);
pArray[minIdx]->pParent = pTemp;
pArray[secMinIdx]->pParent = pTemp;
pTemp->pLeft = pArray[minIdx];
pTemp->pRight = pArray[secMinIdx];
pArray[minIdx] = pTemp;
pArray[secMinIdx] = NULL;
}
return pTemp;
}
void createHuffmanCode(treeNode* root, allZiFu* pZd)
{
int idx;
if (root)
{
if (isLeaf(root))
{
idx = root->data.idx;
setHuffmanCode(root, pZd->zf_arr[idx].code);
}
else
{
createHuffmanCode(root->pLeft, pZd);
createHuffmanCode(root->pRight, pZd);
}
}
}
bool isLeaf(treeNode* root)
{
if (root && root->pLeft == NULL && root->pRight == NULL)
{
return true;
}
return false;
}
void setHuffmanCode(treeNode* root, char* pCodeStr)
{
treeNode* pTemp = root;
char buff[20] = { 0 };
int buffIdx = 0;
while (pTemp->pParent)
{
if (pTemp == pTemp->pParent->pLeft)
{
buff[buffIdx] = 1;
}
else
{
buff[buffIdx] = 2;
}
buffIdx++;
pTemp = pTemp->pParent;
}
char tmp;
for (int i = 0; i < buffIdx / 2; i++)
{
tmp = buff[i];
buff[i] = buff[buffIdx - i - 1];
buff[buffIdx - i - 1] = tmp;
}
strcpy(pCodeStr, buff);
}
void getHuffmanCode(unsigned char zf, allZiFu* pZd, char* pCodeStr)
{
for (int i = 0; i < pZd->zf_count; i++)
{
if (zf == pZd->zf_arr[i].zf)
strcpy(pCodeStr, pZd->zf_arr[i].code);
}
}
void writeCompressionFile(char* srcfileName, char* dstFilename, allZiFu* pZd)
{
FILE* fpSrc = fopen(srcfileName, "rb");
FILE* fpDst = fopen(dstFilename, "wb");
if (fpSrc == NULL || fpDst == NULL)
{
printf("writeCompressionFile open file error !\n");
return;
}
fwrite(pZd, 1, sizeof(struct allZiFu), fpDst);
int r;
char c;
char charForWrite;
int idxForWrite;
int huffmanCodeIdx = 0;
char huffmanCode[20] = {0};
bool isEnd = false;
while (1)
{
if (huffmanCode[huffmanCodeIdx] == 0)
{
r = fread(&c, 1, 1, fpSrc);
if (r != 1) break;
getHuffmanCode(c, pZd, huffmanCode);
huffmanCodeIdx = 0;
}
#if TESTHUFFMANCODE
printf("%c--------------", c);
for (int i = 0; i < 20; i++)
{
printf("%d", huffmanCode[i]);
}
printf("\n");
#endif
idxForWrite = 0;
charForWrite = 0;
while (idxForWrite < 8)
{
if (huffmanCode[huffmanCodeIdx] == 2)
{
charForWrite &= ~(1 << (7 - idxForWrite));
idxForWrite++;
huffmanCodeIdx++;
}
else if (huffmanCode[huffmanCodeIdx] == 1)
{
charForWrite |= (1 << (7 - idxForWrite));
idxForWrite++;
huffmanCodeIdx++;
}
else
{
r = fread(&c, 1, 1, fpSrc);
if (1 != r)
{
isEnd = true;
break;
}
getHuffmanCode(c, pZd, huffmanCode);
#if TESTHUFFMANCODE
printf("%c--------------", c);
for (int i = 0; i < 20; i++)
{
printf("%d", huffmanCode[i]);
}
printf("\n");
#endif
huffmanCodeIdx = 0;
}
}
fwrite(&charForWrite, 1, 1, fpDst);
#if 0
for (int i = 0; i < 8; i++)
{
if ((charForWrite & 0x80) == 0x80)
printf("%d", 1);
else
printf("%d", 0);
charForWrite <<= 1;
}
printf("\n");
#endif
if (isEnd)
break;
}
printf("压缩成功!\n");
fclose(fpDst);
fclose(fpSrc);
}
void readCompressionFile(char* dstFileName, char* srcFilename)
{
FILE* fpDst = fopen(dstFileName, "rb");
FILE* fpSrc = fopen(srcFilename, "wb");
if (fpSrc == NULL || fpDst == NULL)
{
printf("readCompressionFile open file error !\n");
return;
}
allZiFu Zd;
fread(&Zd, sizeof(struct allZiFu), 1, fpDst);
#if 0
for (int i = 0; i < Zd.zf_count + 1; i++)
{
for(int j = 0; j < Zd.zf_arr[i].idx; j++)
printf("%d", Zd.zf_arr[i].code[j]);
printf("\n");
}
#endif
treeNode* root = createHuffmanTree(&Zd);
int r;
char charForRead = 0;
int k = 0;
int code[MAX] = { 0 };
while (1)
{
r = fread(&charForRead, 1, 1, fpDst);
if (r != 1)
break;
for (int i = 0; i < 8; i++)
{
if ((charForRead & 0x80) == 0x80)
{
code[k++] = 1;
}
else
{
code[k++] = 2;
}
charForRead <<= 1;
}
}
treeNode* temp = root;
int num = 0;
for (int i = 0; i < Zd.zf_count; i++)
{
num += Zd.zf_arr[i].count;
}
for (int i = 0; code[i] != 0 && num > 0; i++)
{
if (temp->pLeft == NULL && temp->pRight == NULL)
{
fwrite(&temp->data.zf, sizeof(temp->data.zf), 1, fpSrc);
temp->data.count--;
num--;
temp = root;
}
if (code[i] == 2)
{
temp = temp->pRight;
}
else
{
temp = temp->pLeft;
}
}
printf("解压成功!\n");
fclose(fpDst);
fclose(fpSrc);
}
void _testZd(allZiFu* pZd)
{
printf("带压缩文件中共有%d个字符\n", pZd->zf_count);
int j;
for (int i = 0; i < pZd->zf_count; i++)
{
printf("%c:%d", pZd->zf_arr[i].zf, pZd->zf_arr[i].count);
j = 0;
printf(":code:");
while (pZd->zf_arr[i].code[j])
{
printf("%d", pZd->zf_arr[i].code[j]);
j++;
}
printf("\n");
}
}