数据结构 第5章(树和二叉树)

1. 树和二叉树的定义

1.1 树的定义

  • 树(Tree): n ( n ≥ 0 )个结点的有限集
    • 空树:n = 0
    • 非空树 T :
      • 有且只有一个称之为根的结点
      • 除根结点外的其余结点可分为 m ( m > 0)个互不相交的有限集 T_1 ,T_2 ,··· ,T_m
        • 其中每个集合本身又是一颗树,称为根的子树(SubTree)

树的示例

1.2 树的基本术语

  • 结点:树种的一个独立单元,包含一个数据元素及若干指向其子树的分支
  • 结点的度:结点拥有的子树数量
  • 树的度:树内各结点度的最大值
  • 叶子(终端结点):度为 0 的结点
  • 非终端结点(分支结点):度不为 0 的结点;除根节点外,非终端结点也称为内部结点
  • 双亲和孩子
    • 结点的子树的根称为该结点的孩子
    • 该结点称为孩子的双亲
  • 兄弟:同一个双亲的孩子之间互称兄弟
  • 祖先:从根到该结点所经分支上的所有结点
  • 子孙:以某结点为根的子树种的任一结点
  • 层次:从根开始定义起,根为第一层,根的孩子为第二层,依次类推
  • 堂兄弟:双亲在同一层的结点互为堂兄弟
  • 树的深度(高度):树中结点的最大层次
  • 有序树和无序树
    • 有序树:树中结点的各子树是从左至右有次序的(不能互换)
      • 第一个孩子:最左边的子树的根
      • 最后一个孩子:最右边的子树的根
    • 无序树:与有序树相反
  • 森林:m ( m ≥ 0 )棵互不相交的树的集合

1.3 二叉树的定义

  • 二叉树(Binary Tree):n ( n ≥ 0 )个结点所构成的集合

    • 空树:n = 0
    • 非空树 T :
      • 有且只有一个称之为根的结点
      • 除根结点外的其余结点分为两个互不相交的子集 T_1 和 T_2 ,分别称为 T 的左子树和右子树
        • T_1 和 T_2 本身又都是二叉树
  • 二叉树和树的区别

    • 二叉树每个结点至多只有两棵子树
      • 二叉树中不存在度大于 2 的结点
    • 二叉树的子树有左右之分,其次序不能任意颠倒
  • 二叉树的 5 种基本形态

    • 空二叉树
    • 仅有根结点的二叉树
    • 右子树为空的二叉树
    • 左、右子树均非空的二叉树
    • 左子树为空的二叉树

2. 二叉树的性质和存储过程

2.1 二叉树的性质

  • 满二叉树:深度为 k 且含有 2^k - 1 个结点
    • 每一层上的结点数都是最大的结点数
      • 每一层 i 的结点数都具有最大值 2^(i-1)

满二叉树

  • 完全二叉树:深度为 k 的,有 n 个结点的二叉树,每一个结点都与深度为 k 的满二叉树中编号从 1 至 n 的结点一一对应
    • 叶子结点只可能是在层次最大的两层上出现
    • 对任一结点,若其右分支下的子孙的最大层次为 l ,则其左分支下的子孙的最大层次必为 l 或 l+1

在这里插入图片描述

非完全二叉树①

非完全二叉树②

  • 重要性质

    • 性质 1 :在二叉树的第 i 层上至多有 2^(i-1) 个结点(i ≥ 1)

    • 性质 2 :深度为 k 的二叉树至多有 2^k - 1 个结点(k ≥ 1)

    • 性质 3 :对任何一颗二叉树 T ,如果其终端结点数为 n_0 ,度为 2 的结点数为 n_2 ,则 n_0 = n_2 + 1
      n = n 0 + n 1 + n 2 n = n_0 + n_1 + n_2 n=n0+n1+n2

      n = ( 1 n 1 + 2 n 2 ) + 1 n = (1n_1 + 2n_2) + 1 n=(1n1+2n2)+1

    n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1

    • 性质 4 :具有 n 个结点的完全二叉树的深度为 ⌊ log_2 (n) ⌋ + 1

    • 性质 5 :如果对一棵有 n 个结点的完全二叉树(其深度为 ⌊ log_2 (n) ⌋ + 1 )的结点按层序编号(从第 1 层到第 ⌊ log_2 (n) ⌋ + 1 层,每层从左到右 ),则对任一结点 i ( 1 ≤ i ≤ n )

      • i = 1 :结点 i 是二叉树的根,无双亲
      • i > 1 :其双亲是结点 ⌊ i/2 ⌋
      • 2i > n :结点 i 无左孩子(结点 i 为叶子结点)或左孩子为结点 2i
      • 2i + 1 > n :结点 i 无右孩子或右孩子为结点 2i+1

      结点 i 和结点 i+1 在同一层上

      结点 i 和结点 i+1 不在同一层上

