数据结构与算法-树

树是一种非线性结构,有一个前驱,可能有多个后继(1:n)的数据结构

树的基本知识

树的定义

树的定义
由一个或多个(n≥0)结点组成的有限集合T,有且仅有一个结点称为根(root),当n>1时,其余的结点分为m(m≥0)个互不相交的有限集合T1,T2,···,Tm。每个集合本身又是棵树,被称作这个根的子树

若干术语

  • 根:即根结点(没有前驱)
  • 叶子:即终端结点(没有后继)
  • 森林:指m棵不相交的树的集合(例如删除A后的子树个数)
  • 有序树:结点各子树从左至右有序,不能互换(左为第一)
  • 无序树:结点各子树可互换位置
  • 双亲:即上层的那个结点(直接前驱) parent
  • 孩子:即下层结点的子树 (直接后继) child
  • 兄弟:同一双亲下的同层结点(孩子之间互称兄弟)sibling
  • 堂兄弟:即双亲位于同一层的结点(但并非同一双亲)cousin
  • 祖先:即从根到该结点所经分支的所有结点
  • 子孙:即该结点下层子树中的任一结点
  • 结点:即树的数据元素
  • 结点的度:结点挂接的子树数(有几个直接后继就是几度,亦称“次数”)
  • 结点的层次:从根到该结点的层数(根结点算第一层)
  • 终端结点:即度为0的结点,即叶子
  • 分支结点:除树根以外的结点(也称为内部结点)
  • 树的度:所有结点度中的最大值(Max{各结点的度})
  • 树的深度(或高度):指所有结点中最大的层数(Max{各结点的层次})

树的表示法有几种

  • 图形表示法
  • 嵌套集合表示法
  • 广义表表示法
    ( A ( B ( E ( K, L ), F ), C ( G ), D ( H ( M ), I, J ) ) )
    根作为由子树森林组成的表的名字写在表的左边
  • 目录表示法
  • 左孩子-右兄弟表示法

逻辑结构

(特点):一对多(1:n),有多个直接后继(如家谱树、目录树等等),但只有一个根结点,且子树之间互不相交

存储结构

  • 树是非线性结构,该怎样存储?
    仍然有顺序存储、链式存储等方式

  • 树的顺序存储方案应该怎样制定?
    可规定为:从上至下、从左至右将树的结点依次存入内存
    重大缺陷:复原困难(不能唯一复原就没有实用价值)

  • 树的链式存储方案应该怎样制定?
    可用多重链表:一个前趋指针,n个后继指针。
    细节问题:树中结点的结构类型样式该如何设计?即应该设计成“等长”还是“不等长”?
    缺点:等长结构太浪费(每个结点的度不一定相同);不等长结构太复杂(要定义好多种结构类型)

  • 计算机如何实现各种不同进制的运算?
    实现思路:先研究最简单、最有规律的二进制运算规律,然后设法把各种不同进制的运算转化二进制运算

  • 树的存储可否借鉴这种思路呢?
    解决思路:先研究最简单、最有规律的树,然后设法把一般的树转化为这种简单的树

树的运算

  1. 普通树(即多叉树)若不转化为二叉树,则运算很难实现
  2. 二叉树的运算仍然是插入、删除、修改、查找、排序等,但这些操作必须建立在对树结点能够“遍历”的基础上

二叉树

二叉树的结构最简单,规律性最强
可以证明,所有树都能转为唯一对应的二叉树,不失一般性(利用左孩子-右兄弟表示法)

二叉树的定义

定义:是n(n≥0)个结点的有限集合,由一个根结点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成

逻辑结构: 一对二(1:2)

基本特征:

  • 每个结点最多只有两棵子树(不存在度大于2的结点)
  • 左子树和右子树次序不能颠倒(有序树)

二叉树的性质

  • 性质1: 在二叉树的第i层上至多有2i-1个结点(i>0)
  • 性质2:深度为k的二叉树至多有2k-1个结点(k>0)
  • 性质3:对于任何一棵二叉树,若2度的结点数有n2个,则叶子数(n0)必定为n2+1 (即n0=n2+1)
  • 满二叉树:一棵深度为k 且有2k-1个结点的二叉树。(特点:每层都“充满”了结点)
  • 完全二叉树:深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应(完全二叉树的特点就是,只有最后一层叶子不满,且全部集中在左边。这其实是顺序二叉树的含义)
  • 满二叉树和完全二叉树在顺序存储方式下可以复原
  • 性质4:具有n个结点的完全二叉树的深度必为log2n+1
  • 对完全二叉树,若从上至下、从左至右编号,则编号为i的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)

