-
树
1.1树的定义
树(Tree)是n个有限数据元素的集合。当n=0时,称这一棵树为空树。
在一棵非空树T中:
(2)若 n>1,除根结点之外的其余数据元素被分成 m(m>0)个互不相交的集合 T1, T2,…, Tm,其中每一个集合 Ti(1≤i≤m)本身又是一棵树。树 T1,T2,…,Tm称为这个根结点的子 树。
1.2相关术语
(1)结点的度:结点所拥有的子树的个数称为该结点的度。
(2)叶结点:度为 0 的结点称为叶结点,或者称为终端结点。
(3)分支结点:度不为 0 的结点称为分支结点,或者称为非终端结点。一棵树的结点除 叶结点外,其余的都是分支结点。
(4)孩子、双亲、兄弟:树中一个结点的子树的根结点称为这个结点的孩子。这个结点 称为它孩子结点的双亲。具有同一个双亲的孩子结点互称为兄弟。
(5)路径、路径长度:如果一棵树的一串结点 n1,n2,…,nk有如下关系:结点 ni是 ni+1的 父结点(1≤i<k),就把 n1,n2,…,nk称为一条由 n1至 nk的路径。这条路径的长度是 k-1。
(6)祖先、子孙:在树中,如果有一条路径从结点 M 到结点 N,那么 M 就称为 N 的祖 先,而 N 称为 M 的子孙。
(7)结点的层数:树的根结点的层数为1,其余结点的层数等于它的双亲结点的层数加 1。
(8)树的深度:树中所有结点的最大层数称为树的深度。
(9)树的度:树中各结点度的最大值称为该树的度。
(10)有序树和无序树:如果一棵树中结点的各子树从左到右是有次序的,即若交换了 某结点各子树的相对位置,则构成不同的树,称这棵树为有序树;反之,则称为无序树。
(11)森林:零棵或有限棵不相交的树的集合称为森林。自然界中树和森林是不同的概 念,但在数据结构中,树和森林只有很小的差别。任何一棵树,删去根结点就变成了森林。
2.二叉树
2.1定义与性质
2.1.1 二叉树
二叉树(Binary Tree)是 n 个有限元素的集合,该集合或者为空、或者由一个称为根(root) 的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个结点。 二叉树是有序的,即若将其左、右子树颠倒,就成为另一棵不同的二叉树。即使树中结点只有一棵子树,也要区分它是左子树还是右子树。
2.1.2 满二叉树与完全二叉树
(1)满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同 一层上,这样的一棵二叉树称作满二叉树。
(2)完全二叉树
一棵深度为 k 的有 n 个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行 编号,如果编号为 i(1≤i≤n)的结点与满二叉树中编号为 i 的结点在二叉树中的位置相同,则 这棵二叉树称为完全二叉树。完全二叉树的特点是:叶子结点只能出现在下层和次下层, 且下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全 二叉树未必是满二叉树。
2.1.3 二叉树的性质
性质 1 一棵非空二叉树的第 i 层上多有 2i-1个结点(i≥1)。
性质 2 一棵深度为 k 的二叉树中,多具有 2k-1 个结点。
性质 3 对于一棵非空的二叉树,如果叶子结点数为 n0,度数为 2 的结点数为 n2,则有: n0=n2+1。
性质 4 具有 n 个结点的完全二叉树的深度 k 为 log2n向下取整 +1 或 log2(n+1) 向上取整 。
性质 5 对于具有 n 个结点的完全二叉树,如果按照从上至下和从左到右的顺序对二叉 树中的所有结点从 1 开始顺序编号,则对于任意的序号为 i 的结点,有:
(1)如果 i>1,则序号为 i 的结点的双亲结点的序号为 i/2向下取整 ;如果 i=1,则序号为 i 的 结点是根结点,无双亲结点。
(2)如果 2i≤n,则序号为 i 的结点的左孩子结点的序号为 2i;如果 2i>n,则序号为 i 的 结点无左孩子。
(3)如果 2i+1≤n,则序号为 i 的结点的右孩子结点的序号为 2i+1;如果 2i+1>n,则 序号为 i 的结点无右孩子。 此外,若对二叉树的根结点从 0 开始编号,则相应的 i 号结点的双亲结点的编号为 (i-1)/2向下取整,左孩子编号为2i+1,右孩子编号为2i+2。
2.2二叉树的存储
2.2.1 顺序存储结构
二叉树的顺序存储,就是用一组连续的存储单元存放二叉树中的结点。一般是按照二叉树结点从上至下、从左到右的顺序存储。这样结点在存储位置上的前驱后继关系并不一 定就是它们在逻辑上的邻接关系。
依据二叉树的性质,完全二叉树和满二叉树采用顺序存储比较合适,树中结点的序号可 以唯一地反映出结点之间的逻辑关系。
这种存储对于需增加许多空结点才能将一棵二叉树改造成为一棵完全二叉树的存储时,会造 成空间的大量浪费,不宜用顺序存储结构。坏的情况是右单支树,一棵深度为 k 的右单支 树,只有 k 个结点,却需分配 2k-1 个存储单元。
二叉树的顺序存储表示可描述为:
#define MAX_TREE_SIZE 100 //二叉树的大结点数 typedef TElemType SqBiTree[MAX_TREE_SIZE] //0 号单元存放根结点 SqBiTree b; //将 b 定义为含有 MAX_TREE_SIZE 个 TElemType 类型元素的一维数组
2.2.2 链式存储结构
二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链来指示着元素的逻辑关系。
通常有下面两种形式:
(1)二叉链表
链表中每个结点由三个域组成,除了数据域外,还有两个指针域,分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。
结点的存储结构为:其中,data 域存放某结点的数据信息;lchild 与 rchild 分别存放指向左孩子和右孩子的指 针,当左孩子或右孩子不存在时,相应指针域值为空。
lchild | data | rchild |
二叉树的二叉链表存储表示可描述为:
typedef struct BiTNode{ TElemType data; struct BiTNode *lchild;*rchild; //左右孩子指针 }BiTNode,*BiTree; BiTree b;//即将 b 定义为指向二叉链表结点结构的指针类型。
(2)三叉链表
每个结点由四个域组成。其中,data、lchild 以及 rchild 三个域的意义与二叉链表结构相同;parent 域为指向该结 点双亲结点的指针。
lchild | data | rchild | parent |
这种存储结构既便于查找孩子结点,又便于查找双亲结点;但是,相对于二叉链表存储结构而言,它增加了空间开销。
2.3二叉树的遍历
二叉树的遍历是指按照某种顺序访问二叉树中的每个结点,使每个结点被访问一次且仅 被访问一次。
通过一次完整的遍历,可使二叉树中结点信息由非线性排列变为某种 意义上的线性序列。也就是说,遍历操作实质是对一个非线性结构进行线性化操作。
2.3.1 先序遍历
先序遍历的递归过程为:若二叉树为空,遍历结束。否则,
(1)访问根结点;
(2)先序遍历根结点的左子树;
(3)先序遍历根结点的右子树。
void PreOrder(BiTree b){ if (b!=NULL){ Visit(b->data); //访问结点的数据域 PreOrder(b->lchild); //先序递归遍历 b 的左子树 PreOrder(b->rchild); //先序递归遍历 b 的右子树 } }
2.3.2 中序遍历
中序遍历的递归过程为:若二叉树为空,遍历结束。否则,
(1)中序遍历根结点的左子树;
(2)访问根结点;
(3)中序遍历根结点的右子树。
void InOrder(BiTree b){ if (b!=NULL)){ InOrder(b->lchild); //中序递归遍历 b 的左子树 Visit(b->data); //访问结点的数据域 InOrder(b->rchild); //中序递归遍历 b 的右子树 } }
2.3.3 .后序遍历
后序遍历的递归过程为:若二叉树为空,遍历结束。否则,
(1)后序遍历根结点的左子树;
(2)后序遍历根结点的右子树。
(3)访问根结点;
void PostOrder(BiTree b){ if (b!=NULL)){ PostOrder(b->lchild); //后序递归遍历 b 的左子树 PostOrder(b->rchild); //后序递归遍历 b 的右子树 Visit(b->data); //访问结点的数据域 } }
2.3.4 层序遍历
二叉树的层次遍历,是指从二叉树的第一层(根结点)开始,从上至下逐层遍历, 在同一层中,则按从左到右的顺序对结点逐个访问。
在进行层次遍历时,可设置一个队列结构,遍历从二 叉树的根结点开始,首先将根结点指针入队列,然后从对头取出一个元素,每取一个元素, 执行下面两个操作:
(1)访问该元素所指结点;
(2)若该元素所指结点的左、右孩子结点非空,则将该元素所指结点的左孩子指针和右 孩子指针顺序入队。
此过程不断进行,当队列为空时,二叉树的层次遍历结束。
void LevelOrder(BiTree b){ BiTree Queue[MAX_TREE_SIZE]; int front,rear; if (b==NULL) return; front=-1; rear=0; Queue[rear]=b; while(front!=rear) { Visit(Queue[++front]->data); //访问队首结点数据域 if (Queue[front]->lchild!=NULL) //将队首结点的左孩子结点入队列 Queue[++rear]= Queue[front]->lchild; if (Queue[front]->rchild!=NULL) //将队首结点的右孩子结点入队列 Queue[++rear]= Queue[front]->rchild; } }
2.4线索二叉树和森林
2.4.1 线索二叉树
按照某种遍历方式对二叉树进行遍历,可把二叉树中所有结点排列为一个线性序列,二 叉树中每个结点在这个序列中的直接前驱结点和直接后继结点是什么,二叉树的存储结构中 并没有反映出来,只能在对二叉树遍历的动态过程中得到这些信息。
2.4.2 树的存储结构
要求:存储结构不但能存储各结点本身的数据信息,还要能唯一地反映树中各结点之间的逻辑关系。
3.哈弗曼树和哈弗曼编码
3.1哈弗曼树的基本概念
二叉树的路径长度是指由根结点到所有叶结点的路径长度之和。
在哈夫曼编码树中,树的带权路径长度的含义是各个字符的码长与其出现次数的乘积之和,也就是电文的代码总长,所以采用哈夫曼树构造的编码是一种能使电文代码总长短的 不等长编码。
求哈夫曼编码,实质上就是在已建立的哈夫曼树中,从叶结点开始,沿结点的双亲链域 回退到根结点,每回退一步,就走过了哈夫曼树的一个分支,从而得到一位哈夫曼码值,由 于一个字符的哈夫曼编码是从根结点到相应叶结点所经过的路径上各分支所组成的 0,1 序 列,因此先得到的分支代码为所求编码的低位码,后得到的分支代码为所求编码的高位码。