5.1 树的定义和基本术语

树的递归定义

树是n(n>=0)个结点的有限集。它
(1)或者是一棵空树(n=0),空树中不包含任何结点
(2)或者是一棵非空树(n>0),此时有且仅有一个特定的称为 根(root) 的结点;当n>1时,其余结点可以分为m(m>0)个互不相交的有限集T1,T1,…,Tm,其中每一个本身又是一棵树,并且称为根的子树(subtree)
第五章: 树和二叉树 - 图1

相关概念

:某个节点拥有子树的个数。
叶子节点:度为0的节点叫做叶子节点。
深度:树的层数(次)。(例如上面c图树的深度为3)
高度:从下往上数的层数。
双亲(父)节点:比如c图中A是B的双亲节点。
孩子节点:比如c图中B是A的孩子节点。
兄弟节点:出自同一个父节点的子节点。例如c图中的BCD。
堂兄弟节点:出自不同父节点,但是层数一样的的子节点。
森林:互不相关的树的集合。

性质

  • 树中的结点树等于树的边数加1,也等于所有结点的度数之和加1
  • 树中的结点数等于所有结点的度数加1
  • 度为m的树中第i层上至多有m^(i-1)个结点(i>=1)
  • 高度为h的m叉树至多有(m^h-1)/(m-1)个结点
  • 具有n个结点的m叉树的最小高度为[logm(n*(m-1)+1)]

————————————————

版权声明:本文为CSDN博主「KLeonard」的原创文章,遵循CC 4.0 BY-SA版权协议 原文链接:https://blog.csdn.net/gavin_john/article/details/72228937


5.2 二叉树

相关定义以及性质

二叉树的基本概念(定义,特性,存储结构等)xxxxxxyao的博客-CSDN博客二叉树的定义

二叉树链表的表示方式

  1. typedef struct BiNode{
  2. TElemType data;
  3. struct BiTNode * lchild,* rchild; //左右孩子指针,分别指向左右子树。
  4. }BiNode,* BiTree;

遍历二叉树

代码实现:

  1. #include<stdio.h>
  2. #include<malloc.h>
  3. #include<stdlib.h>
  4. typedef int TElemType;
  5. typedef struct BiTNode{
  6. TElemType data;
  7. struct BiTNode *lchild,*rchild;
  8. }BiTNode,*BiTree;
  9. void CreateBiTree(BiTree &T){
  10. TElemType ch;
  11. scanf("%d",&ch);
  12. if(ch == 0){
  13. T=NULL;
  14. }
  15. else {
  16. T = (BiTNode *)malloc(sizeof(BiTNode));
  17. T->data = ch;
  18. CreateBiTree(T->lchild);
  19. CreateBiTree(T->rchild);
  20. }
  21. };
  22. void PreOrderTraverse(BiTree T) {
  23. // printf("前序遍历的结果是:");
  24. if(T)
  25. {
  26. printf("%d",T->data);
  27. PreOrderTraverse(T->lchild);
  28. PreOrderTraverse(T->rchild);
  29. }
  30. }
  31. void InOrderTraverse(BiTree T){
  32. // printf("中序遍历的结果是:");
  33. if(T)
  34. {
  35. InOrderTraverse(T->lchild);
  36. printf("%d",T->data);
  37. InOrderTraverse(T->rchild);
  38. }
  39. }
  40. void PostOrderTraverse(BiTree T) {
  41. // printf("后序遍历的结果是:");
  42. if(T)
  43. {
  44. PostOrderTraverse(T->lchild);
  45. PostOrderTraverse(T->rchild);
  46. printf("%d",T->data);
  47. }
  48. }
  49. int main(){
  50. BiTree T;
  51. int n;
  52. printf("请创建一个二叉树:");
  53. CreateBiTree(T);
  54. while(n!=0){
  55. printf("前序遍历请输入1\n");
  56. printf("中序遍历请输入2\n");
  57. printf("后序遍历请输入3\n");
  58. printf("中止操作请输入0\n");
  59. scanf("%d",&n);
  60. if(n==1) {
  61. printf("前序遍历的结果是:");
  62. PreOrderTraverse(T);
  63. printf("\n");
  64. }
  65. if(n==2) {
  66. printf("中序遍历的结果是:");
  67. InOrderTraverse(T);
  68. printf("\n");
  69. }
  70. if(n==3) {
  71. printf("中序遍历的结果是:");
  72. PostOrderTraverse(T);
  73. printf("\n");
  74. }
  75. if(n==0) break;
  76. }
  77. return 0;
  78. }

5.3 二叉树的应用

  1. Status CountLeaf(BiTree T, int & count){
  2. if(T){
  3. if ((T->lchild==NULL)&&(T-> rchild==NULL)){
  4. count ++; return OK;
  5. }
  6. CountLeaf(T->lchild.count);
  7. //统计左子树中叶子结点个数
  8. CountLeaf(T-> rchild, count);
  9. //统计右子树中叶子结点个数
  10. }
  11. else return ERROR;
  12. }
  1. int Depth (BiTree T){
  2. if (!T) depthval = 0;
  3. else {
  4. depthLeft=Depth(T->lchild);
  5. depthRight=Depth(T->rchild);
  6. depthval = 1 + max(depthLeft,depthRight);
  7. }
  8. return depthval;
  9. }