二叉树的存储结构

顺序存储结构:
按二叉树的结点“自上而下、从左至右”编号,用一组连续的存储单元存储

链式存储结构:
一般从根结点开始存储。(相应地,访问树中结点时也只能从根开始)
二叉树结点数据类型定义:

typedef struct node *tree_pointer;
typedef struct node {
    int data;
    tree_pointer left_child, right_child;
} node;

二叉链表

typedef struct BiNode
{
	int data;
	struct BiNode  *lchild, *rchild;
}BiNode, *BiTree;

三叉链表

typedef struct TriTNode 
{
	int data;
	struct TriTNode *lchild, *rchild;
	struct TriTNode *parent;
}TriTNode, *TriTree;

双亲链表

typedef struct BPTNode
{
	int data;
	int parentPosition; //指向双亲的指针 //数组下标
	char LRTag; //左右孩子标志域
}BPTNode;

typedef struct BPTree
{
	BPTNode nodes[100]; //因为节点之间是分散的,需要把节点存储到数组中
	int num_node;  //节点数目
	int root; //根结点的位置 //注意此域存储的是父亲节点在数组的下标
}BPTree;

定义一颗简单的树

void main()
{
	BPTree tree;

	BiNode t1, t2, t3, t4, t5;

	memset(&t1, 0, sizeof(BiNode));
	memset(&t2, 0, sizeof(BiNode));
	memset(&t3, 0, sizeof(BiNode));
	memset(&t4, 0, sizeof(BiNode));
	memset(&t5, 0, sizeof(BiNode));

	t1.data = 1;
	t2.data = 2;
	t3.data = 3;
	t4.data = 4;
	t5.data = 5;

	//建立树关系
	//t1的左孩子为t2
	t1.lchild = &t2;
	//t1的右孩子为t3
	t1.rchild = &t3;
	//t2的左孩子为t4
	t2.lchild = &t4;
	//t3的左孩子为t5
	t3.lchild = &t5;

	system("pause");
}

使用指针去定义树

void main()
{
	BiTree p1, p2, p3, p4, p5;

	p1 = (BiTree)malloc(sizeof(BiNode));
	p2 = (BiTree)malloc(sizeof(BiNode));
	p3 = (BiTree)malloc(sizeof(BiNode));
	p4 = (BiTree)malloc(sizeof(BiNode));
	p5 = (BiTree)malloc(sizeof(BiNode));

	memset(p1, 0, sizeof(BiNode));
	memset(p2, 0, sizeof(BiNode));
	memset(p3, 0, sizeof(BiNode));
	memset(p4, 0, sizeof(BiNode));
	memset(p5, 0, sizeof(BiNode));

	p1->data = 1;
	p2->data = 2;
	p3->data = 3;
	p4->data = 4;
	p5->data = 5;

	//建立树关系
	//t1的左孩子为t2
	p1->lchild = p2;
	//t1的右孩子为t3
	p1->rchild = p3;
	//t2的左孩子为t4
	p2->lchild = p4;
	//t3的左孩子为t5
	p3->lchild = p5;

	system("pause");
}

遍历二叉树和线索二叉树

遍历二叉树

遍历定义:指按某条搜索路线遍访每个结点且不重复(又称周游)
遍历用途:它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心
遍历方法:牢记一种约定,对每个结点的查看都是“先左后右”

遍历规则:
二叉树由根、左子树、右子树构成,定义为D、L、R
D、L、R的组合定义了六种可能的遍历方案:LDR, LRD, DLR, DRL, RDL, RLD

若限定先左后右,则有三种实现方案:

  • DLR 先(根)序遍历
  • LDR 中(根)序遍历
  • LRD 后(根)序遍历

DLR—先序遍历,即先根再左再右
LDR—中序遍历,即先左再根再右
LRD—后序遍历,即先左再右再根

e.g.
( A ( B ( D , E ), C ) )
先序遍历的结果是:A B D E C
中序遍历的结果是:D B E A C
后序遍历的结果是:D E B C A
( A ( B ( C ( D , E ) ) , F ( G ( H ) ) ) )
先序遍历:ABCDEFGH
中序遍历:BDCEAFHG
后序遍历:DECBHGFA

