6.4 树和森林
人们曾使用多种形式的存储结构来表示树。下面是三种常用的数据结构:
双亲表示法:利用了每个结点只有一个双亲的性质,便于查找双亲,Parent(T,x)操作可以在常量时间内实现。
缺点:找孩子要进行多次遍历。
#define MAX_TREE_SIZE 100
typedef struct PTNode{//结点结构
TElemType data;
int parent;//双亲位置域
}PTNode;
typedef struct {//树结构
PTNode nodes[MAX_TREE_SIZE];
int r,n;//根的位置和结点数
}
//孩子表示法:优点:便于涉及到的孩子的操作。缺点:找双亲比较麻烦。
typedef struct CTNode{ //孩子结点
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct{
TElemType data;
ChildPtr firstchild;//孩子链表头指针
}CTBox;
typedef struct {
CTBox nodes[MAX_TREE_SIZE];
int n,r; //结点数和根的位置
}CTree;
//孩子兄弟法:二叉链表表示法,适用于各种树的操作。
//树的二叉链表(孩子--兄弟)存储表示
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;
6.4.2 森林与二叉树的转换
由于二叉树和树都可用二叉链表作为存储结构,则以二叉链表作为媒介可导出树和二叉树之间的一个对应关系。
任何一棵和树对应的二叉树,其右子树必空。
6.4.3 树和森林的遍历
由树结构的定义可引出两种次序遍历树的方法:一种是先根(次序)遍历树,即先访问树的根结点,然后依次先根遍历根的每棵子树。另一种是后根(次序)遍历,即依次后根遍历每棵子树,然后访问根结点。
当森林转换成二叉树时,其第一棵树的子树森林转换成左子树,剩余树的森林转换成右子树,则上述森林的先序和中序遍历即为其对应的二叉树的先序和中序遍历。
6.6 赫夫曼树及其应用
最优二叉树:带权路径长度最短。出现概率高的字符使用较短的编码,反之出现概率低的则使用较长的编码。
树的路径长度是从树根到每一个结点的路径长度之和。其中带权路径长度WPL最小的二叉树称作最优二叉树或赫夫曼树。
1.赫夫曼编码的构造顺序明确,但码不是唯一的。
2.赫夫曼编码的字长参差不齐,硬件实现不方便。
3.只有在概率分布很不均匀时,赫夫曼编码才有显著的效果,而在信源分布均匀时,一般不使用赫夫曼编码。
#include "stdafx.h"
#include <stdlib.h>
#include <string.h>
typedef struct {
unsigned int weight;
unsigned int parent,lchild,rchild;
}HTNode,*HuffmanTree;/*动态分配数组存储哈夫曼树*/
typedef char **HuffmanCode; /*动态分配数组存储哈夫曼编码表*/
void select(HuffmanTree HT,int i,int *S1,int *S2);
int min1(HuffmanTree HT, int i);
void HuffmanCoding(HuffmanTree HT,HuffmanCode *HC, int *w, int n)
{/*w存放n个字符的权值(>0),构造哈夫曼树HT,并求出n个字符的哈夫曼编码HC*/
int i,s1,s2;
char *cd;
int start;
int c,f;
HuffmanTree p;
/*字符的个数小于等于1*/
if (n <= 1){
return;
}
/* n是字符的个数,m是构造好以后节点的个数 */
int m = 2 * n - 1;
HT = (HuffmanTree)malloc((m+1) * sizeof(HTNode));
for( p = HT + 1,i = 1;i <= n; ++i, ++p, ++w){
(*p).weight = *w;
(*p).parent = 0;
(*p).lchild = 0;
(*p).rchild = 0;
}
for(;i <= m; ++i, ++p){
(*p).parent = 0;
}
for(i = n + 1;i <= m; ++i){ /* 建赫夫曼树 */
/* 在HT[1~i-1]中选择parent为0且weight最小的两个结点,其序号分别为s1和s2 */
select(HT, i-1, &s1, &s2);
HT[s1].parent = HT[s2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
/* 从叶子到根逆向求每个字符的赫夫曼编码 */
*HC = (HuffmanCode)malloc((n+1) * sizeof(char*));
/* 分配n个字符编码的头指针向量([0]不用) */
cd = (char*)malloc(n * sizeof(char)); /* 分配求编码的工作空间 */
cd[n-1] = '\0'; /* 编码结束符 */
for(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); /* 释放工作空间 */
}
int min1(HuffmanTree HT, int i)
{
int j,flag;
unsigned int k = UINT_MAX;
for (j = 1; j <= i; j++){
if (HT[j].weight < k && HT[j].parent == 0){
k = HT[j].weight;
flag = j;
}
}
HT[flag].parent = 1;
return flag;
}
void select(HuffmanTree HT,int i,int *S1,int *S2)
{/* s1为最小的两个值中序号小的那个 */
int j;
*S1 = min1(HT,i);
*S2 = min1(HT,i);
if ( *S1 > *S2 ){
j = *S1;
*S1 = *S2;
*S2 = j;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
HuffmanTree HT = NULL;
HuffmanCode HC;
int *w,n,i;
printf("请输入权值的个数(>1):");
scanf("%d",&n);
w = (int*)malloc(n * sizeof(int));
printf("请依次输入%d个权值(整型):\n",n);
for(i = 0;i <= n-1; i++){
scanf("%d",w+i);
}
HuffmanCoding(HT, &HC, w, n);
for(i = 1;i <= n; i++){
puts(HC[i]);
}
free(HT);
HT = NULL;
return 0;
}
6.7 回溯法与树的遍历
大家说熟悉的八皇后的问题,就是利用试探和回溯(Backtracking)的搜索技术求解。回溯法也是涉及递归过程的一种重要方法,它的求解过程实质上是一个先序遍历一棵"状态树"的过程。