5.4 树和森林

5.4.1 树的存储结构-双亲表示法

树的顺序存储结构:用结构数组存放树的结点,每个结点含两个域:

  • 数据域:存放结点本身信息
  • 双亲域:指示本结点的双亲结点在数组中位置

树的双亲表存储描述:

  1. #define MAX_TREE_SIZE 100
  2. typedef struct PTNode {
  3. TElemtype data;
  4. int parent;
  5. }PTNode;
  6. typedef struct {
  7. PTNode nodes[MAX_ TREE_SIZE];
  8. int r,n;
  9. }PTree;

优缺点:找双亲节点容易,找孩子节点难。

孩子链表的C语言描述

孩子结点结构:

  1. typedef struct CTNode {
  2. int child;
  3. struct CTNode *next;
  4. }*ChildPtr;

双亲结点结构

  1. typedef struct {
  2. TElemType data;
  3. ChildPtr firstchild;//孩子链表头指针
  4. }CTBox;

孩子链表结构:

  1. typedef struct {
  2. CTBox nodes[MAX_TREE_SIZE];
  3. int n, r;//结点数和根结点的位置
  4. }CTree;

优缺点:找孩子节点容易,找双亲节点难。 为了解决找双亲节点难的问题,可以改进孩子链表。

改变双亲节点,双亲结点结构为:

  1. typedef struct {
  2. int parent;
  3. TElemType data;
  4. ChildPtr firstchild;
  5. }CTBox;

孩子兄弟表示法

树的二叉链表存储表示

  1. typedef struct CSNode{
  2. ElemType data;
  3. struct CSNode *firstchild, *nextsibling;
  4. }CSNode,*CSTree;

其中firstchild为该节点的第一个子节点,nextsibling是第一个兄弟节点。

  • 优点:易于找到孩子节点
  • 缺点:1.不宜查找双亲。2.破坏了树的层次。

    5.4.2 树转化成对应的二叉树

    image.png
    image.png

    5.4.3 树的遍历

    先根遍历:若树不空,先访问根结点,再依次先根遍历各棵子树
    后根遍历:若树不空,先依次后根遍历各棵子树,再访问根结点

    树的先根遍历相当于对应二叉树的先序遍历 树的后根遍历相当于对应二叉树的中序遍历

5.4.4 森林转化为二叉树(不重要)

image.png
image.png


5.5 哈夫曼树(赫夫曼树)(重点)

前置定义:

  • 两个结点间的路径:从树中一个结点到另一个结点之间的分支。
  • 两个结点间的路径长度:路径上的分支数目。
  • 树的路径长度:从树根到每个结点的路径长度之和。
  • 权:一些有意义的实数
  • 结点带权路径长度:从该结点到树根的路径长度与结点权的乘积
  • 树的带权路径长度:树中所有叶子结点的带权路径长度之和

