?
树
树是一种非常重要的非线性数据结构。
树的树形图表示法规定在用直线连接起来的两端结点中,处在上端的结点是前驱,处在下端的结点是后继。
树的逻辑结构可表示为T=(D,R);
数据元素集合:D={A,B,C,D,E,F,G,H,I,J,K,L}
各数据元素之间的前后关系:R = {,,,,,,,,,,}
树的定义
树是由n(n≥0)个结点组成的有限集T。当n=0时,称为空树;当n>0时,集合T需满足如下条件:
1、有且仅有一个没有前驱的结点,该结点称为树的根节点。
2、将根节点去除后,其余结点可分为m(m≥0)个互不相交的子集T1、T2、...、Tm,其中每个子集Ti(i=1,2,3...m)又是一棵树,并称其为根的子树。
树的定义采用的是递归定义方式。
树的表示方法
1、树形图
文章图片
文章图片
?
2、嵌套集合表示法
嵌套集合表示法是通过包含的形式体现结点之间的关系,后继结点集合包含在前驱结点集合中。
文章图片
文章图片
?
3、凹入表表示法
凹入表表示法是利用树的目录形式表示结点之间的关系,后继结点位于前驱结点的下一层目录中。
文章图片
文章图片
?
4、广义表表示法
广义表表示法是利用广义表的多层次结构来表示树,后继结点位于前驱结点的下一层次。
文章图片
文章图片
?
树的基本术语
度:一个结点的后继的数目称为该结点的度。
树的度:树中各结点度的最大值称为树的度。
结点的层:树的根结点所在的层为第一层,其余结点的层等于其前驱结点的层加1。
树的深度:树中各结点的层的最大值称为树的深度。
分支:从一个结点到其后继结点之间的连线称为一个分支。
路径:从一个结点x到另一个结点Y所经历的所有分支构成结点x到结点Y的路径。
路径长度:一条路径上的分支数目称为路径长度。
树的路径长度:从树的根结点到其他各个结点的路径长度之和称为树的路径长度。
叶子结点(终端结点):树中度为0的结点称为叶子结点。
分支结点(非终端结点):度不为0的结点称为分支结点。
内部结点:除根结点以外的分支结点也称为内部结点。
结点间的关系:在树中,一个结点的后继结点称为该结点的孩子,一个结点的前驱结点称为该结点的双亲;同一个双亲结点的孩子结点称为兄弟,不同双亲但在同一层的结点之间胡称为堂兄弟;从树的根结点到某一个结点X的路径上经过的所有结点(包括根结点但不包括x结点)称为x结点的祖先。以某一结点x为根的子树上的所有非根结点(除结点x外)称为结点x的子孙。
有序树和无序树:对于树中任一结点,如果其各子树的相对次序被用来表示数据之间的关系,即交换位置会改变树表示的内容,称为有序树,否则称为无序树。
森林:m(m≥0)棵互不相交的树的集合构成森林。
二叉树
二叉树是一种特殊的树结构。
二叉树的定义也采用递归定义方式。
二叉树是有n(n≥0)个结点组成的有限集T。当n=0时,称为空二叉树;当n>0时,集合T需要满足如下条件:
- 有且仅有一个没有前驱的结点,该结点称为二叉树的根结点;
- 将根结点去除后,其余结点可分为两个互不相交的子集T1、T2,其中每个子集Ti(i=1,2)又是一棵二叉树,并分别称为根结点的左子树和右子树。
二叉树定义:二叉树是每个结点的度都小于等于2的有序树。
二叉树的5种基本形态:
文章图片
文章图片
?
二叉树的顺序编号法:对二叉树中的结点可以按照“自上而下,自左至右”的顺序进行连续编号,称为顺序编号法。
文章图片
文章图片
?
满二叉树和完全二叉树是两种特殊形态的二叉树。
满二叉树:是指除了最后一层的结点为叶子结点外其它结点都有左、右两棵子树的二叉树。
完全二叉树:是指其结点与相同深度的满二叉树中的结点编号完全一致的二叉树。对于深度为k的完全二叉树,其前k-1层与深度为k的满二叉树的前k-1层完全一样,只是在第k层有可能缺少右边若干个结点。
满二叉树必然是完全二叉树,而完全二叉树不一定是满二叉树。
二叉树的性质:
- 在二叉树的第i层上至多有2^(i-1)个结点。
-
文章图片
文章图片
?
- 深度为k的二叉树至多有(2^k)-1个结点。
-
文章图片
文章图片
?
- 在二叉树中,若度为0的结点(即叶子结点)数为n0,度为2的结点数为n2,则 n0=n2+1;
-
文章图片
文章图片
?
- 具有n个结点的完全二叉树其深度为?㏒2??+1 (其中?㏒2??表示不大于㏒2?的最大整数)。
-
文章图片
文章图片
?
- 采用顺序编号的完全二叉树具有如下性质
- 若一个分支结点的编号为i,则其左子树(即左孩子结点)的根节点编号为2xi,右子树(即右孩子结点)的根节点编号为2xi+1;
- 若一个非根结点的编号为i,则其双亲结点的编号为?i/2? (其中?i/2?表示不大于i/2的最大整数)。
二叉树的基本操作
- 创建一棵空二叉树;
- 删除一棵二叉树;
- 先序遍历二叉树;
- 中序遍历二叉树;
- 后序遍历二叉树
- 逐层遍历二叉树;
- 判断二叉树是否为空;
- 清空二叉树;
- 以指定元素值创建根节点;
- 将一个结点作为指定结点的左孩子插入;
- 将一个结点作为指定结点的右孩子插入;
- 删除以指定结点为根的子树;
- 按关键字查找结点;
- 修改指定结点的元素值;
- 获取指定结点的双亲结点;
- 计算二叉树的深度;
- 计算二叉树的叶子结点数;
二叉树的抽象数据类型
ADT BinTree
{
Data:
具有二叉树型结构的0或多个相同类型数据元素的集合
Operations:
BinTree();
//创建空二叉树
~BinTree();
//删除二叉树
PreOrderTraverse();
//先序遍历
InOrderTraverse();
//中序遍历
PostOrderTraverse();
//后序遍历
LevelOrderTraverse();
//逐层遍历
IsEmpty();
//判断二叉树是否为空
CreateRoot();
//以指定元素值创建根结点
Clear();
//清空二叉树
InsertLeftChild();
//将一个结点作为指定结点的左孩子插入
InsertRightChild();
//将一个结点作为指定结点的右孩子插入
DeleteSubTree();
//删除以指定结点为根的子树
SearchByKey();
//按关键字查找结点
ModifyNodeValue();
//修改指定结点的元素值
GetParent();
//获取指定结点的双亲结点
}ADT BinTree;
二叉树的顺序表示
把二叉树的结点按照完全二叉树的顺序编号规则自上而下、自左至右依次存放在一组地址连续的存储单元里构成了二叉树的顺序存储,树中结点的编号就可以唯一地反映出结点之间的逻辑关系。
通常通过定义一个一维数组来表示二叉树的顺序存储空间。
为了使数组元素的下标值与其对应的结点编号一致,将下标为0的空间空闲不用或用作其他用途。
二叉树的顺序表示法操作方便,但缺点是容易造成存储空间浪费。
文章图片
文章图片
?
文章图片
文章图片
?
二叉树顺序表示适用于完全二叉树而不适用于非完全二叉树。
二叉树的链式表示
链式表示通常具有更高的空间利用率,因此在实际应用中一般会使用链式表示来存储二叉树。
根据一个结点中指针域数量的不同,二叉树的链式表示又可以分为二叉链表表示和三叉链表表示。
二叉链表表示
在二叉链表表示中,双亲结点有指向其孩子结点的指针,而孩子结点不包括指向其双亲结点的指针。
二叉树中每个结点最多有两个孩子,因此在一个结点中设置两个指针域leftchild和rightchild分别指向其左孩子和右孩子,数据域data用于存放每个结点中数据元素的值。
如果一个结点没有左孩子,leftchild指针为空(用NULL或0表示),如果一个结点没有右孩子,rightchild指针为空(用NULL或0表示)。
文章图片
文章图片
?
三叉链表表示
【数据结构-二叉树】在三叉链表表示中,双亲结点有指向其孩子结点的指针,而孩子结点也包含指向其双亲结点的指针。
在用三叉链表表示的二叉树的每个结点中,除了具有二叉链表中的两个指向孩子结点的指针域leftchild和rightchild外,还有一个指向双亲结点的指针域parent。
根结点没有双亲,所以它的parent指针为空(用0或NULL表示)。
文章图片
文章图片
?
二叉链表结点类模板
template
class LinkedNode//结点类 {templatefriend class LinkedBinTree;
public:LinkedNode()//无参构造函数{m_pLeftChild=m_pRightChild=NULL;
}LinkedNode(const T &x)//有参构造函数{m_pLeftChild=m_pRightChild=NULL;
m_data=https://www.it610.com/article/x;
} private:T m_data;
//结点数据域LinkedNode *m_pLeftChild, *m_pRightChild;
//左右孩子指针 }
文章图片
二叉树二叉链表类模板
template
class LinkedBinTree {public:LinkedBinTree();
//构造函数,创建空二叉树~LinkedBinTree();
//析构函数,删除二叉树bool IsEmpty() const;
//判断二叉树是否为空LinkNode* CreateRoot(const T& x);
//以指定元素值创建根节点void clear();
//清空二叉树LinkedNode* GetRoot();
//获取根结点LinkedNode* InsertLeftChild(LinkedNode *pNode,const T &x);
//将一个结点作为指定结点的左孩子插入LinkedNode* InsertRightChild(LinkedNode *pNode,const T &x);
//将一个结点作为指定结点的右孩子插入bool ModifyNodeValue(LinkedNode *pNode,const T &x);
//修改指定结点的元素值bool GetNodeValue(LinkedNode *pNode,T &x);
//获取指定结点的元素值LinkNode* GetLeftChild(LinkedNode *pNode);
//获取指定结点的左孩子结点LinkNode* GetRightChild(LinkedNode *pNode);
//获取指定结点的右孩子结点void PreOrderTraverse(LinkedNode *pNode);
//按递归方式先序遍历void InOrderTraverse(LinkedNode *pNode);
//按递归方式中序遍历void PostOrderTraverse(LinkedNode *pNode);
//按递归方式后序遍历void PreOrderTraverse();
//按非递归方式先序遍历void InOrderTraverse();
//按非递归方式中序遍历void PostOrderTraverse();
//按非递归方式后序遍历void LevelOrderTraverse();
//按非递归方式逐层遍历LinkedNode* GetParent(LinkedNode *pNode);
////按非递归方式获取指定结点的双亲结点void DeleteSubTree(LinkedNode *pNode);
//删除以指定结点为根的子树void DeleteSubTreeNode(LinkedNode *pNode);
//由DeleteSubTree函数调用按非递归方式删除以指定结点为根的子树LinkedNode* SearchByKey(const T &x);
//按非递归方式根据关键字查找关键结点private:LinkedNode *m_pRoot;
//指向根结点的指针 };
文章图片
二叉链表的实现
//实现创建空二叉树
template
LinkBinTree::LinkBinTree() {m_pRoot=NULL;
//将指向根结点的指针置为空 } //实现以指定元素值创建根结点 template LinkedNode* LinkBinTree::CreateRoot(const T& x) {if(m_pRoot!=NULL)m_pRoot->m_data=https://www.it610.com/article/x;
//若原先存在根结点,则直接将根结点的值置为xelsem_pRoot = new LinkedNode(x);
//否则创建一个新结点作为根节点return m_pRoot;
} //判断二叉树是否为空 template bool LinkBinTree::IsEmpty() {if(m_pRoot==NULL)return true;
return false;
} //获取根结点 template LinkedNode* LinkBinTree::GetRoot() {return m_pRoot;
} //将一个结点作为指定结点的左孩子插入 template LinkedNode* LinkBinTree::InsertLeftChild(LinkedNode *pNode,const T &x) {LinkedNode *pNewNode;
if(pNode==NULL)return NULL;
pNewNode = new LinkedNode(x);
if(pNewNode==NULL)return NULL;
pNode->m_pLeftChild = pNewNode;
return pNewNode;
} //将一个结点作为指定结点的右孩子插入 template LinkedNode* LinkBinTree::InsertRightChild(LinkedNode *pNode,const T &x) {LinkedNode *pNewNode;
if(pNode==NULL)return NULL;
pNewNode = new LinkedNode(x);
if(pNewNode==NULL)return NULL;
pNode->m_pRightChild = pNewNode;
return pNewNode;
} //修改指定结点的元素值 template bool LinkBinTree::ModifyNodeValue(LinkNode *pNode,const T &x) {if(pNode==NULL)return false;
pNode->m_data = https://www.it610.com/article/x;
return true;
} //获取指定结点的元素值 template bool LinkBinTree::GetNodeValue(LinkNode *pNode,T &x) {if(pNode==NULL)return false;
x = pNode->m_data;
return true;
} //获取指定结点的左孩子结点 template LinkedNode* LinkBinTree::GetLeftChild(LinkNode *pNode) {if(pNode==NULL)return NULL;
return pNode->m_pLeftChild;
} //获取指定结点的右孩子结点 template LinkedNode* LinkBinTree::GetRightChild(LinkNode *pNode) {if(pNode==NULL)return NULL;
return pNode->m_pRightChild;
} //获取指定结点的双亲结点 //二叉链表中,结点没有指向其双亲结点的指针,要获取双亲结点则需要从根结点开始遍历二叉树直至找到指定结点的双亲结点 template LinkedNode* LinkBinTree::GetParent(LinkNode *pNode) {LinkQueue*> q;
LinkedNode *pCurNode = NULL;
if(pNode==m_pRoot)//若指定结点pNode为根结点,则返回空return NULL;
if(m_pRoot==NULL) //若二叉树是空树,则返回空return NULL;
q.Insert(m_pRoot);
//将根结点入队while(!q.IsEmpty())//当队列不为空时循环{q.Delete(pCurNode);
if(pCurNode->m_pLeftChild==pNode || pCurNode->m_pRightChild==pNode)return pCurNode;
if(pCurNode->m_pLeftChild)q.Insert(pCurNode->m_pLeftChild);
if(pCurNode->m_pRightChild)q.Insert(pCurNode->m_pRightChild);
}return NULL;
} //删除以指定结点为根的子树 //删除以指定结点为根的子树,一方面要将子树从二叉树中删除,另一方面要将子树中的结点释放 //通过将指定结点的双亲结点的指针值置空来将子树从二叉树中删除(删除整棵二叉树,应将根结点指针值置空) //将子树中的结点释放,就是遍历子树中所有结点,将各结点占据内存释放。 template void LinkBinTree::DeleteSubTree(LinkNode *pNode) {LinkNode *pParentNode = NULL;
if(pNode==NULL)//若指定结点为空,则返回return;
if(m_pRoot==pNode)//若将整棵二叉树删除,则令根结点为空m_pRoot = NULL;
else if((pParentNode=GetParent(pNode))!=NULL)//否则若指定结点存在双亲结点,则将双亲结点的左孩子或右孩子置空{if(pParentNode->m_pLeftChild==pNode)pParentNode->m_pLeftChild=NULL;
elsepParentNode->m_pLeftChild=NULL;
}else//否则指定结点不是二叉树中的结点,直接返回return;
DeleteSubTreeNode(pNode);
//调用DeleteSubTreeNode删除以pNode为根的子树 } //由DeleteSubTree函数调用按非递归方式删除以指定结点为根的子树 template void LinkBinTree::DeleteSubTreeNode(LinkNode *pNode) {LinkQueue*> q;
LinkedNode *pCurNode = NULL;
if(pNode==NULL)return;
//按非递归层次遍历的方式删除子树q.Insert(pNode);
while(!q.IsEmpty())//当队列不为空时循环{q.Delete(pCurNode);
if(pCurNode->m_pLeftChild)q.Insert(pCurNode->m_pLeftChild);
if(pCurNode->m_pRightChild)q.Insert(pCurNode->m_pRightChild);
delete pCurNode;
} } //根据关键字查找结点 //根据关键字查找结点,实质就是按照某种规则依次访问二叉树中每个结点,直到找到与关键字匹配的结点。 template LinkNode* LinkBinTree::SearchByKey(const T &x) {LinkQueue*> q;
LinkedNode *pMatchNode = NULL;
if(m_pRoot==NULL)return NULL;
q.Insert(m_pRoot);
//按非递归方式根据关键字查找关键结点while(!q.IsEmpty())//当队列不为空时循环{q.Delete(pMatchNode);
if(pMatchNode->m_data=https://www.it610.com/article/=x)return pMatchNode;
if(pMatchNode->m_pLeftChild)q.Insert(pMatchNode->m_pLeftChild);
if(pMatchNode->m_pRightChild)q.Insert(pMatchNode->m_pRightChild);
}return NULL;
} //清空二叉树 template void LinkBinTree::Clear() {DeleteSubTree(m_pRoot);
} //删除二叉树 template LinkBinTree::~LinkBinTree() {clear();
//清空二叉树中结点 }
文章图片
实现二叉链表需要用到队列和栈,
二叉树的遍历,就是按照某种规则依次访问二叉树中的每个结点,且每个结点仅被访问一次。根据结点访问顺序的不同,分为四种遍历方式:先序遍历、中序遍历、后续遍历、逐层遍历
#include
#include "LinkQueue.h"//链接队列类模板
#include "LinkStack.h"//链接栈类模板
二叉树的先序遍历
二叉树先序遍历递归定义:对于一棵二叉树,先访问其根结点,再访问根结点的左、右子树,对于左、右子树的结点仍然按照先序遍历访问,即先访问根结点,再访问左、右子树。
约定:先序、中序、后序遍历中均是先访问左子树后访问右子树。
//按递归方式先序遍历
template
void LinkBinTree::PreOrderTraverse(LinkedNode *pNode) {if(pNode==NULL)return;
coutm_pLeftChild);
PreOrderTraverse(pNode->m_pRightChild);
}
文章图片
按非递归方式先序遍历
算法设计:非递归先序遍历根据访问规律利用栈来实现,栈顶元素即是下一个要访问的子树的根结点。具体步骤如下:
- 将二叉树的根结点入栈;
- 将栈顶元素出栈并访问(即先访问根结点),若栈顶元素存在右子树则将右子树入栈,若栈顶元素存在左子树则将左子树入栈;
- 重复步骤2,直至栈空。
//按非递归方式先序遍历
template
void LinkBinTree::PreOrderTraverse() {LinkStack*> s;
LinkedNode *pNode = NULL;
if(m_pRoot==NULL)return;
//将根结点入栈s.Push(m_pRoot);
while(!s.IsEmpty())//栈不为空时循环{s.Pop(pNode);
//栈顶元素出栈并被访问coutm_pRightChild){//若结点存在右子树,则将右子树根结点入栈s.Push(pNode->m_pRightChild);
}if(pNode->m_pLeftChild){//若结点存在左子树,则将左子树根结点入栈s.Push(pNode->m_pLeftChild);
}} }
文章图片
二叉树的中序遍历
中序遍历递归方式定义:对于一棵二叉树,先访问根结点左子树,再访问根结点,最后右子树;对于左、右子树中的结点仍然按照中序遍历访问。
文章图片
文章图片
?
//按递归方式中序遍历
template
void LinkBinTree::InOrderTraverse(LinkedNode *pNode) {if(pNode==NULL)return;
InOrderTraverse(pNode->m_pLeftChild);
coutm_pRightChild);
}
文章图片
中序遍历非递归方式
//按非递归方式中序遍历待验证!!!!!
template
void LinkBinTree::PreOrderTraverse() {LinkStack*> s;
LinkedNode *pNode = NULL;
if(m_pRoot==NULL)return;
//将根结点入栈s.Push(m_pRoot);
while(!s.IsEmpty())//栈不为空时循环{s.Pop(pNode);
//栈顶元素出栈并被访问if(pNode->m_pLeftChild==NULL){coutm_pRightChild){s.Push(pNode->m_pRightChild);
}}else{s.Push(pNode->m_pLeftChild);
}} }
文章图片
二叉树的后序遍历
后序遍历递归方式定义:对于一棵二叉树,先访问根结点的左子树,后访问右子树,最后访问根结点;对于左、右子树中的结点仍按照后序遍历的方式访问。
文章图片
文章图片
?
//按递归方式后序遍历
template
void LinkBinTree::PostOrderTraverse(LinkedNode *pNode) {if(pNode==NULL)return;
PostOrderTraverse(pNode->m_pLeftChild);
PostOrderTraverse(pNode->m_pRightChild);
cout
文章图片
非递归方式后序遍历
//按非递归方式后序遍历待验证!!!!!
template
void LinkBinTree::PreOrderTraverse() {LinkStack*> s;
LinkedNode *pNode = NULL;
if(m_pRoot==NULL)return;
//将根结点入栈s.Push(m_pRoot);
while(!s.IsEmpty())//栈不为空时循环{s.Pop(pNode);
//栈顶元素出栈并被访问if(pNode->m_pLeftChild!=NULL){if(pNode->m_pRightChild!=NULL){s.Push(pNode->m_pRightChild);
s.Push(pNode->m_pLeftChild);
}else{s.Push(pNode->m_pLeftChild);
}}else if(pNode->m_pRightChild!=NULL){s.Push(pNode->m_pRightChild);
}else{cout
文章图片
二叉树的逐层遍历
逐层遍历:是指从第1层开始依次对每层中的结点按照从左至右的顺序进行访问。
文章图片
文章图片
?
算法设计:
- 将二叉树的根结点入队;
- 将队头元素出队并访问,若队头元素有左子树则将左子树根结点入队,若队头元素有右子树则将右子树根结点入队;
- 重复步骤2,直至队列为空。
//按非递归方式逐层遍历
template
void LinkBinTree::LevelOrderTraverse() {LinkQueue*> q;
LinkedNode *pNode = NULL;
if(m_pRoot==NULL)return;
q.Insert(m_pRoot);
//将根节点入队while(!q.IsEmpty())//当队列不为空是循环{q.Delete(pNode);
//将队头元素出队并访问coutm_pLeftChild)q.Insert(pNode->m_pLeftChild);
//若结点存在右子树,将左子树根结点入队if(pNode->m_pRightChild)q.Insert(pNode->m_pRightChild);
} }
文章图片
二叉排序树
二叉排序树又称二叉查找树,它或者是一棵空树,或者是具有如下性质的树:
- 若它的左子树非空,则左子树上所有结点的值均小于根结点的值。
- 若它的右子树非空,则右子树上所有结点的值均大于根结点的值。
- 左、右子树也分别是二叉排序树。
二叉排序树的生成过程
二叉排序树中插入一个新结点,应保证插入新结点后的二叉树仍然是一棵二叉排序树。对于一个给定元素k,将其插入到二叉排序树中的具体步骤如下:
- 若二叉排序树为空树,则将元素k作为二叉排序树的根结点。
- 若k等于根结点的值,则该元素已经是二叉排序树中的结点,不需要重复插入,直接返回;
若k小于根结点的值,则将k插入到左子树中;若k大于根结点的值,则将k插入到右子树中。
- 重复步骤2,直至要插入的子树为空,此时将k作为该子树的根节点。
//二叉排序树插入新结点的实现,将元素k插入到二叉排序树btree中
template
void InsertBST(LinkedBinTree &btree,T K) {LinkedNode *pNode = NULL,*pChild = NULL;
T x;
//若二叉排序树为空树,则将K作为根结点if(btree.IsEmpty()){btree.CreateRoot(K);
return;
}pNode = btree.GetRoot();
while(pNode!=NULL){btree.GetNodeValue(pNode,x);
if(K==x)//若K已是二叉排序树中的结点return;
if(K void CreateBST(T R[],int nSize,LinkedBinTree &btree) {int n;
//将R中的元素逐一插入到二叉排序树btree中for(n=1;
n<=nSize;
n++)InsertBST(btree,R[n]);
} //以递归方式实现二叉排序树的查找 template LinkedNode* SearchBST(LinkedNode* pRoot,T K) {LinkedBinTree btree;
T x;
if(pRoot==NULL)//若子树为空,则查找失败return NULL;
btree.GetNodeValue(pRoot,x);
if(K==x)//若K等于根结点的值,则查找成功return pRoot;
else if(K
文章图片
不同结构的二叉树,查找效率不一致。
如何生成平衡二叉树?
哈夫曼树
哈夫曼树,又称最优二叉树,是指在一类有着相同叶子结点的树中具有最短带权路径长度的二叉树。
基本术语
1、结点的权和带权路径长度:在实际应用中,往往给树中的结点赋予一个具有某种意义的实数,该实数就称为结点的权。结点的带权路径长度是指从树根到该结点的路径长度与结点的权的乘积。
2、树的带权路径长度:是指树中所有叶子结点的带权路径长度之和,记作:
文章图片
文章图片
?
其中,n为叶子结点的数目,Wi为第i个叶子结点的权,Li为根结点到第i个叶子结点的路径长度,可知WiLi为第i个叶子结点的带权路径长度。
文章图片
文章图片
?
哈夫曼树及其构造方法
在由n个叶子结点构成的一类二叉树中,具有最短带权路径长度的二叉树称为哈夫曼树。
构造方法如下:
- 已知n个权值为Wi(i=1,2,3,4...n)的结点,将每个结点作为根结点生成n棵只有根结点的二叉树Ti,形成森林F = {T1,T2,T3,T4...Tn}。
- 从森林F中选出根结点权值最小的两棵二叉树Tp和Tq,并通过添加新的根结点将它们合并为一棵新二叉树,新二叉树中Tp和Tq分别作为根结点的左子树和右子树,且根结点的权值等于Tp和Tq两棵二叉树的根结点权值之和。以合并后生成的新二叉树替代森林F中的原有二叉树Tp和Tq。
- 重复步骤2,直至森林F中只存在一棵二叉树。
哈夫曼码
哈夫曼编码是利用哈夫曼树得到的一种不定长的二进制编码,它在数据压缩领域有着广泛应用。
哈夫曼编码
利用哈夫曼码进行数据压缩的基本原理:对于出现频率较高的字符,使用较短的编码;而对于出现频率较低的字符,则使用较长的编码,从而使得用于字符序列的编码总长度最短、节省存储空间。
哈夫曼编码是指将用其他编码法表示的字符序列转成用哈夫曼码表示以减少存储空间,其具体方法为:
- 以要编码的字符集C={c1,c2,c3...cn}作为叶子结点、字符出现的频度或次数W={w1,w2,w3...wn}作为结点的权,构造哈夫曼树。
- 规定哈夫曼树中,从根结点开始,双亲结点到左孩子结点的分支标为0,双亲结点到右孩子结点的分支标为1。从根结点到某一叶子结点经过的分支形成的编码即是该叶子结点所对应自负的哈夫曼码。
哈夫曼解码
哈夫曼解码是指将用哈夫曼码表示的字符序列转成其他编码法表示以让计算正确显示字符内容,其具体方法:
- 将用于表示字符序列的哈夫曼码逐位取出并送入哈夫曼树中。
- 从哈夫曼树的根结点开始,对于每一个结点,遇到位0则经左分支到其左孩子,遇到位1则经右分支到其右孩子。重复该过程直至到达某一个叶子结点,该叶子结点所对应的字符即是解码结果。解码一个字符后回到哈夫曼树的根结点开始解码下一个字符。
?
推荐阅读