4.数据结构——二叉树

 一,树

1.概念——非线性数据结构;一般而言一个树会有一个根节点,向下延申出若干个子节点,每个末端的子结点被称为叶子节点

2.有根树——有根树存在一个根节点root。基于习惯,我们往往会把树倒过来画,根节点在最上面。

p的祖先:p及p上面的点;

p的后代:p及p下面的点 ;

结点深度:往上走走几次能走到根;

树的深度:它从上往下有多少层;

二,二叉树

1.对于每一个结点,它拥有的子节点的个数,称为这个节点的.所有节点的度不超过2的树成为二叉树;(引申出p叉树——所有节点的度不超过p的树)

2. 每个二叉树的节点最多只会有两个子节点,他的两个子节点一般会被称为左右儿子,两个子树一般会被称为左右子树。当然,左右儿子甚至根节点本身都有可能缺失(如下图右侧)(一个节点都没有可以称之为空二叉树)。    

3.具有相同节点深度的二叉树位于同一层

3. (1)完全二叉树:除了最后一层外其他层的节点个数全满,而且最后一层的节点从左到右排列直到最后一个节点。(最后一层点的个数范围是[1 , 2^{h-1}],最多就是一个满二叉树)

                [注]深度为h的完全二叉树最少有 2^{h-1} 个节点,最多有  2^{h} - 1 个节点

  (2)满二叉树 :所有层的节点全满

                [注]深度为h的完全二叉树有 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值