注:以下内容均为个人复习使用,不保证所述内容绝对严谨;
注:考研知识点相对基础,因此这里只做知识点合集,不保证内容详细。
一、基本术语:
根节点 root,双亲节点: fa[pos] ; 子节点 to[pos]
兄弟节点:父节点相同的节点
节点的度:子树的个数 (向下的出度)
树的度:节点的度的max
叶子节点:度为0的点
内部节点:非root的点
深度:depth。 depth[root]=1;
森林:多个树的集合
三种存储方法:
孩子链表表示法(即用指针写的邻接表)
孩子兄弟表示法(指针域存储:第一个孩子,和第一个兄弟)
双亲表示法(使用数组,存fa[pos])
二、二叉树
定义:度至多为2,分左右子树的树
1. 完全二叉树:
由上而下,由左及右的构造过程中,每一个节点都存在。
左子树height=右子树height 或左子树height=右子树height+1
(左满树)
2. 满二叉树:
k深树,且有2k - 1 个节点,且符合性质 第i层一定有 2k-1个节点
3. 线索二叉树:
规则:
L-son指向左儿子,如果没有左儿子,则指向线索前驱
R-son指向右儿子,如果没有右儿子,则指向线索后驱
(eg. 中序线索树:线索前驱即中序序列中的前一个点,线索后驱指中序序列中的后一个点)
作用: 提高遍历能力
4. 哈夫曼树
目的:记一点的 Va = 点权 * 点到根的距离,
为了让 Va 相对平均(求和较小),构造哈夫曼树
**操作思路:**每次都选取权值最小的两个点,进行连接。
priority_queue< <int,int> >q;
//q内存入所有点的<id,v>
int x1=q.top();q.pop();int x2=q.top();q.pop();
con(x1,x2,++cnt);//创建新节点cnt,并将x1和x2连接至该点
q.push( <cnt, x1+x2> );//放入队列
性质1:树形状可能不唯一,但 Va 总和唯一
性质2:一定为完全二叉树
性质3:节点权值,从上而下,从大到小
哈夫曼编码:对于要访问的叶子节点,其哈夫曼编码=其访问路径。(访问路径用01字符串表示,0表示左儿子,1表示右儿子)
哈夫曼编码性质:无相同前缀
5. 中序、前序、后序、层序
定义:
在dfs遍历的过程中,处理节点和继续深搜两种操作的先后顺序。
记M、L、R分别表示对当前节点、左子树点集、右子树点集的操作顺序
中序:LMR
void dfs(int pos){
dfs(left_child);
work(pos);
dfs(right_child);
}
后序:LRM
void dfs(int pos){
dfs(left_child);
dfs(right_child);
work(pos);
}
前序:MLR
void dfs(int pos){
work(pos);
dfs(left_child);
dfs(right_child);
}
层序:
BFS遍历,按层序顺序遍历。
6. 树、森林 transform into 二叉树
左孩子右兄弟 原则:
即孩子都在自己的左子树,兄弟都在自己的右子树
另:对于森林而言,每个树的Root互相之间是兄弟关系。
三、常考考点
1. 树上节点数计算
满二叉树前提下
叶子节点数= 2 ^ (层数-1),总节点数=1+2+4+…+叶子节点树= 2^(层数) -1
完全二叉树前提下
n+1层完全二叉树,
总节点数= (2n - 1 ) (n层满二叉) + C (第n+1层的节点数)
叶子节点数 = C + 2n-1 - [C/2] (第n+1层节点是被第n层的 c/2个节点延申出来的)
(叶子节点数 = 第n+1层节点数 + 第n层的叶子节点数 )
易错点: 由叶子节点数反求总节点数最多的情况时时,C可以多取一个(成为奇数)。
度为n的树(※)
总结点数= (i:1~n)Σ i*n[i] + 1(根节点)
总结点数= (i:1~n)Σ n[i] + n[0](叶子节点数)
已知完全二叉树第n层的叶子节点个数为x
求该二叉树的总节点树 最多/最少
根据完全二叉树定义,该完全二叉树只有两种可能形状:
第一种情况:
一共有n层,且第n层只有x个叶子节点,其余n-1层为满二叉树
第二种情况:
一共有n+1层,前n层构成满二叉树;且第n层有x个节点没有对应的n+1层的儿子,据此可计算第n+1层有多少个叶子节点;
2. 前中后层序⇒ 树形
所确定的树形的唯一性
前序+中序 或 中序+后序 或 中序+层序 可以唯一确定二叉树形
中序判断左右子树,前序、后序或层序判断谁是root
其余遍历序列组合 无法 唯一 确定
定形方法
LRroot = 后序
rootLR = 前序
L root R = 中序
该法用于判断灵活题目
(其中L和R表示左右子树的子序,即代表的是区间)
3. 普通树变二叉树
已知总节点数n,叶子节点数x,将其转化为二叉树后,二叉树中无右孩子的节点数 = n - x +1 (※)
证明:
根据 左孩子右兄弟原则
对于树上任意一个非叶子节点 pos,他的所有儿子们 有且仅有一个儿子会没有右孩子(最右边的兄弟)
即 所有非根节点 中,共有 n-x 个节点没有兄弟
而 根节点也没有兄弟
所以 ans = n - x + 1
(易错)任意树的后根遍历 = 对应二叉树的中序遍历
证明:根据左孩子右兄弟,画个图就懂了
4. 本章手写代码题考察:
求 子树大小 和 树深度
size[pos] = Σ size[to], size[pos] ++;
depth[pos] = max( depth[size[to] ) +1;
//代码略
※※※ 二叉树表示四则运算式 ※※※
中缀
即中序遍历,需要借助括号实现 [建议现场梳理一下思路]
下代码框内为思路,非代码
void dfs(pos){
if(T[pos]->va){ cout<<va; return ;}//该点值为数字,则一定为叶子节点,直接输出
work(lchild): //对于左儿子
如果左儿子是数字,dfs(lchild) //不需要括号
如果左儿子是比自己高优先级/同优先级的符号,dfs(lchild)//不需要括号
如果左儿子是比自己低优先级的符号,cout<<"(", dfs(lchild),cout<<")";//需要括号
cout<< T[pos]->sign;//输出当前符号
work(rchild): //对于右儿子
如果右儿子是数字,dfs(rchild)//不需要括号
如果右儿子是比自己高优先级的符号,dfs(rchild)//不需要括号
如果右儿子是比自己低优先级/同优先级的符号,cout<<"(", dfs(rchild),cout<<")";//需要括号
return ;
}
后缀
即后序遍历,无需额外操作
前缀
即前序遍历,无需额外操作
※※※※ 非递归 写二叉树遍历 ※※※※
中序遍历
//遍历思路:先处理当前节点的左儿子;如果当前节点的左儿子部分已被处理完,则输出自己,并紧接着处理自己的右儿子(即让右儿子入栈)。
struct *stack[N];int top;
void midlle(struct *T){
stack[++top]=T;struct *p=T;
while(top){
p=stack[top];top--;
while(p!=NULL) stack[++top]=p->lchild, p=p->lchild;//左儿子入栈,直到叶节点
if(top){
p=stack[top];top-;//出栈
print(p->id);//work该节点
stack[++top]=p->rchild;//右儿子入栈
}
}
return ;
}
前序遍历
struct *stack[N];int top;
void Pre(Struct *T){
stack[++top]=T;
while(top){
struct *p=stack[top];top--;
if(p==NULL) continue;
print(p->id);
stack[++top]=p->rchild;
stack[++top]=p->lchild;//先左儿子后右儿子
}
return ;
}
后序遍历
//左儿子(走到无左儿子为止)全进栈
//右儿子进栈(判重,避免重复进栈)
//若无左右儿子,当前节点出栈