2.2 二叉树的存储结构

2.2.1 顺序存储结构

#define MAXSIZE 10								// 二叉树的最大结点数

typedef TElemType SqBiTree[MAXSIZE];				// 0 号单元存储根结点		
SqBiTree bt;
  • 对于完全二叉树:从根起按层序存储,依次自上而下、从左至右存储结点元素
    • 将完全二叉树上编号为 i 的结点元素存储在如上定义的一维数组中下标为 i-1 的分量中
  • 对于一般二叉树:将其每个结点与完全二叉树上的结点相对照,存储在一维数组的相应分量中

2.2.2 链式存储结构

  • 构成
    • 数据域
    • 左指针域
    • 右指针域
    • 指向双亲结点的指针域
  • 在含有 n 个结点的二叉链表中有 n+1 个空链域
typedef struct BiTNode {
    ElemType elem;									// 结点数据域
    struct BiTNode *lchild;							// 左孩子指针
    struct BiTNode *rchild;							// 右孩子指针
} BiTNode, *BiTree;

二叉链表

3. 遍历二叉树和线索二叉树

3.1 遍历二叉树

  • 遍历二叉树(traversing ):按某条搜索路径巡防树种每个结点,使得每个结点均被访问一次,且仅被访问一次

  • 先序遍历二叉树

    • 访问根节点
    • 先序遍历左子树
    • 先序遍历右子树
  • 中序遍历二叉树

    • 中序遍历左子树
    • 访问根节点
    • 中序遍历右子树
  • 后序遍历二叉树

    • 后序遍历左子树
    • 后序遍历右子树
    • 访问根节点
  • 先序遍历:从上至下,从左至右

  • 中序遍历:从左至右,从上至下

  • 后序遍历:从下至上,从左至右

3.1.1 中序遍历的递归算法

void InOrderTraverse(BiTree T) {
	if (T) {
		InOrderTraverse(T->lchild);					// 中序遍历左子树
		printf("value = %d\n", T->value);			// 访问根节点
		InOrderTraverse(T->rchild);					// 中序遍历右子树
	}
}

3.1.2 中序遍历的非递归算法

  • 步骤
    • 初始化一个空栈 S ,指针 p 指向根结点
    • 申请一个结点空间 q ,用来存放栈顶弹出的元素
    • 当 p 非空或栈 S 非空时,循环执行以下操作
      • p 非空,p 进栈,p 指向该结点的左孩子
      • p 为空,弹出栈顶元素并访问,将 p 指向该结点的右孩子
void InOrderTraverse(BiTree T) {
    BiTree* p = T;
    BiNode q = (BiNode*)malloc(sizeof(BiNode));
    
    InitStack(S);
    while (p || StackEmpty(S)) {
        if (p) {									// p 非空
            Push(S, p);								// 根指针进栈
            p = p->lchild;							// 遍历左子树
        } else {
            Pop(S, q);								// 退栈
            printf("value = %d\n", T->value);		// 访问根节点
            p = q->rchild;							// 遍历右子树
        }
    }
}

3.1.3 先序遍历的顺序建立二叉链表

void CreateBiTree(BiTree* T) {
	int value;
	printf("Enter : ");
	scanf("%d", &value);

	if (value == 0) {
		(*T) = NULL;								// 递归结束,创建空树
	}
	else {
		(*T) = (BiTNode*)malloc(sizeof(BiTNode));	// 生成根结点
		(*T)->value = value;						// 根结点数据域置为 value
		CreateBiTree(&((*T)->lchild));				// 递归创建左子树
		CreateBiTree(&((*T)->rchild));				// 递归创建右子树
	}
}

