目录
树的存储结构
在大量的应用中,人们使用了多种形式的存储结构来表示树,其中最常见的有三种。
1.双亲表示法
双亲表示法以一组连续的存储单元存储树的节点,每个节点除了数据域data外,还附设了一个parent域用以指示其双亲节点的位置。
这种存储结构利用了每个节点(除根以外)只有唯一的双亲的性质。因此求节点双亲很方便,但求节点的孩子节点就需要遍历整个结构。
代码展示:
typedef struct Snode //结点结构
{
ElemType data;
int parent;
}PNode;
typedef struct //树结构
{
PNode tnode[MAX_SIZE];
int n; //结点个数
}PTree;
void InitPNode(PTree &tree)
{
int i,j;
char ch;
printf("请输入结点个数:\n");
scanf("%d",&(tree.n));
printf("请输入结点的值及其双亲序号:\n");
for(i=0;i<tree.n;i++)
{
scanf("%c,%d",&ch,&j);
tree.tnode[i].data = ch;
tree.tnode[i].parent = j;
}
tree.tnode[0].parent = -1;
}
void FindParent(PTree &tree)
{
int i;
printf("请输入要查询的结点的序号\n");
scanf("%d",&i);
printf(" %c 的双亲结点序号为 %d\n",tree.tnode[i].data,tree.tnode[i].parent);
}
2.孩子表示法
树中每个节点可能有多棵子树,则可用多重链表,每个节点有多个指针域,其中每个指针都指向一棵子树的根节点。
data | child1 | child2 | …… | childd |
data | degree | child1 | child2 | …… | childd |
与双亲表示法恰恰相反,孩子表示法在求节点的孩子时很容易,求节点的双亲时需要遍历整个树。
代码展示:
typedef struct CNode
{
int pos;
struct CNode *next;
}CNode, *PChild;
typedef struct
{
char data;
PChild child;
}CTRoot;
typedef struct
{
CTRoot Node[MAX];
int sum; //根结点数
}SqCTree,*CTree;
void InitCTree(CTree &CT)//初始化树
{
CT = (SqCTree *)malloc(sizeof(SqCTree));
CT->sum = 0;
for(int i = 0; i< MAX; i++)
{
CT->Node[i].child = (CNode *)malloc(sizeof(CNode));
CT->Node[i].child->next = NULL;
}
printf("初始化!\n");
}
void CreateCTree(CTree &CT)//创建树
{
printf("请输入结点数:");
scanf("%d", &CT->sum);
fflush(stdin);
int n;
char ch;
for(int i = 0; i < CT->sum; i++)
{
printf("请输入第%d个结点数据及孩子数和孩子位置:",i);
scanf("%c", &ch);
CT->Node[i].data = ch;
scanf("%d", &n);
PChild p = CT->Node[i].child;
for(int j = 0; j < n; j++)
{
PChild s;
s = (CNode *)malloc(sizeof(CNode));
scanf("%d", &s->pos);
s->next = NULL;
p->next = s;
p = p->next;
}
fflush(stdin);
}
}
void LevelOrderTraverse(CTree CT)//层序遍历
{
for(int i = 0; i < CT->sum; i++)
printf("%c ", CT->Node[i].data);
printf("\n");
}
3.孩子兄弟表示法
孩子兄弟表示法在形式上是一种二叉链表的表示
如图:
在链表中凡是节点左链域指向的下一个节点为该节点的孩子节点,凡是在节点右链域指向的下一个节点为该节点的兄弟节点。利用这种存储结构便于实现各种树的操作。首先易于实现找节点孩子等操作。例如,若要访问节点x的第i个孩子,则只要先从左链域找到第1个孩子节点,然后沿着孩子节点的右链域连续走i-1步,便可找到x的第i个孩子。当然,如果为每个节点增设一个parent 域,则同样能方便地实现查找双亲的操作。
这种存储结构的优点是它和二叉树的二叉链表表示完全一样,便于将一般的树结构转换为二
叉树进行处理,利用二叉树的算法来实现对树的操作。
//--树的二叉链表(孩子兄弟表示法)存储表示--
typedef struct CSNode{
ElemType data;
struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;
二叉树与森林的转换
森林是由多个树组成的
二叉树转变为森林的形式是将每个树都通过孩子兄弟表示法转变为二叉树,再使这些二叉树的根节点相连。(树变二叉根相连)
森林转变为二叉树的形式是将森林的根节点及其右节点分隔开,再将分隔后的得到的二叉链表转化为树。(根去右孩,二叉变树)
如图:
哈夫曼树
基本概念
哈夫曼(Hufinan)树又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用涂。哈夫曼树的定义,涉及路径、路径长度、权等概念,下面先给出这些概念的定义,再介绍哈夫曼树。
(1)路径:从树中一个节点到另一个节点之间的分支构成这两个节点之间的路径。
(2)路径长度:路径上的分支数目称作路径长度。
(3)树的路径长度:从树根到每一叶子节点的路径长度之和。
(4)权:赋于某个实体的一个量,是对实体的某个或某些属性的数值化描述。在数据结构中,实体有节点(元素)和边(关系)两大类,所以对应有节点权和边权。节点权或边权具体代表什么意义,由具体情况决定。如果在一棵树中的节点上带有权值,则对应的就有带权树等概念。
(5)节点的带权路径长度:从该节点到树根之间的路径长度与节点上权值的乘积。
(6)树的带权路径长度:树中所有叶子节点的带权路径长度之和。
通常记作 WPL=w1k1+w2k2+…+wnkn。
(7)哈夫曼树:假设有m个权值(W1,W2,…,Wm },可以构造一棵含n个叶子节点的二叉树,每个叶子节点的权值为wi,则其中带权路径长度WPL最小的二叉树称作最优二叉树或哈夫曼树。
如图:为具有不同带权路径长度的二叉树
哈夫曼树的构造过程
1.给定n个权值{W1,W2,…,Wn},构造n棵只有根节点的二叉树,这n棵二叉树构成森林F。
2.在森林F中选取两棵根节点的权值最小的树作为左右子树构造一棵新的二叉树,并置新的二叉树的根节点的权值为其左、右子树上根节点的权值之和。
3.在F中删除以使用的这两棵树,同时加入新得到的二叉树。
4.重复2和3步骤直到F中只剩一棵树,这棵树即为哈夫曼树
哈夫曼树的实现
哈夫曼树是一种二叉树,由于哈夫曼树中没有度为1的节点,则一棵有n个叶子节点的哈夫曼树共有2n-1个节点,可以存储在一个大小为2n-1的一维数组中。树中的每个节点还要包含其双亲信息和孩子节点的信息。
weight | parent | lchild | rchild |
(1)初始化:首先动态申请2n个单元;然后循环2n-1次,从1开始将1到2n-1所有单元中的双亲,左、右孩子的下标都初始化为0;最后循环n次,输入前n个单元中的叶子节点的权值。
(2)创建树:循环n-1次,通过n -1次的选择、删除与合并来创建哈夫曼树。选择是从当前森林中选择双亲为0且权值最小的两个树根节点s1和s2;删除是指将节点s1和s2的双亲改为非0;合并就是将s1和s2的权值和作为一个新节点的权值依次存入数组的n+1号及之后的单元中,同时记录这个新节点左孩子的下标为s1,右孩子的下标为s2。
代码展示:
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct{
int weight;
int parent,lchild,rchild;
}HTNode,*HuffmanTree;
int Select(HuffmanTree &HT,int n,int &s1,int &s2)
{
int min;
for(int j=1;j<=n;++j)
{
if(HT[j].parent==0)
{
min=j;
break;
}
}
for(int j=1;j<=n;++j)
{
if(HT[j].parent==0)
{
if(HT[j].weight<HT[min].weight)
{
min=j;
}
}
}
s1=min;
for(int j=1;j<=n;++j)
{
if(HT[j].parent==0&&j!=s1)
{
min=j;
break;
}
}
for(int j=1;j<=n;++j)
{
if(HT[j].parent==0&&j!=s1)
{
if(HT[j].weight<HT[min].weight)
{
min=j;
}
}
}
s2=min;
}
void CreateHuffmanTree(HuffmanTree &HT,int n)
{
int s1,s2,m;
if(n<=1) return ;
m=2*n-1;
HT=(HuffmanTree)malloc(sizeof(HTNode)*(m+1));
for(int i=1;i<=m;++i)
{
HT[i].weight=0;
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(int i=1;i<=n;++i)
{
scanf("%d",&HT[i].weight);
}
for(int i=n+1;i<=m;++i)
{
Select(HT,i-1,s1,s2);
HT[s1].parent=i;
HT[s2].parent=i;
HT[i].lchild=s1;
HT[i].rchild=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
printf("%d (%d, %d)\n",HT[i].weight,HT[s1].weight,HT[s2].weight);
}
}
int main()
{
HuffmanTree HT;
int n;
scanf("%d",&n);
CreateHuffmanTree(HT,n);
return 0;
}