e.g.(用二叉树表示算术表达式)
二叉树与算术表达式
先序遍历 -> + * * / A B C D E -> 前缀表示法
中序遍历 -> A / B * C * D + E -> 中缀表示法
后序遍历 -> A B / C * D * E + -> 后缀表示法
层序遍历 -> + * E * D / C A B

遍历的算法实现:

//结点数据类型自定义
typedef struct node{
   int  data; 
   struct node *lchild,*rchild;
} NODE;
NODE *root;

先序遍历算法

DLR(NODE *root )
{ 
    if (root) //非空二叉树
    {
        printf(%d”,root->data); //访问D
        DLR(root->lchild); //递归遍历左子树
        DLR(root->rchild); //递归遍历右子树
    }
}

中序遍历算法

LDR(NODE *root)
{
    if(root !=NULL)
    {
        LDR(root->lchild);
        printf(%d”,root->data);
        LDR(root->rchild);
    }
}

后序遍历算法

LRD (NODE *root)
{
    if(root !=NULL)
    {
        LRD(root->lchild);
        LRD(root->rchild);
        printf(%d”,root->data);
    }
}

除去打印条件,其结构是一样的
这里的差别在于:
先序遍历在第一次访问时候输出
中序遍历在第二次访问时候输出
后序遍历在第三次访问时候输出

e.g.(二叉树中叶子结点的数目)