哈夫曼树(最优二叉树):设n个权值{w1,w2,…wn},构造一棵有n个叶子结点的二叉树,第i个叶子结点权值是wi,则其中带权路径长度最小的二叉树称为最优二叉树。
了解上述相关概念之后我们就可以直接开始构造最优二叉树(下面是重点中的重点

方法:
步骤1:根据给定的n个权值{w1, w2, …, wn},构造有n棵二叉树的森林F = {T1, T2, …, Tn},Ti(1≤i≤n)中只有一个带权值wi的根结点,其左、右子树均为空。
步骤2:在F中选取两棵根结点权值最小和次小的二叉树作为左、右子树构造一棵新二叉树 TkTk的根结点的权值为其左、右子树根结点的权值之和

也就是说结点权值为1、2,组成行二叉树的节点权值为3。

步骤3: 在F中删去这两棵二叉树(权值最小和次小的两个节点),把新的二叉树Tk加入F 。
步骤4: 重复步骤2和步骤3, 直到F中仅剩下一棵二叉树为止,这棵二叉树就是最优二叉树。

说明:相同权值的叶子结点所构造的赫夫曼树不唯一,但带权路径长度之和相等。 在最优二叉树中没有度为1的结点,根据二叉树的性质3可知有n个叶子结点的二叉树有**n-1个非终端结点,共有2n-1个结点

哈夫曼树的抽象存储结构

  1. typedef struct{
  2. unsigned int weight;//权值
  3. unsigned int parent, lchild, rchild;//双亲结点,左、右孩子结点
  4. }HTNode,*HuffmanTree;
  5. typedef char ** HuffmanCode // 这里是一个指向char指针的指针

这里的unsigned指的是无符号,详细定义见

C语言中unsigned_bang152101的博客-CSDN博客_c语言unsigned
关于typedef char ** HuffmanCode
这里的HuffmanCode其实就是char**的别名,而char**是是一个指向char指针的指针类型,至于为什么要这么写,后面代码会再详细解释,这里理解是什么意思即可。

通常这种也叫作二级指针

算法实现:

  1. void HuffmanCoding ( HuffmanTree &HT, int *w, int n )
  2. //这里传入了三个参数,一个是哈夫曼树HT,一个是权值w,一个是叶子节点的个数n
  3. { if (n<=1) return;
  4. //当叶子结点少于一个时候返回,因为本身就是一个哈夫曼树了。
  5. m=2*n-1;
  6. //m用来表示结点的总个数
  7. HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
  8. //为哈夫曼树HT分配存储空间,m+1是因为数组下标都是从0开始的,而我们习惯从1开始表示结点,零位置空着不用,便于描述。
  9. for (p=HT, i=1; i<=n; ++i, ++p, ++w) *p={*w,0,0,0};
  10. // 为叶子结点进行赋值,*w为该结点的权值,后面三个0分别为父节点,和左右子树。
  11. for ( ; i < =m; i++ ;++p) *p={0,0,0,0};
  12. // 为非终端结点进行赋值,四个0分别表示结点的权值、父节点、左右子树
  13. for (i=n+1;i<=m;++i)
  14. {Select(HT,i-1,s1,s2);
  15. //挑选出权值最小的s1和权值次小的s2,这个算法在后面详细阐述
  16. HT[s1].parent=i; HT[s2].parent=i;
  17. //给权值最小和次小的结点的父结点进行赋值
  18. HT[i].lchild=s1; HT[i].rchild=s2;
  19. //把这两个结点赋值给这个父结点的两个左右子树
  20. HT[i].weight=HT[s1].weight+HT[s2].weight;
  21. //父结点的权值是两个子树的权值之和
  22. }

完整代码:

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>
  4. #include<malloc.h>
  5. # define MAX 1000
  6. typedef struct{
  7. unsigned int weight;
  8. unsigned int parent,lchild,rchild;
  9. }HTNode,*HuffmanTree;
  10. typedef char **HuffmanCode;
  11. void Select(HuffmanTree HT,int n,int &s1,int &s2){
  12. int i,change;
  13. int min1,min2;
  14. min1=MAX;
  15. min2=MAX;
  16. s1=0;
  17. s2=0;
  18. for(i=1;i<=n;i++)
  19. {
  20. if(HT[i].weight<min1&&HT[i].parent==0)
  21. {
  22. min2=min1;
  23. s2=s1;
  24. min1=HT[i].weight;
  25. s1=i;
  26. }
  27. else if(HT[i].weight<min2&&HT[i].parent==0)
  28. {
  29. min2=HT[i].weight;
  30. s2=i;
  31. }
  32. }
  33. if(s1>s2)
  34. {
  35. change=s1;
  36. s1=s2;
  37. s2=change;
  38. }
  39. }
  40. void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n){
  41. if(n<=1) return;
  42. int m,i,s1,s2,start,c,f;
  43. HuffmanTree p;
  44. char *cd;
  45. m =2*n-1;
  46. HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));
  47. for(p=HT+1,i=1;i<=n;++i,++p,++w){
  48. *p={*w,0,0,0};
  49. }
  50. for(;i<=m;++i,++p){
  51. *p={0,0,0,0};
  52. }
  53. for(i=n+1;i<=m;++i){
  54. Select(HT,i-1,s1,s2);
  55. HT[s1].parent=i;HT[s2].parent=i;
  56. HT[i].lchild=s1;HT[i].rchild=s2;
  57. HT[i].weight=HT[s1].weight+HT[s2].weight;
  58. }
  59. HC=(HuffmanCode)malloc((n+1)*sizeof(char *));
  60. cd = (char *)malloc(n*sizeof(char));
  61. cd[n-1]='\0';
  62. for(i=1;i<=n;++i){
  63. start=n-1;
  64. for(c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent){
  65. if(HT[f].lchild==c) cd[--start]='0';
  66. else cd[--start]='1';
  67. }
  68. HC[i]=(char *)malloc((n-start)*sizeof(char));
  69. strcpy(HC[i],&cd[start]);
  70. }
  71. free(cd);
  72. printf("构建的赫夫曼树如下:\n");
  73. for(i=1;i<=m;i++)
  74. {
  75. printf("%d--%d--%d--%d\n",HT[i].weight,HT[i].parent,HT[i].lchild,HT[i].rchild);
  76. }
  77. for(i=1;i<=n;i++) printf("第%d个字符的编码是:%s\n",i,HC[i]);
  78. }
  79. int main()
  80. {
  81. int *w;
  82. int n,i;
  83. HuffmanTree HT;
  84. HuffmanCode HC;
  85. printf("请输入叶子结点数目:");
  86. scanf("%d",&n);
  87. w=(int *)malloc(n*sizeof(int));
  88. printf("\n请依次输入%d个叶子结点的权值:",n);
  89. for(i=0;i<n;i++){
  90. scanf("%d",&w[i]);
  91. }
  92. HuffmanCoding(HT,HC,w,n);
  93. return 1;
  94. }

更多关于赫夫曼树的讲解

哈夫曼树D_Double’s Journey-CSDN博客哈夫曼树