3.1.4 复制二叉树

void Copy(BiTree T, BiTree* NewT) {
	if (T == NULL)
	{
		*NewT = NULL;								// 递归结束
	}
	else {
		*NewT = (BiTNode*)malloc(sizeof(BiTNode));	
		(*NewT)->value = T->value;					// 复制根结点
		printf("copy value = %d\n", T->value);
		Copy(T->lchild, &(*NewT)->lchild);			// 递归复制左子树
		Copy(T->rchild, &(*NewT)->rchild);			// 递归复制右子树
	}
}

3.1.5 计算二叉树的深度

int Depth(BiTree T) {
	if (T)
	{
		int i = Depth(T->lchild);					// 递归计算左子树的深度记为 i
		int j = Depth(T->rchild);					// 递归计算右子树的深度记为 j
		return (i > j) ? i + 1 : j + 1;
	}
	else
	{
		return 0;									// 递归结束,深度为 0
	}
}

3.1.6 统计二叉树中结点的个数

int NodeCount(BiTree T) {
	return T ? NodeCount(T->lchild) + NodeCount(T->rchild) + 1 : 0;
    // T 不为空 : 结点个数 = 左子树的结点个数 + 右子树结点个数 + 1
    // T 为空 : 结点个数为 0 ,递归结束
}

测试代码

#include <stdio.h>
#include <stdlib.h>

void InOrderTraverse(BiTree);
void CreateBiTree(BiTree);
void Copy(BiTree, BiTree);
int Depth(BiTree);
int NodeCount(BiTree);

typedef struct BiTNode {
	int value;
	struct BiTNode* lchild;
	struct BiTNode* rchild;
} BiTNode, *BiTree;

int main() {
	BiTree tree1;
	BiTree tree2;
    
	CreateBiTree(&tree1);
	printf("Create Success\n");
	printf("****************\n");

	InOrderTraverse(tree1);
	printf("Traverse Success\n");
	printf("****************\n");

	Copy(tree1, &tree2);
	printf("Copy Success\n");
	printf("****************\n");

	InOrderTraverse(tree2);
	printf("Traverse Success\n");
	printf("****************\n");

	int depth = Depth(tree1);
	printf("depth = %d\n", depth);
	printf("****************\n");

	int count = NodeCount(tree1);
	printf("count = %d\n", count);
	printf("****************\n");
}

void InOrderTraverse(BiTree T) {
	if (T) {
		InOrderTraverse(T->lchild);
		printf("value = %d\n", T->value);
		InOrderTraverse(T->rchild);
	}
}

void CreateBiTree(BiTree* T) {
	int value;
	printf("Enter : ");
	scanf("%d", &value);

	if (value == 0) {
		*T = NULL;
	}
	else {
		*T = (BiTNode*)malloc(sizeof(BiTNode));
		(*T)->value = value;
		CreateBiTree(&((*T)->lchild));
		CreateBiTree(&((*T)->rchild));
	}
}

void Copy(BiTree T, BiTree* NewT) {
	if (T == NULL)
	{
		*NewT = NULL;
	}
	else {
		*NewT = (BiTNode*)malloc(sizeof(BiTNode));
		(*NewT)->value = T->value;
		printf("copy value = %d\n", T->value);
		Copy(T->lchild, &(*NewT)->lchild);
		Copy(T->rchild, &(*NewT)->rchild);
	}
}

int Depth(BiTree T) {
	if (T)
	{
		int i = Depth(T->lchild);
		int j = Depth(T->rchild);
		return (i > j) ? i + 1 : j + 1;
	}
	else
	{
		return 0;
	}
}

int NodeCount(BiTree T) {
	return T ? NodeCount(T->lchild) + NodeCount(T->rchild) + 1 : 0;
}