//使用全局变量sum
DLR_CountLeafNum(NODE *root)
{
    if (root)  //非空二叉树条件,还可写成if(root!=NULL)
    {
        if(!root->lchild && !root->rchild)//是叶子结点则统计并打印,在先中后打印并无区别
        {
            sum++;
            printf("%d
",root->data);
        }
        DLR_CountLeafNum(root->lchild); //递归遍历左子树,直到叶子处;
        DLR_CountLeafNum(root->rchild);//递归遍历右子树,直到叶子处;
    }
    return 0;
}
DLR_CountLeafNum(NODE *root, int *num)
{
    if (root != NULL)
    {
        if(!root->lchild && !root->rchild)
        {
            sum++;
            printf("%d
",root->data);
        }
        DLR_CountLeafNum(root->lchild, num); //递归遍历左子树,直到叶子处;
        DLR_CountLeafNum(root->rchild, num);//递归遍历右子树,直到叶子处;
    }
    return 0;
}

e.g.(二叉树的深度)

int GetDepth(BiNode *root)
{
	int depthleft = 0, depthright = 0; 
	int depthVal = 0;
	int tmp = 0;

	if (root == NULL)
	{
		depthVal = 0;
		return depthVal;
	}
	//求左子树高度
	depthleft = GetDepth(root->lchild);
	//求右子树高度
	depthright = GetDepth(root->rchild);

	tmp = ((depthleft>depthright)? depthleft : depthright);
	depthVal = 1 + tmp; 

	return depthVal;
}

e.g.(树的拷贝)

BiNode *copyTree(BiNode *T)
{
	BiNode *newLptr = NULL, *newRptr = NULL;
	BiNode *newNode = NULL;

	if (T->lchild != NULL)
	{
		newLptr =copyTree(T->lchild);
	}
	else
	{
		newLptr = NULL;
	}

	if (T->rchild != NULL)
	{
		newRptr = copyTree(T->rchild);
	}
	else
	{
		newRptr = NULL; 
	}

	newNode = (BiNode *)malloc(sizeof(BiNode));
	if (newNode == NULL)
	{
		return NULL;
	}

	newNode->lchild = newLptr;
	newNode->rchild = newRptr;
	newNode->data = T->data;

	return newNode;
}

中序遍历非递归算法:

步骤1:结点的所有路径情况
如果结点有左子树,该结点入栈;
如果结点没有左子树,访问该结点;

如果结点有右子树,重复步骤1;

如果结点没有右子树(结点访问完毕),回退,让栈顶元素出栈,访问栈顶元素,并访问右子树,重复步骤1

如果栈为空,表示遍历结束。

注意:入栈的结点表示,本身没有被访问过,同时右子树也没有被访问过。

#include <iostream>
#include <stack>
using namespace std;

//二叉链表
typedef struct BiNode
{
	int data;
	struct BiNode  *lchild, *rchild;
}BiNode, *BiTree;

BiNode *GoFarLeft(BiNode *T, stack<BiNode *> &s)
{
	if (T == NULL)
	{
		return NULL;
	}
	while (T->lchild)
	{
		s.push(T);
		T = T->lchild;
	}
	return T;
}
void InOrder(BiNode *T)
{
	 stack<BiNode *> s;
	 //步骤1:一直往左走,找到中序遍历的起点
	 BiTree t = GoFarLeft(T, s);
	 while (t)
	 {
		 printf("%d ", t->data); //中序遍历打印
		 //如果t节点有右子树,那么重复步骤1
		 if (t->rchild != NULL)
		 {
			t =  GoFarLeft(t->rchild, s);
		 }
		 //如果t没有右子树,根据栈顶指示,访问栈顶元素
		 else if (!s.empty())
		 {
			 t = s.top();
			 s.pop();
		 }
		 //如果t没有右子树,并且栈为空 
		 else
		 {
			 t = NULL;
		 }
	 }
}
void main()
{
	BiNode t1, t2, t3, t4, t5;
	memset(&t1, 0, sizeof(BiNode));
	memset(&t2, 0, sizeof(BiNode));
	memset(&t3, 0, sizeof(BiNode));
	memset(&t4, 0, sizeof(BiNode));
	memset(&t5, 0, sizeof(BiNode));
	t1.data = 1;
	t2.data = 2;
	t3.data = 3;
	t4.data = 4;
	t5.data = 5;
	t1.lchild = &t2;
	t1.rchild = &t3;
	t2.lchild = &t4;
	t3.lchild = &t5;
	InOrder(&t1);
	system("pause");
}

二叉树内存释放

void BiTree_Free(BiTNode* T)
{
	BiTNode *tmp = NULL;
	if (T != NULL)
	{
		if (T->rchild != NULL)
        {
            BiTree_Free(T->rchild);
        }
		if (T->lchild != NULL)
        {
            BiTree_Free(T->lchild);
        }
		if (T != NULL)
		{
			free(T); 
			T = NULL;
		}
	}
}

先序遍历创建树,需要有占位符:124#35#

//按给定的先序序列建立二叉链表
BiTNode* BiTree_Creat()
{
	BiTNode *tmp = NULL;
	char ch;
	printf("
请输入字符: ");
	scanf("%c", &ch);
	if (ch == '#')
	{
		return NULL;
	}
	else
	{
		tmp = (BiTNode *)malloc(sizeof(BiTNode));
		if (tmp == NULL)
		{
			return NULL;
		}
		tmp->data = ch; //生成根结点
		tmp->lchild = BiTree_Creat();//构建左子树
		tmp->rchild = BiTree_Creat();//构建右子数
		return tmp;
	}
}

根据先序和中序遍历结果可以得到原树
e.g.
先序结果:ADEBCF
中序结果:DEACFB
分析方法:

  1. 先从先序遍历中找到根节点A,然后根据A在中序遍历中的位置,区分出A的左子树和A的右子树
  2. 在A的左(右)子树中,找到左(右)子树的根节点(在先序中),重复上述步骤

分析:

  1. 由于先序从根节点开始,故A为根节点,再根据中序结果,DE为左节点,CFB为右节点。
  2. 由于在先序和中序中,DE顺序一样,故D为E的父节点,E为D的右节点
  3. 由先序结果,右节点为B,而中序结果中B位于最后,故得到B没有右节点
  4. 由于在先序和中序中,DE顺序一样,则可得出C为B的左节点,F为C的右节点

线索二叉树

普通二叉树只能找到结点的左右孩子信息,而该结点的直接前驱和直接后继只能在遍历过程中获得
若可将遍历后对应的有关前驱和后继预存起来,则从第一个结点开始就能很快“顺藤摸瓜”而遍历整个树了

规定:

  • 若结点有左子树,则lchild指向其左孩子;否则, lchild指向其直接前驱(即线索)
  • 若结点有右子树,则rchild指向其右孩子;否则, rchild指向其直接后继(即线索)
lchildLTagdataRTagrchild

约定:

  • 当Tag域为0时,表示正常情况
  • 当Tag域为1时,表示线索情况

在线索化二叉树中,并不是每个结点都能直接找到其后继的,当标志为0时,则需要通过一定运算才能找到它的后继

线索链表:用上面结点结构所构成的二叉链表
线索:指向结点前驱和后继的指针
线索二叉树:加上线索的二叉树(图形式样)
线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程

二叉树线索化算法

void InTreading(BiThrTree p) 
//中序遍历进行中序线索化
{
    if (p)
    {
        InThreading(p->lchild); /*左子树线索化*/
        if (!p->lchild) /*前驱线索*/
        {
            p->ltag=1;
            p->lchild=pre;
        }
    	if (!pre->rchild) /*后继线索*/
        {
            pre->rtag=1;
            pre->rchild=p;
        }
        pre=p;
        InThreading(p->rchild); /*右子树线索化*/
    }
}

二叉树线索化遍历算法:(非递归,且不用栈)

p = T->lchild; //从头结点进入到根结点;
while(p != T)
{
    while(p->LTag == link)
        p = p->lchild; //先找到中序遍历起点
    if(!visit(p->data))
        return ERROR; //若起点值为空则出错告警
    while(p->RTag == Thread ……)
    {
        p = p->rchild;
        Visit(p->data);
    } //若有后继标志,则直接提取p->rchild中线索并访问后继结点;
    p = p->rchild; //当前结点右域不空或已经找好了后继,则一律从结点的右子树开始重复{  }的全部过程。
}
return OK;

线索化过程就是在遍历过程中修改空指针的过程:
将空的lchild改为结点的直接前驱;
将空的rchild改为结点的直接后继。
非空指针仍然指向孩子结点
e.g.

#include <string.h>
#include <stdio.h>  
#include <stdlib.h>   
#include <io.h>  
#include <math.h>  
#include <time.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXSIZE 100 /* 存储空间初始分配量 */

typedef int Status;	/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef char TElemType;
typedef enum {Link,Thread} PointerTag;	/* Link==0表示指向左右孩子指针, */
										/* Thread==1表示指向前驱或后继的线索 */
typedef  struct BiThrNode	/* 二叉线索存储结点结构 */
{
	TElemType data;	/* 结点数据 */
	struct BiThrNode *lchild, *rchild;	/* 左右孩子指针 */
	PointerTag LTag;
	PointerTag RTag;		/* 左右标志 */
} BiThrNode, *BiThrTree;

TElemType Nil='#'; /* 字符型以空格符为空 */

Status visit(TElemType e)
{
	printf("%c ",e);
	return OK;
}

/* 按前序输入二叉线索树中结点的值,构造二叉线索树T */
/* 0(整型)/空格(字符型)表示空结点 */
Status CreateBiThrTree(BiThrTree *T)
{ 
	TElemType h;
	scanf("%c",&h);

	if(h==Nil)
		*T=NULL;
	else
	{
		*T=(BiThrTree)malloc(sizeof(BiThrNode));
		if(!*T)
			exit(OVERFLOW);
		(*T)->data=h; /* 生成根结点(前序) */
		CreateBiThrTree(&(*T)->lchild); /* 递归构造左子树 */
		if((*T)->lchild) /* 有左孩子 */
			(*T)->LTag=Link;
		CreateBiThrTree(&(*T)->rchild); /* 递归构造右子树 */
		if((*T)->rchild) /* 有右孩子 */
			(*T)->RTag=Link;
	}
	return OK;
}

BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */
/* 中序遍历进行中序线索化 */
void InThreading(BiThrTree p)
{ 
	if(p)
	{
		InThreading(p->lchild); /* 递归左子树线索化 */
		if(!p->lchild) /* 没有左孩子 */
		{
			p->LTag=Thread; /* 前驱线索 */			p->lchild=pre; /* 左孩子指针指向前驱 */
		}
		if(!pre->rchild) /* 前驱没有右孩子 */
		{
			pre->RTag=Thread; /* 后继线索 */
			pre->rchild=p; /* 前驱右孩子指针指向后继(当前结点p) */
		}
		pre=p; /* 保持pre指向p的前驱 */
		InThreading(p->rchild); /* 递归右子树线索化 */
	}
}

/* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点 */
Status InOrderThreading(BiThrTree *Thrt,BiThrTree T)
{ 
	*Thrt=(BiThrTree)malloc(sizeof(BiThrNode));
	if(!*Thrt)
		exit(OVERFLOW);
	(*Thrt)->LTag=Link; /* 建头结点 */
	(*Thrt)->RTag=Thread;
	(*Thrt)->rchild=(*Thrt); /* 右指针回指 */
	if(!T) /* 若二叉树空,则左指针回指 */
		(*Thrt)->lchild=*Thrt;
	else
	{
		(*Thrt)->lchild=T;
		pre=(*Thrt);
		InThreading(T); /* 中序遍历进行中序线索化 */
		pre->rchild=*Thrt;
		pre->RTag=Thread; /* 最后一个结点线索化 */
		(*Thrt)->rchild=pre;
	}
	return OK;
}

/* 中序遍历二叉线索树T(头结点)的非递归算法 */
Status InOrderTraverse_Thr(BiThrTree T)
{ 
	BiThrTree p;
	p=T->lchild; /* p指向根结点 */
	while(p!=T)
	{ /* 空树或遍历结束时,p==T */
		while(p->LTag==Link)
			p=p->lchild;
		if(!visit(p->data)) /* 访问其左子树为空的结点 */
			return ERROR;
		while(p->RTag==Thread&&p->rchild!=T)
		{
			p=p->rchild;
			visit(p->data); /* 访问后继结点 */
		}
		p=p->rchild;
	}
	return OK;
}

int main()
{
	BiThrTree H,T;
	printf("请按前序输入二叉树(如:'ABDH##I##EJ###CF##G##')
");
 	CreateBiThrTree(&T); /* 按前序产生二叉树 */
	InOrderThreading(&H,T); /* 中序遍历,并中序线索化二叉树 */
	printf("中序遍历(输出)二叉线索树:
");
	InOrderTraverse_Thr(H); /* 中序遍历(输出)二叉线索树 */
	printf("
");
	system("pause");
	return 0;
}

树和森林

树和森林的存储方式

  • 双亲表示法
  • 孩子表示法
  • 孩子兄弟表示法

树和森林与二叉树的转换

  • 树如何转为二叉树
    方法:加线—擦线—旋转
    step1: 将树中同一结点的兄弟相连;
    step2: 保留结点的最左孩子连线,删除其它孩子连线;
    step3: 将同一孩子的连线绕左孩子旋转45度角

  • 二叉树怎样还原为树:
    把所有右孩子变为兄弟

  • 森林如何转为二叉树:

    • ①各森林先各自转为二叉树;
      ②依次连到前一个二叉树的右子树上
    • 森林直接变兄弟,再转为二叉树
  • 二叉树如何还原为森林
    即B={root, LB, RB} ==> F={T1, T2, …,Tm}
    把最右边的子树变为森林,其余右子树变为兄弟

一般树的遍历

  • 深度遍历(先序、中序、后序)
  • 广度遍历(层次)

先序遍历

  • 访问根结点;
  • 依次先序遍历根结点的每棵子树

后序遍历

  • 依次后序遍历根结点的每棵子树;
  • 访问根结点

树没有中序遍历(因子树不分左右)

霍夫曼树

对于文本”BADCADFEED”的传输而言,因为重复出现的只有
”ABCDEF”这6个字符,因此可以用下面的方式编码:
霍夫曼树起源
接收方可以根据每3个bit进行一次字符解码的方式还原文本信息。
这样的编码方式需要30个bit位才能表示10个字符
当需要传送的数据量很大时,采用新的编码方式
霍夫曼树形成
准则:任一字符的编码都不是另一个字符编码的前缀!

霍夫曼树

  1. 给定n个数值{ v1, v2, …, vn}
  2. 根据这n个数值构造二叉树集合F
    F = { T1, T2, …, Tn}
    Ti的数据域为vi,左右子树为空
  3. 在F中选取两棵根结点的值最小的树作为左右子树构造一棵新的二叉树,这棵二叉树的根结点中的值为左右子树根结点中的值之和
  4. 在F中删除这两棵子树,并将构造的新二叉树加入F中
  5. 重复3和4,直到F中只剩下一个树为止。这棵树即霍夫曼树

假设经过统计ABCDEF在需要传输的报文中出现的概率如下
霍夫曼树字母概率
霍夫曼树
霍夫曼树是一种特殊的二叉树
霍夫曼树应用于信息编码和数据压缩领域
霍夫曼树是现代压缩算法的基础

原文地址:https://www.cnblogs.com/cj5785/p/10664704.html