【数据结构】课堂复习笔记4——树和二叉树
发表于更新于
字数总计:3k阅读时长:10分钟阅读量: 杭州
编程【数据结构】课堂复习笔记4——树和二叉树
Kendal学习的网课是青岛大学-王卓的数据结构与算法基础系列课程,可在bilibili上观看。并配合zjut课堂笔记整理,仅用于学习和复习
四、树和二叉树
4.1 树和二叉树的定义
4.1.1 树的定义
- 树:是n个结点的有限集
- 有且仅有一个称之为根的结点
- 除根结点以外的其余结点可分为m(m>0)个互不相交的有限集T1, T2, …,
Tm, 其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)
- 树的前驱和后继的特点:
- 树的根结点没有前驱结点,除根结点之外的所有结点有且只有一个前驱结点
- 树中所有结点可以有零个或多个后继结点。
- 基本术语:
- 1:
- 根:即根结点(没有前驱)
- 叶子:即终端结点(没有后继)
- 森林:指m棵不相交的树的集合(例如删除A后的子树)
- 有序树:结点各子树从左至右有序,不能互换(左为第一)
- 无序树:结点各子树可互换位置
- 2:
- 双亲:即上层的那个结点(直接前驱)
- 孩子:即下层结点的子树的根(直接后继)
- 兄弟:同一双亲下的同层结点(孩子之间互称兄弟)
- 堂兄弟:即双亲位于同一层的结点(但并非同一双亲)
- 祖先:即从根到该结点所经分支的所有结点
- 子孙:即该结点下层子树中的任一结点
- 3:
- 结点:即树的数据元素
- 结点的度:结点挂接的子树数
- 结点的层次:从根到该结点的层数(根结点算第一层)
- 终端结点:即度为0的结点,即叶子
- 分支结点:即度不为0的结点(也称为内部结点)
- 树的度:所有结点度中的最大值
- 树的深度:指所有结点中最大的层数
4.1.2 二叉树的定义
- 二叉树:是n(n≥0)个结点所构成的集合,它或为空树(n = 0);或为非空树,对于非空树T:
- 有且仅有一个称之为根的结点
- 除根结点以外的其余结点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树
4.2 二叉树的性质和存储结构
4.2.1 二叉树的性质
- 性质:
- 性质1: 在二叉树的第i层上至多有个结点。至少1个
- 性质2: 深度为k的二叉树至多有个结点,至少k个
- 性质3: 对于任何一棵二叉树,若2度的结点数有个,则叶子数必定为 (即)
- 性质4: 具有n个结点的完全二叉树的深度必为
- 性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i
的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为
- 特殊形态的二叉树:
- 满二叉树:一棵深度为k 且有个结点的二叉树
- 完全二叉树:深度为k
的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k
的满二叉树中编号从1至n的结点一一对应
4.2.2 二叉树的顺序存储
- 实现:按满二叉树的结点层次编号,依次存放二叉树中的数据元素。
4.2.3 二叉树的链式存储
二叉链表:
typedef struct BiNode{ TElemType data; struct BiNode *lchild,*rchild; }BiNode,*BiTree;
|
三叉链表:
typedef struct TriTNode { TelemType data; struct TriTNode *lchild,*parent,*rchild; }TriTNode,*TriTree;
|
4.3 遍历二叉树和线索二叉树
4.3.1 遍历算法
- 先序遍历:根、左、右
- 中序遍历:左、根、右
- 后序遍历:左、右、根 先序遍历算法:
Status PreOrderTraverse(BiTree T){ if(T==NULL) return OK; else{ cout<<T->data; PreOrderTraverse(T->lchild); PreOrderTraverse(T->rchild); } }
|
中序遍历算法:
Status InOrderTraverse(BiTree T){ if(T==NULL) return OK; else{ InOrderTraverse(T->lchild); cout<<T->data; InOrderTraverse(T->rchild); } }
|
后序遍历算法:
Status PostOrderTraverse(BiTree T){ if(T==NULL) return OK; else{ PostOrderTraverse(T->lchild); PostOrderTraverse(T->rchild); cout<<T->data; } }
|
- 时间效率:,每个结点只访问一次
- 空间效率:,栈占用的最大辅助空间
4.3.2 遍历算法的应用
二叉树的建立:按先序遍历序列建立二叉树的二叉链表
void CreateBiTree(BiTree &T){ cin>>ch; if (ch==’#’) T=NULL; else{ T=new BiTNode; T->data=ch; CreateBiTree(T->lchild); CreateBiTree(T->rchild); } }
|
计算二叉树结点总数:
int NodeCount(BiTree T){ if(T == NULL ) return 0; else return NodeCount(T->lchild)+NodeCount(T->rchild)+1; }
|
计算二叉树叶子结点总数:
int LeadCount(BiTree T){ if(T==NULL) return 0; if (T->lchild == NULL && T->rchild == NULL) return 1; else return LeafCount(T->lchild) + LeafCount(T->rchild); }
|
计算二叉树的深度:
int Depth (BiTree T ){ int depthval, depthLeft, depthRight; if ( !T ) depthval = 0; else { depthLeft = Depth( T->lchild ); depthRight= Depth( T->rchild ); depthval = 1 + (depthLeft > depthRight ?depthLeft : depthRight); } return depthval; }
|
重要结论:
若二叉树中各结点的值均不相同,则:由二叉树的先序序列和中序序列,或由其后序序列和中序序列均能唯一地确定一棵二叉树,但由前序序列和后序序列却不一定能唯一地确定一棵二叉树。
中序遍历非递归算法:
Status InOrderTraverse(BiTree T){ BiTree p; InitStack(S); p=T; while(p||!StackEmpty(S)){ if(p) {Push(S,p);p=p->lchild;} else { Pop(S,q); printf("%c",q->data); p=q->rchild; } } return OK; }
|
二叉树层次遍历算法:
typedef struct{ BTNode data[MaxSize]; int front,rear; }SqQueue;
void LevelOrder(BTNode *b){ BTNode *p; SqQueue *qu; InitQueue(qu); enQueue(qu,b); while(!QueueEmpty(qu)){ deQueue(qu,p); printf("%c",p->data); if(p->lchild!=NULL) enQueue(qu,p->lchild); if(p->rchild!=NULL) enQueue(qu,p->rchild); } }
|
复制二叉树:
int Copy(BiTree T,BiTree &NewT){ if(T==NULL){ NewT=NULL; return 0; } else{ NewT=new BiTNode; NewT->data=T->data; Copy(T->lChild,NewT->lchild); Copy(T->rChild,NewT->rchild); } }
|
4.3.3 线索二叉树
- 若结点有左子树,则lchild指向其左孩子;否则,
lchild指向其直接前驱(即线索)
- 若结点有右子树,则rchild指向其右孩子;否则,
rchild指向其直接后继(即线索)
- 这种改变指向的指针称为:线索
- 对二叉树按某种遍历次序使其变为线索二叉树的过程叫线索化
- 增设标志域:itag,rtag
- itag=0:指向左孩子,itag=1:指向前驱
- rtag=0:指向右孩子,rtag=1:指向后继
4.4 树和森林
4.4.1 树的存储结构
typedef struct PTNode{ TElemType data; int parent; }PTNode;
#define MAX_TREE_SIZE 100 typedef struct{ PTNode nodes[MAX_TREE_SIZE]; int r,n; }PTree;
|
typedef struct CTNode{ int child; struct CTNode *next; }*ChildPtr;
typedef struct{ TElemType data; ChildPtr firstchild; }CTBox;
#define MAX_TREE_SIZE 100 typedef struct{ PTNode nodes[MAX_TREE_SIZE]; int r,n; }CTree;
|
- 孩子兄弟表示法:二叉链表表示法
分别指向第一个孩子结点和下一个兄弟结点
typedef struct CSNode{ ElemType data; struct CSNode *firstchild,*nextsibling; }CSNode,*CSTree;
|
4.4.2 树与二叉树的转换
- 树转换成二叉树: 给定一棵树,可以找到唯一对应的二叉树
- 加线:在兄弟之间加一连线
- 抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系
- 旋转:所有水平路径以子树的根结点为轴心,将整树顺时针转45°
树转换成的二叉树其根结点右子树一定为空
- 二叉树转换成树:
- 加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子,……沿分支找到的所有右孩子,都与p的双亲用线连起来
- 抹线:抹掉原二叉树中双亲与右孩子之间的连线
- 调整:将结点按层次排列,形成树结构
4.4.3 森林与二叉树的转换
- 森林转换成二叉树:
- 将各棵树分别转换成二叉树。
- 将每棵树的根结点用线相连。
- 以第一棵树根结点为新二叉树的根,每棵树的根结点水平直线连接,再以新二叉树的根结点为轴心,顺时针旋转,构成二叉树型结构。
- 二叉树转换成森林:
- 抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
- 还原:将孤立的二叉树还原成树
4.5 哈夫曼树
4.5.1 哈夫曼树的基本概念
- 路径:由一结点到另一结点间的分支所构成
- 路径长度:路径上的分支数目
- 树的路径长度:从树根到每一个结点的路径长度之和。记作:TL
- 结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树
- 权:将树种结点赋给一个有着某种含义的数值
- 带权路径长度:结点到根的路径长度与结点上权的乘积
- 树的带权路径长度:树中所有叶子结点的带权路径长度之和
- 哈夫曼树:构造出来的带权路径长度最小的二叉树
- 特点:
- 满二叉树不一定是哈夫曼树
- 权大的结点靠近根
- 权相同的二叉树,哈夫曼树不唯一
4.5.2 哈夫曼树的构造算法
- 根据给定的n个权值{w1,w2,……wn},构造n棵只有根结点的二叉树
- 在森林中选取两棵根结点权值最小的树作左右子树,构造一棵新的二叉树,置新二叉树根结点权值为其左右子树根结点权值之和。
- 在森林中删除这两棵树,同时将新得到的二叉树加入森林中。
- 重复上述两步,直到只含一棵树为止,这棵树即哈夫曼树。
哈夫曼树的结点度数为0或2,没有度为1的结点
一棵有n个叶子结点的Huffman树有2n-1个结点(n-1次合并,共产生n-1个新结点且都具有2个孩子)
typedef struct { int weght; int parent,lch,rch; }*HuffmanTree
|
void CreatHuffmanTree (HuffmanTree HT,int n){ if(n<=1)return; m=2*n-1; HT=new HTNode[m+1]; for(i=1;i<=m;++i) {HT[i].lch=0;HT[i].rch=0;HT[i].parent=0;} for(i=1;i<=n;++i)cin>>HT[i].weight; }
|
for( i=n+1;i<=m;++i){ { Select(HT,i-1, s1, s2); HT[s1].parent=i; HT[s2] .parent=i; HT[i].lch=s1; HT[i].rch=s2 ; HT[i].weight=HT[s1].weight + HT[s2] .weight; } }
|
4.5.3 哈夫曼树编码
- 哈夫曼编码是前缀编码,即任一字符的编码都不是另一字符编码的前缀。
- 哈夫曼编码树中没有度为1的结点。若叶子结点的个数为n,则哈夫曼编码树的结点总数为
2n-1
- 接收过程:按左0、右1的规定,从根结点走到一个叶结点,完成一个字符的译码。反复此过程,直到接收数据结束。