3.2 线索二叉树

  • 线索链表:以特殊结点结构构成的二叉链表作为二叉树的存储结构
    • 线索:指向结点前驱和后继的指针
    • 线索二叉树(Thread Binary Tree):带有线索的二叉树
    • 线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程
    • 结点形式:
      • 左孩子
      • 左标志
        • 0 :指向结点的左孩子
        • 1 :指向结点的前驱
      • 数据
      • 右标志
        • 0 :指向结点的右孩子
        • 1 :指向结点的后继
      • 右孩子
typedef struct BiThrNode {
    ElemType elem;
    struct BiThrNode *lchild, *rchild;				// 左右孩子指针
    int LTag, RTag;									// 左右标志
} BiThrNode, *BiThrTree;

3.2.1 以结点 p 为根的子树中序线索化

  • 步骤
    • p 非空,左子树递归线索化
    • p 的左孩子为空,给 p 加上左线索,将 LTag 置为 1 ,让 p 的左孩子指针指向 pre (前驱)
      • 非空,将 p 的 LTag 置为 0
    • pre 的右孩子为空,给 pre 加上右线索,将 RTag 置为 1 ,让 pre 的左孩子指针指向 p (后继)
      • 非空,将 pre 的 RTag 置为 0
    • 将 pre 指向刚访问过的结点
      • pre = p
    • 右子树递归序列化
void InThreading(BiThrTree p) {
    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);						// 右子树递归序列化
    }
}

3.2.2 带头结点的二叉树中序线索化

void InOrderThreading(BiThrTree* p, BiThrTree T) {
    *p = (BiThrNode*)malloc(sizeof(BiThrNode));		// 建立头结点
    (*p)->LTag = 0;									// 头结点有左孩子,若树非空,则其左孩子为树根
    (*p)->RTag = 1;									// 头结点的右孩子指针为右线索
    (*p)->rchild = *p;								// 初始化时右指针指向自己
    
    if (!T)
    {
        (*p)->lchild = *p;							// 若树为空,则左指针也指向自己
    }
    else
    {
        (*p)->lchild = T;							// 头结点的左孩子指向根
        pre = *p;									// pre 初值指向头结点
        InThreading(T);								// 对以 T 为根的二叉树进行中序线索化
        printf("InThreading Success\n");			
        pre->rchild = *p;							// pre 为最右结点,pre 的右线索指向头结点
        pre->RTag = 1;
        (*p)->rchild = pre;							// 头结点的右线索指向 pre
    }
}

3.2.3 遍历线索二叉树

  • 在中序线索二叉树中查找

    • 查找 p 指针所指结点的前驱
      • p->LTag = 1:p 的左链指示其前驱
      • p->LTag = 0:p 有左子树,结点的前驱是遍历左子树时最后访问的一个结点(左子树中最右下的结点)
    • 查找 p 指针所指结点的后继
      • p->RTag = 1:p 的右链指示其后继
      • p->RTag = 0:p 有右子树,结点的后继是遍历右子树时访问的第一个结点(右子树中最左下的结点)
  • 在先序线索二叉树中查找

    • 查找 p 指针所指结点的前驱
      • p->LTag = 1:p 的左链指示其前驱
      • p->LTag = 0:p 有左子树
        • 结点的前驱
          • *p 是其双亲的左孩子,则其前驱为其双亲结点
          • *p 是其双亲的右孩子,则其前驱为其双亲左子树上先序遍历最后访问二点结点
    • 查找 p 指针所指结点的后继
      • p->RTag = 1:p 的右链指示其后继
      • p->RTag = 0:p 有右子树,*p 的后继是其左子树根(若存在)或右子树根
  • 在后序线索二叉树中查找

    • 查找 p 指针所指结点的前驱

      • p->LTag = 1:p 的左链指示其前驱
      • p->LTag = 0:
        • p->RTag = 1:p 的左链指示其前驱
        • p->RTag = 0:p 的右链指示其前驱
    • 查找 p 指针所指结点的后继

      • *p 是二叉树的根:后继为空

      • *p 是其双亲的右孩子:后继为双亲结点

      • *p 是其双亲的左孩子:

        • *p 没有右兄弟:后继为双亲结点
        • *p 有右兄弟:后继为双亲的右子树上按后序遍历出的第一个结点(右子树中最左下的叶结点)
