数据结构-4-树

数据结构-4-树

    • 结点
    • 树的其他概念
    • 树的抽象数据类型
    • 树的存储结构
      • 双亲表示法
      • 孩子表示法
      • 孩子兄弟表示法
  • 二叉树
    • 特殊二叉树
    • 二叉树的性质
    • 二叉树的顺序结构
    • 二叉链表
    • 树的深度算法
    • 遍历二叉树
      • 前序遍历
      • 中序遍历
      • 后续遍历
      • 层序遍历
    • 扩展二叉树
    • 建立二叉树
    • 线索二叉树
      • 线索二叉树结构实现
  • 树、森林与二叉树的转换
    • 树转换为二叉树
    • 森林转换为二叉树
    • 二叉树转换为树
    • 二叉树转换为森林
  • 树与森林的遍历
  • 赫夫曼树及其应用
    • 路径长度
    • 赫夫曼树
    • 赫夫曼编码
    • 应用

前两章都是线性结构,此章为树结构。

树(Tree)是n(n>=0)个结点的有限集。
n=0时称为空树。
在任意一棵非空树中:
(1)有且仅有一个特定的称为根(Root)的结点;
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、……、Tm,其中每一个集合本身又是一颗树,并且称为根的子树(SubTree)。

结点

结点的分类:
结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶结点或者终端结点;度不为0的结点称为分支结点或者非终端结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。
结点间的关系:
结点的子树的根称为该结点的孩子,相应地,该结点称为孩子的双亲。同一个双亲的孩子之间互称兄弟。结点的祖先是从根到该结点所经分支上的所有结点。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。

树的其他概念

结点的层次:从根开始定义起,根为第一层,根的孩子为第二层。树中结点的最大层次称为树的深度或高度。
森林:m棵互不相交的树的集合。

树的抽象数据类型

ADT 树(tree)
Data
	树是由一个根结点和若干棵子树构成。树中结点具有相同数据类型及层次关系。
Operation
	InitTree(*T)
	DestroyTree(*T)
	CreatTree(*T,definition):按照definition中给出树的定义来构造树。
	TreeEmpty(T)
	TreeDepth(T)
	Root(T):返回T的根结点。
	Value(T,cur_e):cur_e是树T中的一个结点,返回此结点的值。
	Assign(T,cur_e,value):给树T的结点cur_e赋值为value。
	Parent(T,cur_e)
	LeftChild(T,cur_e):若cur_e是树T的非叶结点,则返回它最左的孩子;否则返回空。
	RightSibling(T,cur_e):若cur_e有右兄弟,则返回它的右兄弟;否则返回空。
	InsertChild(*T,*p,i,c):其中p指向树T的某个结点,i为所指结点p的度加上1,非空
							树c与T不相交,操作结果为插入c为树T中p指结点的第i棵子树。
	DeleteChild(*T,*p,i):删除T中p所指结点的第i棵子树。
endADT

树的存储结构

双亲表示法

在每个结点中,附设一个指示器指示其双亲结点在数组中的位置。
结点结构定义:

#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode
{
	TElemType data;
	int parent;
}PTNode;
typedef struct
{
	PTNode nodes[MAX_TREE_SIZE];
	int r,n;	//根的位置和结点数
}PTree;

由于根结点没有双亲,所以根结点的双亲域为-1。
改进1:为方便得知某结点的孩子是什么,增加一个结点最左边孩子的域(长子域),如果没有孩子的结点,则其长子域为-1。
改进2:为方便得知某结点的兄弟关系,增加一个右兄弟域,如果右兄弟不存在,则赋值为-1。

孩子表示法

每个结点都有多个指针域,其中每个指针指向一棵子树的根结点,又叫多重链表表示法。
方案一:每个结点的指针域的个数等于树的度。
方案二:每个结点的指针域的个数等于该结点的度,专门再设一个度域来存储结点指针域的个数。
而孩子表示法就是多重链表表示法的改进:
把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点又n个孩子链表,如果是叶结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
即,每个子树由其根结点起一个接一个连着它的孩子,而每个指向结点的指针又组成一个一维数组。
孩子表示法的结构定义:

#define MAX_TREE_SIZE 100
typedef struct CTNode		//孩子结点
{
	int child;
	struct CTNode *next;
}*ChildPtr;
typedef struct				//表头结构
{
	TElemType data;
	ChildPtr firstchild;
}CTBox;
typedef struct				//树结构
{
	CTBox nodes[MAX_TREE_SIZE];	//结点数组
	int r,n;				//根的位置和结点数
}CTree;

孩子兄弟表示法

设置两个指针,分别指向某结点的第一个孩子和此结点的右兄弟。
结构定义:

typedef struct CSNode
{
	TElemType data;
	struct CSNode *firstchild,*rightsib;
} CSNode,*CSTree;

为方便查找双亲,可再加一个parent指针。
但,这个表示法的最大好处是把一颗复杂的树变成一棵二叉树。每个结点都至多只有两个指向。

二叉树

