二叉树

一 . 一维数组二叉

所用知识点:1 . 左子树 = 二倍根 ; 2 . 右子树 = 二倍根子树 + 1 ;

      2 . 一棵树有 2 的 k 次方 减一个节点 ;

      3 . 一棵树最后一行有 2 的 k-1 次方个节点;

解题链接:P4715 【深基16.例1】淘汰赛 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<bits/stdc++.h>
using namespace std;
int n;
struct human{
    int id,eng;
}a[100000];
int main(){
    cin>>n;
    int f=pow(2,n);//1<<n
    for(int i=1;i<=f;i++){
        cin>>a[f+i-1].eng;
        a[f+i-1].id=i;
    //    cout<<a[f+i-1].id <<" "<<a[f+i-1].eng <<endl;
    }
    for(int j=1;j<=n;j++){
        int o=1;
        for(int i=f+o-1;i<=f+f-1;i+=2){
            if(a[i].eng >a[i+1].eng ){//将获胜方存入位置
                a[i/2].eng =a[i].eng ;
                a[i/2].id =a[i].id ;
            }else{
                a[i/2].eng =a[i+1].eng ;
                a[i/2].id =a[i+1].id ;
            }
            //a[i/2].eng=max(a[i].eng ,a[i+1].eng );
            o+=2;
        }
        f/=2;
    }
    int p=0;
    if(a[2].eng >a[3].eng )cout<<a[3].id;
    else cout<<a[2].id;
return 0;
}

二 . 二叉树的遍历

知识点:1 . 中序遍历:按照左子树,根,右子树的顺序访问节点;

    2 . 前序遍历:按照根,左子树,右子树的顺序访问节点;

    3 . 后序遍历:按照左子树,右子树,根的顺序访问节点。

如此这般,我们可知前序与中序求后序,或根据中序与后序求前序;

【模板】遍历三项

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n;
 4 struct tree{
 5     int left,right;
 6 }a[10000];
 7 void front(int m){
 8     if(m==0)return ;
 9     cout<<m<<" ";//
10     front(a[m].left);//
11     front(a[m].right);//
12 }
13 void mid(int m){
14     if(m==0)return ;
15     mid(a[m].left);//
16     cout<<m<<" ";//
17     mid(a[m].right);//
18 }
19 void last(int m){
20     if(m==0)return ;
21     last(a[m].left);//
22     last(a[m].right);//
23     cout<<m<<" ";//
24 }
25 int main(){
26     cin>>n; 
27     for(int i=1;i<=n;i++){
28         cin>>a[i].left>>a[i].right;
29     }
30     front(1);
31     cout<<endl;
32     mid(1);
33     cout<<endl;
34     last(1);
35 return 0;
36 }

 

综上,可得例题:P1827 [USACO3.4] 美国血统 American Heritage - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 string zx,qx,hx,op,i,o;
 4 void bl(string q,string z){
 5     if(q.empty()) return ;
 6     char t=q[0];
 7     int k=z.find(t);//找出地址
 8     q.erase(q.begin());//用完即丢!方便后续操作 。。。
 9     bl(q.substr(0,k),z.substr(0,k));
10     bl(q.substr(k),z.substr(k+1));//分别便利左儿子和右儿子
11     cout<<t;
12 }
13 
14 int main(){
15     cin>>zx>>qx;
16     bl(qx,zx);
17     return 0;
18 }

 【变式】求线序数列:P1030 [NOIP2001 普及组] 求先序排列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 string q,z,h; 
 4 void dfs_q(string zx,string hx){
 5     if(zx.empty())return ;
 6     char s=hx[hx.size()-1];
 7     cout<<s;
 8     int pos=zx.find(s,0); 
 9     string zx_left=zx.substr(0,pos),hx_left=hx.substr(0,pos);//在函数substr内,pos是指下标下一位; 
10     string zx_right=zx.substr(pos+1,zx.size()-1),hx_right=hx.substr(hx_left.size(),zx_right.size());
11 //    cout<<zx_left<<" "<<zx_right<<endl;
12 //    cout<<hx_left<<" "<<hx_right<<endl;
13 //    cout<<endl;
14     dfs_q(zx_left,hx_left);
15     dfs_q(zx_right,hx_right); 
16 }
17 int main(){
18     cin>>z>>h;
19     dfs_q(z,h);
20 return 0;
21 }

 