遍历中序线索二叉树
  • 步骤
    • 指针 p 指向根结点
    • p 为非空树或遍历未结束时,循环执行以下操作
      • 沿左孩子向下,到达最左下结点 *p ,它是中序的第一个结点
      • 访问 *p
      • 沿右线索反复查找当前结点 *p 的后继结点并访问后继结点,直至右线索为 0 或者遍历结束
      • 转向 p 的右子树
void InOrderThraverse_Thr(BiThrTree T)
{
    pre = T->lchild;								// p 指向根结点

    while (pre != T)								// 空树或遍历结束时,pre == T
    {
        while (pre->LTag == 0)
        {
            pre = pre->lchild;						// 沿左孩子向下
        }

        printf("value = %d\n", pre->value);			// 访问其左子树为空的结点

        while (pre->RTag == 1 && pre->rchild != T)
        {
            pre = pre->rchild;						// 沿右线索
            printf("value = %d\n", pre->value);		// 访问后继结点
        }

        pre = pre->rchild;							// 转向 p 的右子树
    }
}

测试代码

#include <stdio.h>
#include <stdlib.h>

void CreateBiThrTree(BiThrTree);
void InThreading(BiThrTree);
void InOrderThreading(BiThrTree, BiThrTree);
void InOrderThraverse_Thr(BiThrTree);

typedef struct BiThrNode {
    int value;
    struct BiThrNode* lchild, * rchild;
    int LTag, RTag;
} BiThrNode, *BiThrTree;

BiThrTree pre = NULL;

int main() {
    pre = (BiThrNode*)malloc(sizeof(BiThrNode));
    pre->rchild = NULL;

    BiThrTree tree1;
    BiThrTree tree2;

    CreateBiThrTree(&tree1);
    printf("Create Success\n");
    printf("****************\n");

    InOrderThreading(&tree2, tree1);
    printf("InOrderThreading Success\n");
    printf("****************\n");

    InOrderThraverse_Thr(tree2);
    printf("InOrderThraverse_Thr Success\n");
    printf("****************\n");
}

void CreateBiThrTree(BiThrTree* p) {
    int value;
    printf("Enter : ");

    scanf("%d", &value);

    if (value == 0) {
        *p = NULL;
    }
    else {
        *p = (BiThrNode*)malloc(sizeof(BiThrNode));
        (*p)->value = value;
        CreateBiThrTree(&((*p)->lchild));
        CreateBiThrTree(&((*p)->rchild));
    }
}

void InThreading(BiThrTree p) {
    if (p) {
        InThreading(p->lchild);

        if (!p->lchild) {
            p->LTag = 1;
            p->lchild = pre;
        }
        else {
            p->LTag = 0;
        }

        if (!pre->rchild) {
            pre->RTag = 1;
            pre->rchild = p;
        }
        else {
            pre->RTag = 0;
        }

        pre = p;

        InThreading(p->rchild);
    }
}

void InOrderThreading(BiThrTree* p, BiThrTree T) {
    *p = (BiThrNode*)malloc(sizeof(BiThrNode));
    (*p)->LTag = 0;
    (*p)->RTag = 1;
    (*p)->rchild = *p;
    
    if (!T)
    {
        (*p)->lchild = *p;
    }
    else
    {
        (*p)->lchild = T;
        pre = *p;
        InThreading(T);
        printf("InThreading Success\n");
        pre->rchild = *p;
        pre->RTag = 1;
        (*p)->rchild = pre;
    }
}

void InOrderThraverse_Thr(BiThrTree T)
{
    pre = T->lchild;

    while (pre != T)
    {
        while (pre->LTag == 0)
        {
            pre = pre->lchild;
        }

        printf("value = %d\n", pre->value);

        while (pre->RTag == 1 && pre->rchild != T)
        {
            pre = pre->rchild;
            printf("value = %d\n", pre->value);
        }

        pre = pre->rchild;
    }
}

4. 树和森林

4.1 树的存储结构

4.1.1 双亲表示法

  • 以一组连续的存储单元存储树的结点,每个结点除了数据域 data 外,还附设一个 parent 域用以表示其双亲结点的位置
  • 构成
    • 数据域
    • 双亲域

树的双亲表示法