二叉树(Binar Tree)是n个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
二叉树的左子树和右子树是有顺序的,即使是树中某结点只有一棵子树,也要区分它是左子树还是右子树。

特殊二叉树

  1. 斜树:所有结点都只有左子树或者右子树。(即线性表,线性表结构可以理解为树的一种特殊的表现形式。)
  2. 满二叉树:所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上。
  3. 完全二叉树:对一棵具有n个结点的二叉树按层序编号,编号为i的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同。(即一棵满二叉树中,删去最后一层的最后几片叶子;满二叉树也是一棵完全二叉树)

二叉树的性质

  1. 在二叉树的第i层上至多有2i-1个结点。
  2. 深度为k的二叉树至多有2k-1个结点。(注:是2k再减1)
  3. 对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1
  4. 具有n个结点的完全二叉树的深度为[log2n]+1([x]表示不大于x的最大整数)
  5. 如果对一棵有n个结点的完全二叉树(其深度为[log2n]+1)的结点按层序编号(从第1层到第[log2n]+1层,每层从左到右),对任一结点i,有:(1)如果i=1,则结点i是二叉树的根;如果i>1,则双亲是结点[i/2]。(2)如果2i>n,则结点i为叶子结点;否则其左孩子是结点2i。(3)如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。

二叉树的顺序结构

用一维数组按层序编号存储二叉树中的结点,把不存在的结点设置为"^"。
顺序结构一般只用于完全二叉树。

二叉链表

含一个数据域和两个指针域。
结构定义:

