数据结构 五、树与二叉树
基本概念
树和森林
森林:
是m(m>=0)棵互不相交的树的集合
树(Tree):
是n(n>=0)个结点的有限集。
n=0时称为空树。
在任意一颗非空树中:
(1)有且仅有一个特定的称为根(Root)的结点。
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、.....、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
基本术语
根结点: 非空树中无前驱结点的结点
结点的度: 结点拥有的子树数
树的度: 树内各结点的度的最大值
叶子: 度为0的, 终端结点
分支结点: 度不为0, 非终端结点,内部结点
孩子, 双亲: 结点的子树的根称为该结点的孩子,该结点称为孩子的双亲
兄弟结点: 有共同的双亲
堂兄弟: 双亲在同一层的结点
树的深度: 树中结点的最大层次
有序树: 树中结点的各子树从左到右有次序
无序树:树中结点的各子树无次序.
二叉树
为什么要使用二叉树
定义
二叉树不是树的特殊情况,是另一个概念(因为二叉树区分左右子树)
二叉树(Binary Tree)
是n(n>=0)个结点的有限集合,
该集合或者为空集(空二叉树),
或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成(子树也为二叉树)。
特点
每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点。
左子树和右子树是有顺序的,次序不能任意颠倒。
即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。
五种基本形态
空二叉树
只有一个根结点
根结点只有左子树
根结点只有右子树
根结点既有左子树又有右子树
抽象数据类型定义
性质
性质1:
在二叉树的第i层上至多有个结点(i>=1)
(请问第i层最少有几个结点? 1个)
性质2:
深度为k的二叉树至多有个结点(k>=1)
性质3:
对任何一棵二叉树T,如果其
叶子结点数为,度为2的结点数为,则。
证明:
一棵二叉树,除了叶子结点,就是度为1或2的结点。再设度为1的结点数,
则树T 的结点总数n:。
我们再换个角度,
求解树T的总边数m:
从入度角度:
由于根结特殊,没有分支进入,所以总边数为结点总数减去1。也就是
从出度角度:
可得:;
可推导出:;
继续推导可得。
满二叉树
定义:
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点。
或者说:
是一棵深度为k并且有个结点的二叉树。
特点:
- 每层都铺满结点
- 所有叶子结点在同一层上。
性质:
1) 一颗树深度为h,最大层数为k,深度与最大层数相同,k=h;
2) 叶子数为2h;
3) 第k层的结点数是:2k-1;
4) 总结点数是:2k-1,且总节点数一定是奇数。
完全二叉树
定义:
若设二叉树的深度为h,除第 h 层外,其它各层的结点数都达到最大个数,第h层所有的结点都连续集中在最左边。
也就是说:
在满二叉树中,从最后一个结点开始,连续的去掉任意数量的结点,得到的树就完全二叉树。
性质
性质4:
具有n个结点的完全二叉树的深度为
(向下取整:表示不大于X的最大整数)。
证明:
性质5:
如果对一颗有n个结点的完全二叉树的结点按层序编号(从第1层到第层,每层从左到右),对任一结点i(1<=i<=n)有:
- 如果i=1,则结点i是二叉树的根,无双亲;
-
- 如果i>1,则其双亲是结点。
- 如果2×i>n,则结点i无左孩子(结点i为叶子结点);
-
- 否则其左孩子是结点2×i。
- 如果2×i+1>n,则结点i无右孩子;
-
- 否则其右孩子是结点2×i+1。
线索二叉树
定义:
利用二叉链表中的空指针域:
如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱;
如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继;
——这种改变指向的指针称为“线索” ;
加上了线索的二叉树称为线索二叉树(Threaded Binary Tree)
对于叉树按基种遍历次序使其变为线索二叉树的过程叫线索化
存储结构
顺序存储
方式:
按满二叉树的结点层次编号,一次存放二叉树。
(使用maxINF代表不存在的结点。)
缺点:
- 不能反应逻辑关系;
- 对于特殊的二叉树(左斜树、右斜树),浪费存储空间。
结论
所以二叉树顺序存储结构一般只用于满二叉树/完全二叉树。
链式存储
使用二叉链表
/*二叉树的二叉链表结点结构定义*/ typedef struct BiNode { char data; /*结点数据*/ struct BiNode *lchild;//左孩子指针 struct BiNode *rchild;//右孩子指针 }BiNode,*BiTree;
注意:
二叉链表储存效率不是100%;
因为:在n个结点的二叉链表中,有(n+1)个空指针域。
证明:
一定有2n个链域,除了根节点入度一定为1,则入度一定为n-1,即有n-1个链域存放了指针。
( 2n -(n-1)=n+1 )
建立和遍历二叉树
遍历二叉树定义:
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
目的:
因为计算机只会处理线性序列,而我们研究遍历,就是把树中的结点变成某种意义的线性序列,这给程序的实现带来了好处。
二叉树
正常建树和遍历
先序遍历
void Pre(node *F){ if(!F)return; cout<<F->value; Pre(F->L); Pre(F->R); }
代码
/** *输入格式:ABC##DE#G##F### **/ #include<bits/stdc++.h> using namespace std; //定义链树 struct btnode { char value; btnode *lc; btnode *rc; }; //先序建树 void build(btnode *&F) { char ch; cin>>ch; if(ch=='#') F=NULL; else { F=new btnode; F->value=ch; build(F->lc); build(F->rc); } } //中序输出 void output(btnode *F) { if(F) { output(F->lc); cout<<F->value; output(F->rc); } } int main() { btnode *F; build(F); output(F); return 0; }
实战
根据先序/后序+中序建树
前提:
若二叉树的节点值各不相同,则二叉树先序,中序,后序均唯一。
由先序和中序序列 或者 中序和后序序列可以确定唯一的二叉树
思想
先序和中序:先序判断根,中序判断左右
中序和后序:后序判断根,中序判断左右
一、已知二叉树的前序序列和中序序列,建树。
在二叉树的前序遍历序列中,第一个数字总是树的根结点的值。
但在中序遍历序列中,根结点的值在序列的中间,左子树的结点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。
因此我们扫描中序遍历序列,就能找到根结点的值。
分别找到了左、右子树的前序遍历序列和中序遍历序列,我们就可以用递归方法分别去构建左右子树。
代码步骤:
1、确定树的根节点。 树根是当前树中所有元素在前序遍历中最先出现的元素。
2、求解树的子树。
找出根节点在中序遍历中的位置,
根左边的所有元素就是左子树,根右边的所有元素就是右子树。
若根节点左边或右边为空,则该方向子树为空;
若根节点左边和右边都为空,则根节点已经为叶子节点。
3、递归求解树。 将左子树和右子树分别看成一棵二叉树,重复1、2、3步,直到所有的节点完成定位。
代码
#include <bits/stdc++.h> using namespace std; struct node { char value; node *L; node *R; }; char pre[58]; char pos[58]; int n; int ans = 0; node *buildtree(char pre[], char pos[], int n, int cnt) { if (n == 0) return NULL; node *F = new node; char root = pre[0]; F->value = root; int k = 0; for (int i = 0; i < n; i++) { if (pos[i] == root) { k = i; break; } } cnt++; F->L = buildtree(pre + 1, pos, k, cnt); //左子树的先序头结点为pre+1,,左子树的中序头结点为pos,左子树的长度为k, if (F->L) { ans = max(cnt, ans); } F->R = buildtree(pre + k + 1, pos + k + 1, n - k - 1, cnt); //左子树的先序头结点为pre+k+1,左子树的中序头结点为pos+k+1,左子树的长度为n-k-1, if (F->R) { ans = max(cnt, ans); } return F; } int main() { cin >> n; cin >> pre; cin >> pos; // cout<<ans; node *F = buildtree(pre, pos, n, 1); if (n == 1) ans = 1; cout << ans; return 0; }
实战
7-1 还原二叉树 (25 分)
给定一棵二叉树的前序遍历序列和中序遍历序列,要求计算该二叉树的高度。
输入格式:
输入首先给出正整数 n(≤50),为树中结点总数。随后 2 行先后给出前序和中序遍历序列,均是长度为 n 的不包含重复英文字母(区别大小写)的字符串。
输出格式:
输出为一个整数,即该二叉树的高度。
输入样例:
9
ABDFGHIEC
FDHGIBEAC
输出样例:
5
根据两序遍历得出树的深度
#include<bits/stdc++.h> using namespace std; int dfs(char a[],char b[],int n){ int i; if(n==0)return 0; for(i=0;i<n;i++){ if(b[i]==a[0]){ break; } } int x=dfs(a+1,b,i)+1;//求左子树的深度 int y=dfs(a+i+1,b+i+1,n-i-1)+1;//求右子树的深度 return x>y?x:y; } int main() { char a[101]; char b[101]; int n; cin>>n; cin>>a>>b; int cnt=dfs(a,b,n); cout<<cnt<<endl; }
二叉树与树的转化
树变二叉树
二叉树变树
并查集
参考:
https://www.tspweb.com/key/层次遍历序列重建二叉树.html
哈夫曼树和哈夫曼编码
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/15726453.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步