4.1.2 孩子表示法

  • 构成
    • 数据域
    • 孩子域

孩子链表

  • 与双亲表示法结合:带双亲的孩子链表

带双亲的孩子链表

4.1.3 孩子兄弟表示法

  • 孩子兄弟表示法:二叉树表示法、二叉链表表示法

  • 构成

    • 第一个孩子结点
    • 数据域
    • 下一个兄弟结点
typedef struct CSNode {
    ElemType elem;
    struct CSNode *firstchild;
    struct CSNode *nextsibling;
} CSNode, *CSTree;

树的二叉链表表示法

4.2 森林与二叉树的转换

4.2.1 森林转换成二叉树

  • F = { T_1 ,T_2 ,··· ,T_m } 为森林,转化成一颗二叉树 B = { root ,LB ,RB }
    • F 为空(m = 0):B 为空树
    • F 非空(m ≠ 0):
      • B 的根 root :森林中第一棵树的根 ROOT( T_1 )
      • B 的左子树 LB :从 T_1 中根结点的子树森林 F_1 = { T_11 ,T_12 ,··· ,T_1m } 转换而成的二叉树
      • B 的右子树 RB :从森林 F’ = { T_2 ,T_3 ,··· ,T_m } 转换而成的二叉树

4.2.2 二叉树转换成森林

  • B = { root ,LB ,RB } 为一颗二叉树,转化成森林 F = { T_1 ,T_2 ,··· ,T_m }
    • B 为空:F 为空
    • B 非空:
      • F 中第一棵树 T_1 的根 ROOT( T_1 ):B 的根 root
      • T_1 中根结点的子树森林 F_1:B 的左子树 LB 转换而成的森林
      • 除 T_1 之外其余树组成的森林 F’ = { T_2 ,T_3 ,··· ,T_m } :B 的右子树 RB 转换而成的森林

4.3 树和森林的遍历

4.3.1 树的遍历

  • 先根遍历:先访问树的根结点,然后依次先根遍历每棵子树
  • 后根遍历:先依次后根遍历每棵子树,然后访问根结点

4.3.2 森林的遍历

  • 先序遍历森林

    • 访问森林中第一棵树的根结点
    • 先序遍历第一棵树的根结点的子树森林
    • 先序遍历除去第一棵树之后剩余的树构成的森林
  • 中序遍历森林

    • 中序遍历第一棵树的根结点的子树森林

    • 访问森林中第一棵树的根结点

    • 中序遍历除去第一棵树之后剩余的树构成的森林

5. 哈夫曼树及其应用

5.1 哈夫曼树的基本概念

5.1.1 哈夫曼树的定义

  • 哈夫曼树(Huffman Tree)(最优树):一类带权路径长度最短的树

5.1.2 哈夫曼树的基本术语

  • 路径:从树中的一个结点到另一个结点之间的分支构成这两个结点之间的路径

  • 路径长度:路径上的分支数目

  • 树的路径长度:从树根到每一结点的路径长度之和

  • :对实体的数值化描述

    • 结点权
    • 边权
  • 结点的带权路径长度:从该结点到树根之间的路径长度与结点上全的乘积

  • 树的带权路径长度:树中所有叶子结点的带权路径长度之和,记作
    W P L = ∑ k = 1 n w k l k WPL = \sum_{k=1}^{n}{w_kl_k} WPL=k=1nwklk

  • 哈夫曼树:m 个权值 { w_1 ,w_2 ,··· ,w_m },可以构造一棵含 n 个叶子结点的二叉树,每个叶子结点的权为 w_i ,则其中带权路径长度 WPL 最小的二叉树称作最优二叉树或哈夫曼树

5.2 哈夫曼树的构造算法

5.2.1 哈夫曼树的构造过程

  • 根据给定的 n 个权值 { w_1 ,w_2 ,··· ,w_n },构造 n 棵只有根结点的二叉树,这 n 棵二叉树构成一个森林 F
  • 在森林 F 中选取两棵根结点的权值最小的数作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右树上根结点的权值之和
  • 在森林 F 中删除这两棵树,同时将新得到的二叉树加入 F 中
  • 重复第二、三步骤,直到 F 只含一棵树为止

哈夫曼树的构造过程

