一,树
1.概念——非线性数据结构;一般而言一个树会有一个根节点,向下延申出若干个子节点,每个末端的子结点被称为叶子节点
2.有根树——有根树存在一个根节点root。基于习惯,我们往往会把树倒过来画,根节点在最上面。
p的祖先:p及p上面的点;
p的后代:p及p下面的点 ;
结点深度:往上走走几次能走到根;
树的深度:它从上往下有多少层;
二,二叉树
1.对于每一个结点,它拥有的子节点的个数,称为这个节点的度.所有节点的度不超过2的树成为二叉树;(引申出p叉树——所有节点的度不超过p的树)
2. 每个二叉树的节点最多只会有两个子节点,他的两个子节点一般会被称为左右儿子,两个子树一般会被称为左右子树。当然,左右儿子甚至根节点本身都有可能缺失(如下图右侧)(一个节点都没有可以称之为空二叉树)。
3.具有相同节点深度的二叉树位于同一层
3. (1)完全二叉树:除了最后一层外其他层的节点个数全满,而且最后一层的节点从左到右排列直到最后一个节点。(最后一层点的个数范围是[1 , ],最多就是一个满二叉树)
[注]深度为h的完全二叉树最少有 个节点,最多有 - 1 个节点
(2)满二叉树 :所有层的节点全满
[注]深度为h的完全二叉树有 - 1 个节点
三,完全二叉树的存储,建立
——————例题一
1.存储
(1)一般会以数组的[1]位置为根节点建立二叉树;
(2)数组的[t]位置的左儿子和右儿子对应的位置分别为[2t]和[2t+1],父亲节点的位置为[t/2](做儿子。
(3) 节点按从上到下从左到右进行标号(数组存储方式及二叉树图像如下图)
2.建立
用递归的方式建树
void Build(int t) //建立以t为根的子树
{
UpdateData(t);//更新t节点的数据(包括是否存在)
Build(t + t);//如果子节点存在,建立左儿子,右儿子
Build(t + t + 1);
}
[注]如果用此方法建立非完全二叉树问题?
会浪费很多点(按左右儿子分别为2t,2t+1的计算方法,如节点4下面没有子节点,但是节点5下面有子结点,左节点为10,右节点为11,这样就会导致8.9这两个节点浪费掉);
四.一般二叉树的存储
1.可以用数组下表模拟节点编号,用多个数组来记录节点信息。
2.可以用结构体来存
struct TreeNode{
int value;//存节点的值
int l,r,fa;//定义左儿子,右儿子,和父亲节点
}a[100001];//a[i]表示上述信息
3.用指针来存储
struct TreeNode{
int value;
TreeNode *r,*l,*fa;//左儿子,右儿子,父节点的节点地址
}
TreeNode *root;//根节点的地址
操作
五,二叉树的遍历(访问)顺序
————访问一棵建立好的二叉树(二叉树的遍历顺序可分为先序遍历、中序遍历和后序遍历,以访问根节点的时间做区分)
1.先序遍历(先访问根)(根左右)(遍历顺序1 2 4 5 3 6 7)
void PreOrder(TreeNode *p)//遍历节点的位置是p的节点
{
cout << p->value << endl;//输出节点p上存储的值
if(p->l) PreOrder(p->l);//如果左节点存在,访问左节点
if(p->r) PreOrder(p->r);//如果右节点存在,访问右节点
}
PreOrder(root);
2.中序遍历(根访问的顺序在中间)(左根右)(遍历顺序4 2 5 1 6 3 7)
void InOrder(TreeNode *p)
{
if(p->l) InOrder(p->l);
cout << o->value;
if(p->r) InOrder(p->r);
}
InOrder(root);
3.后序遍历(根访问的顺序在最后)(左右根)(遍历顺序4 5 2 6 7 3 1)
void PostOrder(TreeNode *p)
{
if(p->l) PostOrder(p->l);
if(p->r) PostOrder(p->r);
cout << p->value << endl;
}
PostOrder(root);
4.层级遍历(BFS序列)(逐层访问节点,加入队列遍历)(利用队列思想)
TreeNode *q[N];//结构体数组——队列数组的每一个元素都是一个结构体
void BFS(TreeNode *root)
{ //新建队列
int front = 1,rear = 1;//已经有一个头结点存在;
q[1] = root;//root为队列的队首
while(front <= rear){
TreeNode *p = q[front];//把p点位置的节点定义为队首节点
front ++;//持续遍历(让队首节点出队)
cout << p->value << endl;//输出p节点元素的值(输出出栈元素的值)
if(p->l) q[++ rear] = p->l;//如果左节点存在,让左节点入栈
if(p->r) q[++ rear] = p->r;//如果右节点存在,让右节点存在
}
}
BFS(root);
5.深度计算
初始 d = 1;
遍历时 p->l->d = p->d + 1;
p->l->d = p->d + 1;
六,例题
1.遍历完全二叉树
#include<bits/stdc++.h>
using namespace std;
int n;
//先序
inline void PreOrder(int t)
{
printf("%d ",t);
if(t + t <= n) PreOrder(t + t);
if(t + t + 1 <= n) PreOrder(t + t + 1);
}
//中序
inline void InOrder(int t)
{
if(t + t <= n) InOrder(t + t);
printf("%d ",t);
if(t + t + 1 <= n) InOrder(t + t + 1);
}
//后序
inline void PostOrder(int t)
{
if(t + t <= n) PostOrder(t + t);
if(t + t + 1 <= n) PostOrder(t + t + 1);
printf("%d 7",t);
}
int main()
{
scanf("%d",&n);
PreOrder(1);
printf("\n");
InOrder(1);
printf("\n");
PostOrder(1);
printf("\n");
return 0;
}
2.遍历一般二叉树
结构体方法
#include<bits/stdc++.h>
using namespace std;
int n;
struct TreeNode{
int l,r,fa;
}a[1030];
//先序
inline void PreOrder(int t)
{
printf("%d ",t);
if(a[t].l) PreOrder(a[t].l);
if(a[t].r) PreOrder(a[t].r);
}
//中序
inline void InOrder(int t)
{
if(a[t].l) InOrder(a[t].l);
printf("%d ",t);
if(a[t].r) InOrder(a[t].r);
}
//后序
inline void PostOrder(int t)
{
if(a[t].l) PostOrder(a[t].l);
if(a[t].r) PostOrder(a[t].r);
printf("%d ",t);
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
int x,y;//x是左儿子,y是右儿子
scanf("%d%d",&x,&y);
if(x) a[i].l = x,a[x].fa = i;
if(y) a[i].r = y,a[y].fa = i;
}
PreOrder(1);
printf("\n");
InOrder(1);
printf("\n");
PostOrder(1);
printf("\n");
return 0;
}
指针方法
#include<bits/stdc++.h>
using namespace std;
int n;
struct TreeNode{
int value;
TreeNode *l,*r,*fa;
}a[1030];
//先序
inline void PreOrder(TreeNode *t)
{
printf("%d ",t->value);
if(t->l) PreOrder(t->l);
if(t->r) PreOrder(t->r);
}
//中序
inline void InOrder(TreeNode *t)
{
if(t->l) InOrder(t->l);
printf("%d ",t->value);
if(t->r) InOrder(t->r);
}
//后序
inline void PostOrder(TreeNode *t)
{
if(t->l) PostOrder(t->l);
if(t->r) PostOrder(t->r);
printf("%d ",t->value);
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
int x,y;//x是左儿子,y是右儿子
scanf("%d%d",&x,&y);
a[i].value = i;
if(x) a[i].l = &a[x],a[x].fa = &a[i];//此时l,r,fa都是指针类型所以它们的值都是地址
if(y) a[i].r = &a[y],a[y].fa = &a[i];
}
PreOrder(&a[1]);
printf("\n");
InOrder(&a[1]);
printf("\n");
PostOrder(&a[1]);
printf("\n");
return 0;
}
3.最近公共祖先
法一:利用数组存储公共结点进行遍历
#include<bits/stdc++.h>
using namespace std;
struct TreeNode{
int l,r,fa;
}a[1001];
int n,c[1001],d[1001];//c和d分别存,u,v分别到根节点的路径,及路径上节点的内容
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)//结构体法构造二叉树
{
int x , y;//x是左儿子的下标,y是右儿子的下标
scanf("%d%d",&x,&y);
if(x) a[i].l = x,a[x].fa = i;
if(y) a[i].r = y,a[y].fa = i;
}
int u,v;
scanf("%d%d",&u,&v);
int l1 = 0;;//存u到根的路径有多少个点
while(u != 1)
c[++ l1] = u,u = a[u].fa;//从u点往上走到根节点下的那个点,且把节点位置信息复制到数组上
c[++ l1] = 1;
int l2 = 0;//存v到根的路径有多少个点
while(v != 1)
d[++ l2] = v,v = a[v].fa;//从v点往上走到根节点下面的那个点,且把节点的位置信息复制到数组上
d[++ l2] = 1;
//寻找最近公共祖先
int x = 0;//从上往下记录公共祖先的位置
for(int i = l1,j = l2;i && j; -- i,-- j)//此时的l1,l2分别是u,v分别到根节点的距离
{
if(c[i] == d[j]) x = c[i];//x会一直覆盖,一直更新
else break;//从上往下只要开始有不一样的,那么下面的肯定不一样
}
printf("%d",x);
return 0;
}
法二:BFS
//利用bfs,如果u,v不在同一深度,先把深的点挪上去
//如果挪上去后他俩是同一个点,那么离根节点近的点就是远的点的祖先
//如果挪上去后不是一个点,那么他俩就一起网上挪,一段一段挪,直至相遇,相遇点就是最近祖先
//仍然是利用结构体的思路
#include<bits/stdc++.h>
using namespace std;
int n,q[1001];
struct TreeNode
{
int depth;
int l,r,fa;
}a[1001];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
int x , y;
scanf("%d%d",&x,&y);
if(x) a[i].l = x,a[x].fa = i;
if(y) a[i].r = y,a[y].fa = i;
}
//开始利用队列模拟
int front = 1,rear = 1;
q[1] = 1;//声明根节点的位置是1
a[1].depth = 1;//声明根的深度是1
while(front <= rear)//队列内有元素时
{
int p = q[front];//把p点定义为队首节点
++ front;//把队首出队列
if(a[p].l) q[++ rear] = a[p].l,a[a[p].l].depth = a[p].depth + 1;
if(a[p].r) q[++ rear] = a[p].r,a[a[p].r].depth = a[p].depth + 1;
}
//开始寻找最近公共祖先
int u , v;
scanf("%d%d",&u,&v);
if(a[u].depth < a[v].depth) swap(u,v);
int x = a[u].depth - a[v].depth;
for(int i = 1;i <= x;i ++) u = a[u].fa;
while(u != v)
{
u = a[u].fa;
v = a[v].fa;
}
printf("%d\n",u);
return 0;
}
4.二叉树节点子树和
(1)数据范围1
#include<bits/stdc++.h>
using namespace std;
struct TreeNode
{
int value;//value是该点权值
int l,r,fa;
}a[1025];
int n,sum;
//遍历
inline void order(int t)
{
sum += a[t].value;
if(a[t].l) order(a[t].l);
if(a[t].r) order(a[t].r);
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
int x,y;
scanf("%d%d",&x,&y);
if(x) a[i].l = x,a[x].fa = i;
if(y) a[i].r = y,a[x].fa = i;
}
for(int i = 1;i <= n;i ++) scanf("%d",&a[i].value);
//遍历求和
for(int i = 1;i <= n;i ++)
{
sum = 0;
order(i);//遍历以i为根的子树
printf("%d ",sum);
}
return 0;
}
(2)数据范围2(dfs)
思路:节省空间,不用重复遍历
遍历一个节点的子树 = 该点内容 + 左子树权值和 + 右子树权值和(左右子树已经遍历过所以不会浪费时间空间)
#include<bits/stdc++.h>
using namespace std;
struct TreeNode
{
int value;//value是该点权值
int l,r,fa;
}a[1025];
int n,v[1000010];
//遍历
int solve(int t)
{
int x = a[t].value;
if(a[t].l) x += solve(a[t].l);
if(a[t].r) x += solve(a[t].r);
v[t] = x;
return x;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
int x,y;
scanf("%d%d",&x,&y);
if(x) a[i].l = x,a[x].fa = i;
if(y) a[i].r = y,a[x].fa = i;
}
for(int i = 1;i <= n;i ++) scanf("%d",&a[i].value);
solve(1);
//遍历求和
for(int i = 1;i <= n;i ++) printf("%d ",v[i]);
return 0;
}