一道很不错的暴力二叉树(广搜+二叉树):P1364 医院设置 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,vis[105],total=1e9,ans;
 4 struct node{int peo,l,r,f;}a[105];
 5 struct hspt{int id,dis;};
 6 queue<hspt>q;
 7 int main(){
 8     cin>>n;
 9     for(int i=1;i<=n;i++){
10         cin>>a[i].peo>>a[i].l>>a[i].r;
11         a[a[i].l].f=i;a[a[i].r].f=i;
12     }
13     for(int i=1;i<=n;i++){
14         memset(vis,0,sizeof(vis));
15         vis[i]=1;
16         ans=0;
17         q.push({i,0});
18         while(!q.empty()){
19             hspt tmp=q.front();q.pop();
20             //父节点 
21             if(a[tmp.id].f&&!vis[a[tmp.id].f]){
22                 ans+=a[a[tmp.id].f].peo*(tmp.dis+1);
23                 vis[a[tmp.id].f]=1;
24                 q.push({a[tmp.id].f,tmp.dis+1});
25             }
26             //左儿子 
27             if(a[tmp.id].l&&!vis[a[tmp.id].l]){
28                 ans+=a[a[tmp.id].l].peo*(tmp.dis+1);
29                 vis[a[tmp.id].l]=1;
30                 q.push({a[tmp.id].l,tmp.dis+1});
31             }
32             //右儿子 
33             if(a[tmp.id].r&&!vis[a[tmp.id].r]){
34                 ans+=a[a[tmp.id].r].peo*(tmp.dis+1);
35                 vis[a[tmp.id].r]=1;
36                 q.push({a[tmp.id].r,tmp.dis+1});
37             }
38             
39         }
40         total=min(ans,total);
41     }
42     cout<<total;
43     return 0;
44 }

 三.二叉搜索树(BST)

1.BST性质

(1)左子树小于根,右子树大于

(2)二叉搜索树的每一棵子树都是一颗二叉搜索树 

2.二叉树的查询

 根据二叉树的性质,如果根节点的数刚好是查找的数,则返回根节点的值;不然有两种情况,一是此数比根节点小,则查询此根左子树,二是此数比根节点大,则查询此根右子树。

例如上图,

我们要查询60

从根节点50开始,60 > 50 往右子树里找,到65,65 > 60 往右子树里找,找到60

3.二叉树的插入

与查询一样,依次判断寻找子树,直到叶子节点后加入此数。

如图我们要插入48

48 < 50,去左子树,到40

48 > 40,去右子树,到45

48 > 45,45没有子节点了,48成为45的右儿子

4.二叉树的最值

最小值 >> 一直在左子树找

最大值 >> 一直在右子树找

5.二叉树的删除

在二叉树中,最好删除的是叶子结点,如此我们可以看此节点是否为叶子结点,如果不是,就在不破坏二叉树性质的前提下,将其与左/右节点交换位置,直到它成为叶子结点。

6.例题讲解