5.2.2 哈夫曼算法的实现

  • 哈夫曼树中没有度为 1 的结点,则一棵有 n 个叶子结点的哈夫曼树共有 2n-1 个结点,可以存储在一个大小为 2n-1 的一维数组中

    • 数组的 0 号单元不使用,从 1 号单元开始使用,所以数组大小为 2n
      • 将叶子结点集中存储在前面部分 1~n 个位置,而后面的 n-1 个位置存储其余非叶子结点
  • 构成

    • 权值
    • 双亲
    • 左孩子
    • 右孩子
typedef struct {
    int weight;										// 结点的权值
    int parent;										// 结点的双亲的下标
    int lchild;										// 结点的左孩子的下标
    int rchild;										// 结点的右孩子的下标
} HTNode, *HuffmanTree;
  • 构造哈夫曼树
void CreateHuffanTree(HuffmanTree* tree, int n) {
    if (n <= 1) {
        return 0;
    }
    
    int m = 2 * n - 1;
    
    *tree = (HTNode*)malloc(sizeof(HTNode));
    // 0 号单元未用,所以需要动态分配 m+1 单元,tree[m] 表示根结点
    
    for (int i = 1; i <= m; i++) {					// 将 1~m 号单元中的双亲、左孩子,右孩子的下标都初始化为 0
        tree[i].parent = 0;
        tree[i].lchild = 0;
        tree[i].rchild = 0;
    }
    
    for (int i = 1; i <= m; i++) {					// 输入前 n 个单元中叶子结点的权值
        scanf("%d", &tree[i].weight);
    }
    
    for (int i = n + 1; i <= m; i++) {
        // 通过 n-1 次的选择、删除、合并来创建哈夫曼树
        
        Select(tree, i-1, s1, s2);
        // 在 tree[k] (1 ≤ k ≤ i-1) 中选择两个其双亲域为 0 且权值最小的结点,并返回它们在 tree 中的序号 s1 和 s2
        
        tree[s1].parent = i;
        tree[s2].parent = i;
        // 得到新结点 i ,从森林中删除 s1 , s2 ,将 s1 和 s2 的双亲域由 0 改为 1
        
        tree[i].lchild = s1;
        tree[i].rchild = s2;
        // s1 , s2 分别作为 i 的左右孩子
        
        tree[i].weight = tree[s1].weight + tree[s2].weight;
        // i 的权值为左右孩子权值之和
    }
}

