数据结构 第五章学习小结
Posted stdeng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 第五章学习小结相关的知识,希望对你有一定的参考价值。
- 数据结构第五章学习小结
- 5.1-5.4
- 5.1 树和二叉树的定义
- 5.1.1 树的定义
- 树是n个结点的有限集,它或为空树,或为非空树
- 对于非空树T:
- (1)有且仅有一个称之为根的结点;
- (2)除根结点以外的其余结点可分为 m个互不相交的有限集 T1, T2 , …,Tm,其中每一个集合本身又是一棵树,并且称为根的子树
- 树的结构定义是一个递归的定义
- 5.1.2 树的基本术语
- (1)结点:树中的一个独立单元
- (2)结点的度:结点拥有的子树数称为结点的度
- (3)树的度:树的度是树内各结点度的最大值
- (4)叶子: 度为 0 的结点称为叶子或终端结点
- (5) 非终端结点:度不为 0 的结点称为非终端结点或分支结点。除根结点之外,非终端结点也称为内部结点
- (6)双亲和孩子:结点的子树的根称为该结点的孩子,相应地,该结点称为孩子的双亲
- (7) 兄弟:同一个双亲的孩子之间互称兄弟
- (8) 祖先:从根到该结点所经分支上的所有结点
- (9) 子孙:以某结点为根的子树中的任一结点都称为该结点的子孙
- (10) 层次:结点的层次从根开始定义起,根为 第一层,根的孩子为第二层。树中任一结点的层次等于其双亲结点的层次加 1
- (11)堂兄弟:双亲在同 一层的结点互为堂兄弟
- (12)树的深度:树中结点的最大层次称为树的深度或高度
- (13)有序树和无序树:如果将树中结点的各子树看成从左至右是有次序的(即不能互换),则称该树为有序树,否则称为无序树。在有序树中最左边的子树的根称为第一个孩子,最右边的称为最后一个孩子
- (14)森林:是 m (m>=O)棵互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林
- 5.1.3 二叉树的定义
- 二叉树与树一样具有递归性质
- 二叉树与树的区别
- (1 )二叉树每个结点至多只有两棵子树(即二叉树中不存在度大千2 的结点)
- (2) 二叉树的子树有左右之分,其次序不能任意颠倒
- 5.2 案例引入
- 数据压缩问题
- 利用二叉树求解表达式的值
- 5.3 树和二叉树的抽象类型定义
- 树的抽象类型定义
-
ADT Tree{
数据对象 D: D 是具有相同特性的数据元素的集合。 数据关系 R: 若 D 为空集,则称为空树; 若 D仅含一个数据元素,则 R 为空集,否则 R={H}, H 是如下二元关系: (1) 在D 中存在唯一的称为根的数据元素 root,它在关系 H 下无前驱; (2) 若D-{root} ≠空集,则存在D-{root}的一个划分D1 , D2, …,比(m>0), 对任意j≠k(1<=j, k<=m) 有 DjnDk =空集, 且对任意的 i (1<=i<=m), 唯一存在数据元素 xi∈Di, 有 <root, xi>∈H; (3) 对应于D-{root} 的划分,H-{ < root, x1 >, …, <root,xm> =有唯一的一个划分 H1 , H2 , …, Hm (m> 0), 对任意j≠k(l<=j<=m) 有HjnHk =空集, 且对任意 i(1<=i<=m), H1 是D1 上的二元关系,(D1, {Hi}) 是一棵符合本定义的树,称为根 root 的子树。 基本操作P: InitTree(&T) 操作结果:构造空树T。 DestroyTree (&T) 初始条件:树T存在。 操作结果:销毁树T。 CreateTree(&T,definition) 初始条件:definition 给出树 T 的定义。 操作结果:按 definition 构造树 T。 ClearTree(&T) 初始条件:树T存在。 操作结果:将树T清为空树。 TreeEmpty(T) 初始条件:树T存在。 操作结果:若 T 为空树,则返回 true, 否则 false。 TreeDepth(T) 初始条件:树T存在。 操作结果:返回T的深度。 Root(T) 初始条件:树T存在。 操作结果:返回T的根。 Value(T,cur_e) 初始条件:树 T 存在, cur_e是 T 中某个结点。 操作结果:返回 cur_e 的值。 Assign(T,cur_e,value) 初始条件:树 T 存在, cur_e是 T 中某个结点。 操作结果:结点 cur_e 赋值为 value。 Parent(T,cur_e); 初始条件:树 T 存在, cur_e是 T 中某个结点。 操作结果:若 cur_e是 T 的非根结点,则返回它的双亲,否则函数值为 “空”。 LeftChild(T,cur_e) 初始条件:树 T 存在, cur_e是 T 中某个结点。 操作结果:若 cur_e是T 的非叶子结点,则返回它的最左孩子,否则返回 “空”。 RightSibling(T,cur_e) 初始条件:树 T 存在, cur_e是 T 中某个结点。 操作结果:若 cur_e 有右兄弟,则返回它的右兄弟,否则函数值为 “空”。 InsertChild(&T,p,i,c) 初始条件:树 T 存在, p 指向 T 中某个结点, 1<=i<=p 所指结点的度+ 1, 非空树 c 与 T 不相交。 操作结果:插入c为T中 p 指结点的第l.棵子树。 DeleteChild(&T,p,i) 初始条件:树 T 存在, p 指向 T 中某个结点, 1<=i<=p 指结点的度。 操作结果:删除T中 p 所指结点的第l.棵子树。 TraverseTree(T) 初始条件:树T存在。 操作结果:按某种次序对T的每个结点访问一次。 ) ADT Tree
-
- 二叉树的抽象类型定义
-
ADT BinaryTree{ 数据对象D: D是具有相同特性的数据元素的集合。 数据关系R: 若 D=<空集, 则 R=<空集, 称 BinaryTree 为空二叉树; 若 D-,6¢, 则 R={H}, H是如下二元关系: (1) 在 D 中存在唯一的称为根的数据元素 root, 它在关系 H 下无前驱; (2) 若 D-{root} -,6¢, 则存在 D-{root}={D1, Dr}, 且 D1 nDr=<空集; (3) 若 D1 ≠空集, 则 D1中存在唯一的元素x1, <root,x1>∈H, 且存在 D1 上的关系 H1 ∈H; 若Dr≠空集则 Dr中存在唯一的元素 Xr, <root , Xr> ∈H, 且存在 Dr上的关系 H工 仁 H; H={ <root, xi>, <root, xr>, Hi, Hr); (4) (D1, { H1}) 是一棵符合本定义的二叉树,称为根的左子树, (Dr, {Hr})是一棵符合本定义的二叉树,称为根的右子树。 基本操作 P: InitBiTree(&T) 操作结果:构造空二叉树T。 DestroyBiTree(&T) 初始条件:二叉树T存在。 操作结果:销毁二叉树T。 Crea七eBiTree(&T,definition) 初始条件; definition 给出二叉树 T的定义 。 操作结果:按 definition 构造二叉树 T。 ClearBiTree(&T) 初始条件:二叉树T存在。 操作结果:将二叉树T清为空树。 BiTreeEmpty(T) 初始条件:二叉树T存在。 操作结果:若 T 为空二叉树,则返回 true, 否则 false。 BiTreeDepth (T) 初始条件:二叉树T存在。 操作结果:返回T的深度。 Root(T) 初始条件:二叉树T存在。 操作结果:返回T的根 。 Value(T,e) 初始条件:二叉树 T存在,e是 T中某个结点。 操作结果:返回e的值。 Assign(T,&e,value) 初始条件:二叉树T存在, e是T中某个结点。 操作结果:结点 e 赋值为 value。 Parent(T,e) 初始条件:二叉树 T存在,e是 T中某个结点。 操作结果:若 e是T的非根结点,则返回它的双亲,否则返回 “空”。 LeftChild(T,e) 初始条件:二叉树T存在, e是T中某个结点。 操作结果:返回e的左孩子。若e 无左孩子,则返回 “空”。 RightChild(T,e) 初始条件:二叉树T存在,e是T中某个结点。 操作结果:返回 e的右孩子。若 e 无右孩子,则返回 “空”。 LeftSibling (T, e) 初始条件:二叉树T存在, e是T中某个结点。 操作结果:返回 e的左兄弟。若 e是T的左孩子或无左兄弟,则返回 “空”。 RightSibling(T,e) 初始条件:二叉树T存在,e是T中某个结点。 操作结果:返回 e的右兄弟。若 e是T的右孩子或无右兄弟,则返回 “空”。 InsertChild(&T,p,LR,c) 初始条件:二叉树 T存在, p 指向 T中某个结点,LR 为 0 或 1, 非空二叉树 c 与 T 不相交且右子树为空。 操作结果:根据 LR 为 0 或 1, 插入 c 为 T中p 所指结点的左或右子树。p 所指结点的原有左或右子树则成为 c的右子树。 DeleteChild (&T, p, LR) 初始条件:二叉树T存在,p指向T中某个结点,LR为0或1。 操作结果:根据LR为0或1, 删除T中p所指结点的左或右子树。 PreOrderTraverse(T} 初始条件:二叉树T存在。 操作结果:先序遍历T, 对每个结点访问一次。 InOrderTraverse(T) 初始条件:二叉树T存在。 操作结果:中序遍历T, 对每个结点访问一次。 PostOrderTraverse(T} 初始条件:二叉树T存在。 操作结果:后序遍历T, 对每个结点访问一次5 LevelOrderTraverse(T) 初始条件:二叉树T存在。 操作结果:层序遍历T, 对每个结点访问一次。 } ADT BinaryTree
-
- 5.4 二叉树的性质和存储结构
- 5.4.1 二叉树的性质
- 满二叉树:深度为 k且含有2^k-1个结点的二叉树
- 特点:每一层上的结点数都是最大结点数,即每一层l的结点数都具有最大值 2i-1
- 完全二叉树:深度为k的, 有n个结点的二叉树, 当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时, 称之为完全二叉树
- 特点:
- (1)叶子结点只可能在层次最大的两层上出现
- (2)对任一结点, 若其右分支下的子孙的最大层次为i, 则其左分支下的子孙的最大层次必为 i或 i+ 1
- 特点:
- 性质
- 性质1:在二叉树的 第i层上至多有2^(i-1)个结点(i>=1)
- 性质2:深度为k的 二叉树至多有 2^k -1 个结点 (k>=1)
- 性质3:对任何一棵二叉树T, 如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2+1
- 性质 4:具有 n 个结点的完全二叉树的深度为[log(2)n]+ 1
- 性质 5:如果对一棵有 n个结点的完全二叉树(其深度为[log(2)n]+ 1) 的结点按层序编号(从第 1 层到第[log(2)n]+ 1 层, 每层从左到右), 则对任一结点i(1<=i<=n), 有
- (1)如果i = 1, 则结点i是二叉树的根,无双亲;如果i> 1, 则其双亲PARENT(i)是结点[i/2]
- ( 2 )如果2i>n, 则结点i无左孩子(结点i为叶子结点);否则其左孩子LCHILD(1)是结点2i
- (3)如果2i+1>n, 则结点i无右孩子;否则其右孩子RCHILD(i)是结点2i+1
- 满二叉树:深度为 k且含有2^k-1个结点的二叉树
- 5.4.2 二叉树的存储结构
- 1. 顺序存储结构
-
//-----二叉树的顺序存储表示----- #define MAXTSIZE 100 //二叉树的最大结点数 typedef TElemType SqBiTree [MAXTSIZE]; //0 号单元存储根结点 SqBi Tree bt;
- 这种顺序存储结构仅适用于完全二叉树。因为, 在最坏的情况下, 一个深度为k且只有k个结点的单支树(树中不存在度为2 的结点)却需要长度为2^k-1的一维数组。这造成了存储空间的极大浪费
-
- 2. 链式存储结构
-
//- - - - -二叉树的二叉链表存储表示- ---- typedef struct BiTNode{ TElemType data; //结点数据域 struct BiTNode *lchild,*rchild; //左右孩子指针 ) BiTNode,*BiTree;
- 对于一般二叉树,更适合采取链式存储结构
- 二叉链表和三叉链表:二叉树的链表中的结点至少包含 3 个域:数据域和左、 右指针域。有时,为了便于找到结点的双亲,还可在结点结构中增加一个指向其双亲结点的指针域
- 链表的头指针指向二叉树的根结点
- 线索链表——在含有 n个结点的二叉链表中有 n+l 个空链域,可以利用这些空链域存储其他有用信息
- 在不同的存储结构中,实现二叉树的操作方法也不同,如找结点x的双亲PARENT(T, e), 在三叉链表中很容易实现,而在二叉链表中则需从根指针出发巡查
-
- 1. 顺序存储结构
- 5.4.1 二叉树的性质
- 5.5 遍历二叉树和线索二叉树
- 5.5.1 遍历二叉树
- 先序遍历、中序遍历、后序遍历、层次遍历
- 算法:
- 中序遍历的递归算法
-
void InOrderTraverse{BiTree T) {//中序遍历二叉树T的递归算法 if(T)//若二叉树非空 { InOrderTraverse {T-> lchild) ; / /中序遍历左子树 cout<<T->data; II访问根结点 InOrderTraverse {T-> rchild); //中序遍历右子树 } }
- 中序遍历的非递归算法
-
void InOrderTraverse(BiTree T) {//中序遍历二叉树T的非递归算法 InitStack(S);p=T; q=new BiTNode; while (p || ! StackEmpty (S)) { if(p) //p非空 { Push(S,p);//根指针进栈 p=p-> lchild; //根指针进栈, 遍历左子树 } else { Pop (S, q);//退栈 cout<<q->data;//访问根结点 p=q-> rchild;//遍历右子树 } }//while }
- 先序遍历的顺序建立二叉链表
-
void CreateBiTree(BiTree &T) {//按先序次序输入二叉树中结点的值( 一个字符), 创建二叉链表表示的二叉树T cin>>ch; if(ch== ‘#‘) T=NULL; //递归结束, 建空树 else {//递归创建二叉树 T=new BiTNode; //生成根结点 T-> data=ch; //根结点数据域置为 ch CreateBiTree (T-> lchild); //递归创建左子树 CreateBiTree (T-> rchild); //递归创建右子树 } //else }
- 复制二叉树
-
void Copy(BiTree T,BiTree &NewT) {//复制一棵和T完全相同的二叉树 if(T==NULL) //如果是空树, 递归结束 { NewT=NULL; return; } else { NewT=new BiTNode; NewT-> da ta=T->data; //复制根结点 Copy (T-> lchild, NewT-> lchild); //递归复制左子树 Copy (T-> rchild, NewT-> rchild); //递归复制右子树 }//else }
- 计算二叉树的深度
-
int Depth(BiTree T) {//计算二叉树T的深度 if(T==NULL) return 0; //如果是空树,深度为0, 递归结束 else { m=Depth {T->lchild); //递归计算左子树的深度记为m n=Depth {T->rchild) ; //递归计算右子树的深度记为n if{m>n) return{m+1); //二叉树的深度为m与n的较大者加1 else return(n+1); } }
- 统计二叉树中结点的个数
-
int NodeCount(BiTree T) {//统计二叉树T中结点的个数 if (T==NULL) return O; //如果是空树,则结点个数为0, 递归结束 else return NodeCount (T->lchild) +Node Count (T->rchild) + 1; //否则结点个数为左子树的结点个数+右子树的结点个数+1 }
- 算法:
- 先序遍历、中序遍历、后序遍历、层次遍历
- 5.5.2 线索二叉树
- 二叉树的二叉线索类型定义
-
//- - - - -二叉树的二叉线索存储表示- ---- typedef struct BiThrNode { TElemType data; struct BiThrNode *lchild,*rchild; //左右孩子指针 int LTag,RTag;//左右标志 ) BiThrNode,*BiThrTree;
- 算法
- 以结点p为根的子树中序线索化
-
void InThreading(BiThrTree p) { //pre是全局变址,初始化时其右孩子指针为空,便于在树的最左点开始 建线索 if(p) InThreading (p-> lchild) ;//左子树递归线索化 if (! p-> lchild) //p的左孩子为空 { p->LTa_g=1; //给p加上左线索 p-> lchild=pre; //p的左孩子指针指向pre (前驱) }//if else p->LTag=O; //pre的右孩子为空 if { ! pre-> rchild) { pre-> RTag=1; //给pre加上右线索 pre-> rchld=p; //pre的右孩子指针指向p (后继) }//if else p->RTag=0; pre=p; //保持pre指向p的前驱 InThrending (p-> rchild) ; //右子树递归线索化 } }
- 带头结点的二叉树中序线索化
-
void InOrderThreading(BiThrTree &Thrt,BiThrTree T) {// 中序遍历二叉树 T, 并将其中序线索化,Thrt指向头结点 Thrt=new Bi ThrNode;//建头结点 Thrt->LTag=O; // 头结点有左孩子, 若树非空,则其左孩子为树 根 Thrt-> RTag=l; // 头结点的右孩子指针为 右线索 Thrt-> rchild=Thrt; //初始化时右指针指向自己 if (! T) Thrt-> lchild=Thrt; //若树为空,则左指针也指向自己 else { Thrt-> lchild=T; pre=Thrt; //头结点的左孩子指向根,pre 初值指向头结点 InThreading(T); //调用算法5. 7, 对以T为 根的二叉树进行中序线索化 pre-> rchild=Thrt; //算法5.7结束后,pre为 最右结点,pre的右线索指向头结点 pre-> RTag=1; Thrt- > rchild=pre; //头结点的右线索指向 pre } }
- 遍历中序线索二叉树
-
void InOrderTraverse_Thr(BiThrTree T) {//T指向头结点,头结点的左链lchild指向根结点, 可参见线索化算法5.8。 //中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出 p=T-> lchild; //p指向根结点 while(p!=T)//空树或遍历结束时,p==T { while (p-> LTag==O) p=p-> lchild; //沿左孩子向下 cout<<p->data;II访问其左子树为空的结点 while (p-> RTag==l&&p-> rchild ! =T) { p=p-> rchild; cout<<p-> data; //沿右线索访问后继结点 } p=p-> rchild;//转向p的右子树 } }
- 5.5.1 遍历二叉树
- 5.6-5.7
- 5.6 树和森林
- 5.6.1 树的存储结构
- 双亲表示法、孩子表示法、孩子兄弟表示法
-
//- - - - - -树的二叉链表(孩子-兄弟)存储表示----- typedef struct CSNode{ ElemType data; struct CSNode *firstchild, *ne·xtsibling; ) CSNode,*CSTree;
- 5.6.2 森林与二叉树的转换
- 5.6.3 树和森林的遍历
- 森林的两种遍历方法:先序遍历和中序遍历
- 当以二叉链表做树的存储结构时,树的先根遍历和后根遍历可借用二叉树的先序遍历和中序遍历的算法实现
- 5.7 哈夫曼树及其应用
- 5.7.1 哈夫曼树的基本概念
- 哈夫曼树又称最优树,是一类带权路径长度最短的树
- (1) 路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径
- (2) 路径长度:路径上的分支数目称作路径长度
- (3)树的路径长度:从树根到每一结点的路径长度之和
- (4)权:赋予某个实体的一个量,是对实体的某个或某些属性的数值化描述
- (5)结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积
- (6)树的带权路径长度:树中所有叶子结点的带权路径长度之和
- (7)哈夫曼树:假设有m个权值{w1,w2,.....,wm},可以构造一棵含n个叶子结点的二叉树,每个叶子结点的权为 wi, 则 其中带权路径长度 WPL最小的二叉树称做最优二叉树或哈夫曼树
- 在哈夫曼树中,权值越大的结点离根结点越近。
- 哈夫曼树又称最优树,是一类带权路径长度最短的树
- 5.7.2 哈夫曼树的构造算法
- 哈夫曼树的存储表示
-
//- - - - -哈夫曼树的存储表示 --- - - .- typedef struct{ int weight; //结点的权值 int parent,lchild,rchild; ) HTNode,*HuffmanTree; //结点的双亲、左孩子、右孩子的下标 //动态分配数组存储哈夫曼树
- 哈夫曼树是一种二叉树,由于哈夫曼树中没有度为 1 的结点,则一棵有 n 个叶子结点的哈夫曼树共有 2n-1 个结点,可以存储在一个大小为 2n-1的一维数组中
- 构造哈夫曼树算法的实现:初始化和创建树
-
void CreateHuffmanTree(HuffmanTree &HT,int n) {//构造哈夫曼树 HT if(n<=l) return; m=2*n-l; HT=new HTNode[m+1); //0 号单元未用,所以需要动态分配 m+l 个单元, HT[m)表示根结点 for(i=1;i<=m;++i) //将1~m号单元中的双亲、左孩子,右孩子的下标都初始化为0 {HT[i] .parent=O;HT[i] .lchild=O;HT[i] .rchild=O;} for(i=1;i<=n;++i} //输人前 n 个单元中叶子结点的权值 cin>>HT[i] .weight; /*- - -初始化工作结束, 下面开始创建哈夫曼树- - - */ for (i=n+1; i<=m; ++i} {//通过 n-1 次的选择、删除 、 合并来创建哈夫曼树 Select (HT, i-1, sl, s2); //在 HT[k] (1<=k<=i-1)中选择两个其双亲域为0 且权值最小的结点,并返回它们在 HT 中的序号 sl和 s2 HT[sl] .parent=i;HT[s2] .parent=i; //得到新结点 i, 从森林中删除sl, s2, 将sl和s2 的双亲域由 0改为l. HT[i] .lchild=sl;HT [i]. rchild=s2; //s1, s2分别作为i的左右孩子 HT[i] .weight=HT[sl] .weight+HT[s2] .weight; //i的权值为左右孩子权值之和 }/ /for }
- 5.7.3 哈夫曼编码
- 两个性质
- 1.哈夫曼编码是前缀编码
- 2.哈夫曼编码是最优前缀编码
- 哈夫曼编码的算法实现
- 根据哈夫曼树求哈夫曼编码
-
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n) //I从叶子到根逆向求每个字符的哈夫曼编码, 存储在编码表HC中 HC=new char* [n+1]; //分配存储n个字符编码的编码表空间 cd=new char [n]; //分配临时存放每个字符编码的动态数组空间 cd[n-1]=‘
- 两个性质
- 5.1-5.4