typedef struct BiTNode
{
	TElemType data;
	struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

如有需要可再添加一个指向双亲的指针域,此时称为三叉链表。

树的深度算法

int depth(BiTree &T)
{
	if(!T)
		return 0;
	int leftDepth = depth(T->lchild);
	int rightDepth = depth(T->rchild);
	return leftDepth > rightDepth ? (leftDepth + 1) :(rightDepth + 1);

遍历二叉树

二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

A
B左
C
D左
G
H
E
I右
F

前序遍历

若二叉树为空,则空操作返回;否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。按上图顺序为:ABDGHCEIF。

	void PreOrderTraverse(BiTree &T)
	{
		if(T==NULL)
			return;
		printf("%c",T->data);
		PreOrderTraverse(T->lchild);
		PreOrderTraverse(T->rchild);
	}

中序遍历

若树为空,则空操作返回;否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树。顺序:GDHBAEICF。

	void InOrderTraverse(BiTree &T)
	{
		if(T==NULL)
			return;
		InOrderTraverse(T->lchild);
		printf("%c",T->data);
		InOrderTraverse(T->rchild);
	}

后续遍历

从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。顺序:GHDBIEFCA。

	void PostOrderTraverse(BiTree &T)
	{
		if(T==NULL)
			return;
		PostOrderTraverse(T->lchild);
		PostOrderTraverse(T->rchild);
		printf("%c",T->data);
	}

层序遍历

从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。顺序:ABCDEFGHI。

void LevelOrderTraverse(BiTree &T)
{
    std::queue q;		//利用队列的先进先出的特点
    BiTree *front;
    if (T== NULL)
    	return;
    q.push(T);
    while (!q.empty())
    {
        front = q.front();		//front指向队头
        q.pop();				//队头出队
        if (front->left)
            q.push(front->left);	//原队头的左孩子从队尾入队
        if (front->right)
            q.push(front->right);	//原队头的右孩子入队
        printf("%c ", front->data);
    }
}

注:若已知中序加前序或后序,是可以确定唯一的一棵二叉树;但若已知前序和后续是不可以确定唯一的一颗二叉树。

扩展二叉树

即,使每一个结点的空的孩子赋值为 ‘#’ 。

建立二叉树

把已知树改为扩展二叉树,然后按前序顺序输入。

void CreateBiTree(BiTree *T)
{
	TElemType ch;
	scanf("%c",&ch);
	if(ch=='#')
		*T=NULL;
	else
	{
		*T=(BiTree)malloc(sizeof(BiTNode));
		if(!*T)
		exit(OVERFLOW);
		(*T)->data = ch;
		CreateBiTree(&(*T)->lchild);
		CreateBiTree(&(*T)->rchild);
	}
}

线索二叉树

在前面三种遍历方法中,按顺序每个结点有前驱和后继,指向其前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,对应的二叉树就称为线索二叉树(Threaded Binary Tree)。即,将每个结点的空的孩子用来存储其前驱或后继,左孩子存前驱,右孩子存后继。用以利用好所有已用空间。
对二叉树以某种次序遍历使其变为线索二叉树的过程称为线索化。
为了区分存储的是孩子还是前驱或后继,在每个结点再增设两个标志域ltag、rtag,只存放0和1的布尔型变量。(布尔型变量占用的内存空间要小于指针型的)其中:

  • ltag为0时指向该结点的左孩子,为1时指向该结点的前驱。
  • rtag为0时指向该结点的右孩子,为1时指向该结点的后继。

如果所用二叉树需经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构。

线索二叉树结构实现

二叉树的线索存储结构定义:

typedef enum {Link,Thread} PointerTag;	//Link==0表示指向左右孩子指针
										//Thread==1表示指向前驱或后继的线索
typedef struct BiThrNode
{
	TElemType data;
	struct BiThrNode *lchild,*rchild;
	PointerTag LTag;
	PointerTag RTag;
}BiThrNode,*BiThrTree;

中序遍历线索化的递归函数:

BiThrTree pre;	//全局变量,始终指向刚刚访问过的结点。
void InThreading(BiThrTree p)
{
	if(p)
	{
		InThreading(p->lchild);
		if(!p->lchild)
		{
			p->LTag = Thread;	//Tag==Thread时是前驱或后继,==Link时是孩子
			p->lchild = pre;
		}
		if(!pre->rchild)
		{
			pre->RTag = Thread;
			pre->rchild = p;
		}
		pre = p;	//保持pre指向p的前驱
		InThreading(p->rchild);
	}

在二叉树线索链表上添加一个头结点,并令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点。令二叉树的中序序列中的第一个结点中,lchild域指针和最后一个结点的rchild域指针均指向头结点。
遍历代码:

Status InOrderTraverse_Thr(BiThrTree T)		//T指向头结点
{
	BiThrTree p;
	p = T->lchild;	//p指向根结点
	while(p!=T)		//空树或遍历结束时,p==T
	{
		while(p->LTag==Link)
			p = p->lchild;
		printf("%c",p->data);
		while(p->RTag==Thread && p->rchild!=T)
		{
			p = p->rchild;
			printf("%c",p->data);
		}
		p = p->rchild;
	}
	return OK;

树、森林与二叉树的转换

树转换为二叉树

  1. 加线。在所有兄弟结点之间加一条连线。
  2. 去线。保留长子与其双亲、兄弟间的连线,其余的连线删除。
    长子与双亲:长子作为双亲的左孩子。
    兄弟间:依次成为左一个兄弟的右孩子。
  3. 层次调整。以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。(注:旋转后根结点只会有一个左孩子,因为根结点没有兄弟。而往后的结点中,其左孩子为它在原树中的长子,其右孩子为它在原树中它的左一位兄弟)

森林转换为二叉树

  1. 把每棵树转换为二叉树。
  2. 从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树根结点的右孩子。(由第一步所得的二叉树的根结点只有左孩子)

二叉树转换为树

要判断一棵二叉树应转换为树还是森林,就看它的根结点有没有右孩子。

  1. 加线。若某结点的左孩子存在,则将这个左孩子的n个右孩子结点都作为此结点的孩子,连线。(即,某个左孩子,其双亲与这个左孩子的右孩子、与这个左孩子的右孩子的右孩子……相连)
  2. 去线。删除原二叉树中所有结点与其右孩子结点的连线。
  3. 层次调整。使之结构层次分明。

二叉树转换为森林

  1. 从根结点开始,把根结点与其右孩子、其右孩子与其右孩子的右孩子……之间的连线断开,分成多棵二叉树。
  2. 把各二叉树转换成树。

树与森林的遍历

树的遍历分两种方式:

  1. 先根遍历:先访问树的根结点,然后依次先根遍历根的每棵子树。(即前序遍历)
  2. 后根遍历:先依次后根遍历每棵子树,然后再访问根结点。(即后续遍历)

森林的遍历也分两种,即每棵树依次进行前序遍历或后序遍历。
可发现,森林的前序遍历与其二叉树形式的前序遍历结果相同。森林的后序遍历与其二叉树形式的中序遍历结果相同。树的先根遍历和后根遍历完全可以借用二叉树的前序遍历和中序遍历的算法来实现。

赫夫曼树及其应用

路径长度

从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度。(即走了多少步)
树的路径长度就是从树根到每一结点的路径长度之和。
结点的权:定义在路径上面的值,可以理解为结点间的距离。也指一个结点的权重,可以理解为出现的频率。
结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径之和。

赫夫曼树

赫夫曼树:带权路径长度WPL最小的二叉树。
(注:带权路径长度最小不代表性能最高)
找出赫夫曼树的方法:

  1. 找出权值最小的两个叶子结点,将其作为一个新结点的两个孩子,小的最为左孩子。
  2. 把所得的新结点替换掉已用的两个结点,再进行上面步骤,直至连接完成。

其算法描述:

  1. 根据给定的n个权值{w1,w2,…,wn}构成n棵二叉树的集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi根结点,其左右子树为空。
  2. 在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
  3. 在F中删除这两棵树,同时将新得到的二叉树加入F中。
  4. 重复2和3步骤,直到F只含一棵树为止。

赫夫曼编码

先构建赫夫曼树,然后将树中所有结点的左分支上的权值改为0,右分支上的权值改为1。然后从根结点到各叶子所经过的路径的0或1来编码。

应用

用于压缩与解压。

你可能感兴趣的:(数据结构-4-树)