P5076 【深基16.例7】普通二叉树(简化版

1.首先读题可知,题目中需要我们的操作是

(1)查询排名为x的数

(2)查询数x的排名

(3)求x的前驱与后继

(4)插入数x

ok,这样让我们一个个来讲解查看

首先,也是最重要的一点,我们先构建一棵二叉查找树

(1)造树

 普及一个小知识,结构体内连接初始值操作方法如下

1 struct Node{//此结构体有两个构造函数 
2     int left,right,size,value,num;
3     Node(int l,int r,int s,int v):left(l),right(r),size(s),value(v),num(1){ }//构造初始值 
4     Node(){    }//无参默认构造函数 
5 }t[MAXN];

 

使用inline’关键字可以提高函数调用的效率,但它通常用于短小的函数,因为将函数内容嵌入到每个调用点可能会增加代码大小。在这里,它被用于、update’函数,因为这个函数的目的是在每个节点的更新中执行一些小的计算,内联它可以提高执行效率。

1 //左子树坐标下左子树大小 + 右子树坐标下右子树的大小 + 根节点的大小
2 inline void update(int root){
3     t[root].size =t[t[root].left ].size +t[t[root].right ].size +t[root].num;//不断将每一个点的大小加入根节点并汇总 
4     //更新节点信息 
5 }

汇总:所有的预备工作

1 struct Node{//此结构体有两个构造函数 
2     int left,right,size,value,num;
3     Node(int l,int r,int s,int v):left(l),right(r),size(s),value(v),num(1){ }//构造初始值 
4     Node(){    }//无参默认构造函数 
5 }t[MAXN];
6 inline void update(int root){
7     t[root].size =t[t[root].left ].size +t[t[root].right ].size +t[root].num;//不断将每一个点的大小加入根节点并汇总 
8     //更新节点信息 
9 }

 

其次,我们先看最常用的查询操作

(2)查询

1.查询数x的排名

因为我们知道二叉树的性质,左子树中数都小于根,右子树中数都大于根,所以我们可以在查询中判断x是否大于根,又或是小于根,那么就在右/左子树中递归查找(注意如果在右子树中查找时记得将左子树大小加入答案),直到根就是左子树,输出答案即可。

 1 int rank(int x,int root){//查找数x的排名
 2     if(root){//根中有叶子而不为0
 3         if(x<t[root].value)//如果此点小于根,则进入左子树查找 
 4             return rank(x,t[root].left);
 5         if(x>t[root].value)//如果此点大于根,则进入右子树查找 
 6             return rank(x,t[root].right+t[t[root].left ].size +t[root].num);
 7         return  t[root].num +t[t[root].left ].size ;//如果刚好查到其本身,则返回左子树与根的大小总和 
 8     } 
 9     return 1;//如果该树未有叶子,则直接返回值一 
10 }

2.查询排名为x的数

 此查找与上一段最显著的差别就是,这个是以排名查找树的值,而上一个是以树的值查找排名;

则此时则也有三种情况:

(1)x 这个排名比根节点排名小,则进入左子树

(2)x 这个排名比根节点排名大,则进入右子树,同时把 x 减去左子树的排名

(3)x 这个排名就是根节点排名,返回根节点的值

1 int kth(int x,int root){//查询排名为 x 的数
2     if(x<=t[t[root].left ].size )return kth(x,t[root].left );//排名为x的数在左子树,故进入左子树 
3     if(x<=t[t[root].left ].size +t[root].num )return t[root].value;//当前根节点为要找的数,直接返回 
4     return kth(x-t[t[root].left ].size -t[root].num ,t[root].right );
5     //排名为x的数在右子树,进入右子树并把x减去左子树 size+t[root].num(根节点).
6 }

 (3)插入

插入的规则很容易理解,首先判断插到哪棵子树,在递归寻找没有左或右子节点的树,新建后插入

记得每次插入后跟新节点总值

 1 //插入的规则很容易理解,首先判断插到哪棵子树,在递归寻找没有左或右子节点的树,新建后插入
 2 void insert(int x,int &root){
 3     if(x<t[root].value ){//是否在左子树中 
 4         if(!t[root].left)//是否有左子树 
 5             t[t[root].left=cnt++]=Node(0,0,1,x);//新建所需节点 
 6         else
 7             insert(x,t[root].left );//递归查询 
 8     }else if(x>t[root].value ){//是否在右子树中 
 9         if(!t[root].right ){//是否有右子树 
10             t[t[root].right =cnt++]=Node(0,0,1,x);//新建节点 
11         }else{
12             insert(x,t[root].right );//递归查询 
13         }
14     }else{//如果根节点就是这个数 
15         t[root].num ++;//根节点个数 
16     }
17     update(root); //更新此时节点信息 
18 } 

至此之上,便是这道题目主要考察的算法点了

 

posted @ 2023-09-17 11:57  晓屿  阅读(11)  评论(0)    收藏  举报