5.3 哈夫曼编码

  • 编码

    • 前缀编码
    • 哈夫曼编码
  • 哈夫曼编码性质

    • 哈夫曼编码是前缀编码
    • 哈夫曼编码是最优前缀编码
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.两个串相等的充要条件是( )。A.串长度相等B.串长度任意 C.串中各位置字符任意 D.串中各位置字符均对应相等 2.对称矩阵的压缩存储:以行序为主序存储下三角中的元素,包括对角线上的元素。二维下标为( i, j ),存储空间的一维下标为k,给出k与 i, j (i<j)的关系k=( ) (1<= i, j <= n , 0<= k < n*(n+1)/2)。 A.i*(i-1)/2+j-1 B.i*(i+1)/2+j C.j*(j-1)/2+i-1 D.j*(j+1)/2+i 3.二维数组A[7][8]以列序为主序的存储,计算数组元素A[5][3] 的一维存储空间下标 k=( )。 A.38 B.43 C.26 D.29 4.已知一维数组A采用顺序存储结构,每个元素占用4个存储单元,第9个元素的地址为144,则第一个元素的地址是( )。A.108 B.180 C.176 D.112 5. 下面( )不属于特殊矩阵。 A.对角矩阵 B. 三角矩阵C. 稀疏矩阵 D. 对称矩阵 6. 假设二维数组M[1..3, 1..3]无论采用行优先还是列优先存储,其基地址相同,那么在两种存储方式下有相同地址的元素有( )个。 A. 3 B. 2 C. 1 D. 0 7. 若Tail(L)非空,Tail(Tail(L))为空,则非空广义表L的长度是( )。(其中Tail表示取非空广义表的表尾) A. 3 B. 2 C. 1 D. 0 8.串的长度是( )。 A.串中不同字母的个数 B.串中不同字符的个数C.串中所含字符的个数,且大于0 D.串中所含字符的个数 9.已知广义表(( ),(a), (b, c, (d), ((d, f)))),则以下说法正确的是( )。A.表长为3,表头为空表,表尾为((a), (b, c, (d), ((d, f))))B.表长为3,表头为空表,表尾为(b, c, (d), ((d, f)))C.表长为4,表头为空表,表尾为((d, f)) D.表长为3,表头为(()),表尾为((a), (b, c, (d), ((d, f))))10.广义表A=(a,b,c,(d,(e,f))),则Head(Tail(Tail(Tail(A))))的值为( )。(Head与Tail分别是取表头和表尾的函数) A.(d,(e,f)) B.d C.f D.(e,f)二、填空题(每空 2 分,共 8 分)。 1.一个广义表为 F = (a, (a, b), d, e, (i, j), k),则该广义表的长度为________________。GetHead(GetTail(F))= _______________。 2.一个n*n的对称矩阵,如果以行或列为主序压缩存放入内存,则需要 个存储单元。 3.有稀疏矩阵如下: 0 0 5 7 0 0 -3 0 0 0 4 0 0 2 0 它的三元组存储形式为: 。 三、综合题(共 22 分)。 1.(共8分)稀疏矩阵如下图所示,描述其三元组的存储表示,以及转置后的三元组表示。 0 -3 0 0 0 4 0 6 0 0 0 0 0 0 7 0 15 0 8 0 转置前(4分): 转置后(4分): 2. (共14分)稀疏矩阵M的三元组表如下,请填写M的转置矩阵T的三元组表,并按要求完成算法。 (1)写出M矩阵转置后的三元组存储(6分): M的三元组表: T的三元组表: i j e 2 1 3 3 2 4 4 2 5 4 3 5 5 1 6 5 3 6 i j e (2)如下提供了矩阵采用三元组存储时查找指定行号(m)和列号(n)元素值的算法框架,将代码补充完整(每空2分,共8分)。 typedefstruct{ inti,j; ElemType e; }Triple; typedefstruct{ Triple data[MAXSIZE+1]; //data[0]未用 intmu,nu,tu; //矩阵的行数,列数和非零元的个数 }TSMatrix; voidFind_TSMatrix(TSMatrix M, int m, int n, ElemType&e) //M为要查找的稀疏矩阵三元组存储,m为要查找的元素的行号,n为列号,e为查找后得到的值。 { for ( i=1 ; i<=M.tu ;i++) if( && ) { e=M.data[i].e; ; } if( ) e=0; }
1. 什么是二叉树二叉树是一种数据结构,其中每个节点最多有两个子节点,分别称为左子节点和右子节点。 2. 什么是二叉搜索? 二叉搜索是一种特殊的二叉树,其中左子节点的值小于等于父节点的值,右子节点的值大于等于父节点的值。 3. 什么是完全二叉树? 完全二叉树是一种特殊的二叉树,除了最后一层外,每一层都必须填满,且最后一层从左到右依次填入节点。 4. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉搜索,其左右子高度差不超过1。 5. 什么是二叉树的遍历? 二叉树的遍历是指按照某种顺序依次访问二叉树中的每个节点,常见的遍历方式包括前序遍历、中序遍历和后序遍历。 6. 什么是二叉树的前序遍历? 二叉树的前序遍历是指按照根节点、左子、右子的顺序依次访问二叉树中的每个节点。 7. 什么是二叉树的中序遍历? 二叉树的中序遍历是指按照左子、根节点、右子的顺序依次访问二叉树中的每个节点。 8. 什么是二叉树的后序遍历? 二叉树的后序遍历是指按照左子、右子、根节点的顺序依次访问二叉树中的每个节点。 9. 什么是二叉树的层序遍历? 二叉树的层序遍历是指按照从上到下、从左到右的顺序依次访问二叉树中的每个节点。 10. 如何判断一个二叉树是否为二叉搜索? 可以通过中序遍历得到二叉树中所有节点的值,然后判断这些值是否按照升序排列即可判断是否为二叉搜索

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值