c++里的树

一、二叉树

树的定义

树的术语

种类(二叉树,树)

二叉树的定义

二叉树的基本形态

思考:n个节点能构成多少种不同形态的二叉树?

n个节点的二叉树,左子树i个节点,右字数n-i-1个节点

譬如:5个节点的二叉树 可以分成以下几大类

(1)左子树0个节点,右子树4个节点

(2)左子树1个节点,右子树3个节点

(3)左子树2个节点,右子树2个节点

(4)左子树3个节点,右子树4个节点

(5)左子树4个节点,右子树0个节点

设f(i) 表示i个节点构成二叉树的形态个数,那么5个节点的二叉树每类的个数为:

(1)左子树0个节点,右子树4个节点 的二叉树共f(0)*f(4)个;

(2)左子树1个节点,右子树3个节点 的二叉树共f(1)*f(3)个;

(3)左子树2个节点,右子树2个节点 的二叉树共f(2)*f(2)个;

(4)左子树3个节点,右子树1个节点 的二叉树共f(3)*f(1)个;

(5)左子树4个节点,右子树0个节点 的二叉树共f(4)*f(0)个;

总数就是

f(5)=f(0)*f(4)+f(1)*f(3)+f(2)*f(2)+f(3)*f(1)+f(4)*f(0)

f(0)=1

f(1)=1

f(n)=∑f(i)f(n-1-i) (0<=i<=n-1)

任务1:请同学们用在纸上推算f(2),f(3),f(4)

也可以用解析公式计算

f(n) = C(2n,n)/(n+1)

任务2:请同学们用解析公式计算f(2),f(3),f(4)

任务3:请同学们编程序完成 Jzoj

7、二叉树的性质

性质1:二叉树的第i层上至多有2i-1个结点

性质2:深度为k的二叉树最多有2k - 1个结点

性质3:如果度为0的结点数为n0,度为2的结点数为n2 ,那么 n0 = n2 +1

证明:

总结点数 n = n0 + n1 + n2 ①

孩子结点数 n -1 = n1 + 2*n2 ②

(提示:只有根结点不是别人家的孩子)

联立①式和②式,得 n0 = n2 + 1

性质4: n个结点的完全二叉树的深度为 ⌊log2n⌋+1

性质5:对 n 个结点的完全二叉树从上到下从左到右进行 1 到n 编号,则对任意编号为 i 的结点有如下性质:

(1)若 i = 1,则该结点是二叉树的根, 否则其父结点编号为 i/2 ;

(2)若 2*i >n,则该结点无左孩子, 否则其左孩子编号为 2*i ;

(3)若 2*i+1>n,则该结点无右孩子,否则其右孩子编号为2*i+1 。

三、二叉树的存储

顺序存储

非完全二叉树采用顺序存储需先补为完全二叉树

在某些特殊情况下,容易浪费存储空间。

2、链式存储

不记录父结点

struct Node{   
    ElementType data;   
    int lchild;   
    int rchild;}
T[MAXN];

记录双亲(父)结点

struct Node{
   ElementType data;
   int lchild;
   int rchild;
   int father;
}T[MAXN];

3、用链式存储建一颗树

输入样例:

8

A 2 3

B 4 5

C 6 -1

D -1 -1

E -1 -1

F 7 8

G -1 -1

H -1 -1

const int MAXN = 100 + 10;
struct Node{
       char data;
       int lchild, rchild;
}T[MAXN];

int n;
cin >> n;
for(int i = 1; i <= n; i++){
        cin >> T[i].data >> T[i].lchild >> T[i].rchild;
}

四、二叉树的遍历

先序遍历(先根遍历)(根左右)

(1)先访问根结点

(2)递归访问左子树

(3)递归访问右子树

void preorder(int root){
     if(root == -1) return;
     cout << T[root].data;
     preorder(T[root].lchild);
     preorder(T[root].rchild);
}

中序遍历(中根遍历)(左根右)

(1)递归访问左子树

(2)访问根结点

(3)递归访问右子树

void inorder(int root){
     if(root == -1) return;
     inorder(T[root].lchild);
     cout << T[root].data;
     inorder(T[root].rchild);
}

后序遍历(后根遍历)(左右根)

(1)递归访问左子树

(2)递归访问右子树

(3)访问根结点

void postorder(int root){
     if(root == -1) return;
     postorder(T[root].lchild);
     postorder(T[root].rchild);
     cout << T[root].data;
}

例题1:FBI树

描述:我们可以把由0和1组成的字符串分为三类:

(1)全0串称为B串

(2)全1串称为I串

(3) 既含0又含1的串则称为F串。

FBI树是一棵二叉树,它的结点类型包括F结点,B结点和I结点三种。

由一个长度为2^N(0<=N<=10)的01串S可以构造出一棵FBI树T,递归的构造方法如下:(1)T的根结点为R,其类型与串S的类型相同;(2)若串S的长度大于1,将串S从中间分开,分为等长的左右子串S1和S2;由左子串S1构造R的左子树T1,由右子串S2构造R的右子树T2。

请用上述构造方法构造一棵FBI树,并输出它的后序遍历序列。

输入样例:

3

10001011

输出样例:

IBFBBBFIBFIIIFF

分析:

方法1:直接递归处理


#include
using namespace std;
string gen;
int n;
string code(int l,int r){
    if(l==r){
        if(gen[l]=='0') return "B";
        else return "I";
    }
    
    int mid=(l+r)/2;
    string x1=code(l,mid);
    string x2=code(mid+1,r);
        
    int l2=x2.size();
    if(x1==x2)   //左子树的编码和右子树的编码相同,那么根的编码和子树的根编码相同。 
        return x1+x2+x2[l2-1];// 后序遍历的编码等于左子树编码连接右子树编码再连接根的编码 (也可以写成 x1+x2+x1[l2-1];) 
        
    else   //左子树的编码和右子树的编码不同,那么根的编码一定是“F”。
        return x1+x2+"F";
        
}

int main(){
    cin>>n>>gen;
    cout<

方法2:

#include
#include
using namespace std;
int n,sum=1;
bool a[2000];
char cnt[1025][1025];
int dfs(int l,int r)
{    
    if (l==r)
    {
        if (a[l]==0) cnt[l][r]='B'; else cnt[l][r]='I';
        cout<>1;
    dfs(l,mid);
    dfs(mid+1,r);
    if (cnt[l][mid]==cnt[mid+1][r] ) 
        cnt[l][r]=cnt[l][mid];
    else cnt[l][r]='F';
    
    cout<>n;
    for (int i=1;i<=n;i++) sum*=2;
    for (int i=1;i<=sum;i++)
        scanf("%1d",&a[i]);
    dfs(1,sum);
//    for (int i=1;i<=t;i++) cout<

求先序序列

给出一棵二叉树的中序与后序序列,输出其先序序列。(约定树结点用不同的大写字母表示,长度≤8)。

先序遍历 ABCD

中序遍历 BADC

后序遍历 BDCA

已知其中一个遍历能否确定一颗树?

已知其中两个遍历能否确定一棵树?

字符串处理常用函数

s=ss.substr(1,3); // 提取函数,提取ss字符串从1位置起的3个字符给s字符串
s=ss.substr(3); // 提取函数,提取ss字符串从3位置起的所有内容给s字符串
s=ss.substr(); // 提取函数,提取ss字符串的所有内容给s字符串
len=ss.size(); // 返回字符串字符个数
len=ss.length(); // 返回字符串字符个数
pos=ss.find(c,1); // 从前面1位置开始查找,查找c字符串最早出现在ss字符串的位置,若没找到,则返回string::npos
pos=ss.find(c);    // 从前面0位置开始查找,查找c字符串最早出现在ss字符串的位置,若没找到,则返回string::npos

#include 
using namespace std;

void solve(string inorder,string postorder){
    int len=postorder.size();
    if(len>0){
        char ch=postorder[len-1]; //根节点
        cout<>inorder>>postorder;
    solve(inorder,postorder);
    return 0;
}

例题:对称二叉树

描述:一棵有点权的有根树如果满足以下两个条件,则称为对称(或镜像)二叉树。

(1)二叉树;

(2)将这棵树所有结点的左右子树交换,新树和原树对应位置的结构相同且点权相等。

现在给出一棵二叉树,希望找出它的一棵子树,该子树为对称二叉树,且结点数最多。请输出这棵子树的结点数。

约定:

(1)规定节点编号 1~n,其中节点1 是树根。

(2)只有根结点的树也是对称二叉树。

(3) 以结点 T 为根的一棵“子树”指的是:结点 T 和它的全部后代结点构成的二叉树。

第一行一个正整数 n,表示给定的树的节点数目

第二行 n 个正整数,用一个空格分隔,第 i 个正整数 vi 代表节点 i 的权值。

接下来 n 行,每行两个正整数 li , ri ,分别表示节点 i 的左右孩子的编号。如果不存在左 / 右孩子,则以 −1 表示。两个数之间用一个空格隔开。

分析

枚举每一个结点作为根结点进行dfs,判断以其为根的子树是否对称(结构对称 + 权值对称)

# include 
using namespace std;
/*
//n
//权值
// 后面有n行,第i行表示ID号为i的左右孩子编号 -1表示没孩子 
输入
10  
2 2 5 5 5 5 4 4 2 3
9 10
-1 -1
-1 -1
-1 -1
-1 -1
-1 2
3 4
5 6
-1 -1
7 8
输出
7 3 


11  
2 2 5 5 5 5 4 4 2 3 2
9 10
-1 -1
11 -1
-1 -1
-1 -1
-1 2
3 4
5 6
-1 -1
7 8
-1 -1

输出
10 9 

*/ 
# include 
using namespace std;
const int maxN=1e6+10;
int v[maxN],c[maxN],ans;
struct node{
    int l,r;
    int data;
}T[maxN];

bool same(int a, int b) //比较以a,b为根两个子树是否对称(a为左子树,b为右子树) 
{
    if(a == b) return 1; 
    if(a==-1 || b==-1) return 0;
    if (T[a].data!=T[b].data) return 0;
    if (!(same(T[a].l, T[b].r))) return 0;
    if (!(same(T[a].r, T[b].l))) return 0;
    return 1;
}

void dfs(int i) {
    if(i==-1) return; //没有孩子 
    dfs(T[i].l); //遍历左子树
    dfs(T[i].r); //遍历右子树
    c[i] = 1 + c[T[i].l] + c[T[i].r];
    if(ans < c[i] && same(T[i].l, T[i].r)  ) //如果左右孩子相等 打擂台取最大
        {  ans = c[i];}
}

int main(){    
    int n; cin>>n;
    for(int i=1; i<=n; ++i) cin>>v[i];//读权值     
    for(int i = 1; i <= n; ++i) {
         cin>>T[i].l;    
         cin>>T[i].r;    
         T[i].data=v[i];
    }    
    dfs(1);
    cout<

加分二叉树

设一个n个节点的二叉树tree的中序遍历为(l,2,3,...,n),其中数字1,2,3,...,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:

subtree的左子树的加分×subtree的右子树的加分+subtree的根的分数

若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。

试求一棵符合中序遍历为(1,2,3,...,n)且加分最高的二叉树tree。要求输出:

(1)tree的最高加分

(2)tree的前序遍历

输入

第1行:一个整数n(n<30),为节点个数。

第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。

输出

第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。

第2行:n个用空格隔开的整数,为该树的前序遍历。

输入样例

5

5 7 1 2 10

输出样例

145

3 1 2 4 5

分析:

(1)如何计算一个节点的加分?请问下面这个二叉树加分是多少?f=subtree的左子树的加分×subtree的右子树的加分+subtree的根的分数

圈内数字是节点编号,圈外数字数字是分数

f[i] 表示 i为根的子树的加分

f[1]=5 f[3]=1 f[5]=10

f[2]=f[1]*f[3]+7=5*1+7=12

f[4]=f[2]*f[5]+2=12*10+2=122

圈内数字是节点编号,圈外数字数字是分数

f[2]=7 f[5]=10

f[1]=1*f[2]+5=12

f[4]=1*f[5]+2=1*10+2=12

f[3]=f[1]*f[4]+1=12*12+1=145

(2)只知道中序遍历能否确定这个树的形态?

1到n个节点中的任意一个节点都可以是根

设节点k是根,则左子树包含的节点是1到k-1,右子树包含的节点是k+1到n

树的形态有很多种,总共C(2n,n)/(n+1) 种

不同形态的树,加分不同,求那个最大的。

设f[i][j]表示i到j 这些节点构成的子树的最大加分

那么要求的值就是f[1][n]

f[1][n]=max{f[1][k-1]*f[k+1][n]+f[k][k]}(1<=k<=n)

f[i][j]=max{f[i][k-1]*f[k+1][j]+f[k][k]}(i<=k<=j)

f[i][i]=a[i]

f[i][i-1]=1

方法1:记忆化搜索

#include
#include
#include
using namespace std;
const int MAXN = 50;
typedef long long ll;
ll n;
ll f[MAXN][MAXN]; //记录加分,f[i][j]表示i到j 这些节点构成的子树的最大加分 
ll root[MAXN][MAXN];//记录根,root[i][j] 表示i到j 这些节点构成的子树得最大加分最大加分时的根 

void print(ll l, ll r) {
    if (l > r)return;
    printf("%lld ", root[l][r]);
    if (l == r)return;
    print(l, root[l][r] - 1);
    print(root[l][r]+1,r);
}

ll dfs(int i,int j){
    if(f[i][j]) return f[i][j];   //记忆化搜索 
    if(i>j) return f[i][j]=1;     //空树加分为1
     
    ll ans=0;
    for(int k=i;k<=j;k++) { 
        ll temp=dfs(i,k-1)*dfs(k+1,j)+f[k][k];
        if(ans

方法二:递推

将相邻的任意个节点构成的子树的最大加分算出来

第一阶段计算 相邻的1个节点构成的子树最大加分 (可以在初始化时完成)

分别是f[1][1],f[2][2],f[3][3],f[4][4],f[5][5],……,f[n][n]。

第二阶段计算 相邻的2个节点构成的子树最大加分

分别是f[1][2],f[2][3],f[3][4],f[4][5],f[5][6],……,f[n-1][n]。

第三阶段计算 相邻的3个节点构成的子树最大加分

分别是f[1][3],f[2][4],f[3][5],f[4][6],f[5][7],……,f[n-2][n]。

第四阶段计算 相邻的4个节点构成的子树最大加分

分别是f[1][4],f[2][5],f[3][6],f[4][7],f[5][8],……,f[n-3][n]。

……

第n-1阶段计算 相邻的2个节点构成的子树最大加分

分别是f[1][n-2],f[2][n]。

第n阶段计算 相邻的2个节点构成的子树最大加分

分别是f[1][n]。

#include
#include
#include
using namespace std;
const int MAXN = 50;
typedef long long ll;
ll n;
ll f[MAXN][MAXN]; //记录加分,f[i][j]表示i到j 这些节点构成的子树的最大加分 
ll root[MAXN][MAXN];//记录根,root[i][j] 表示i到j 这些节点构成的子树得最大加分最大加分时的根 

void print(ll l, ll r) {
    if (l > r)return;
    printf("%lld ", root[l][r]);
    if (l == r)return;
    print(l, root[l][r] - 1);
    print(root[l][r]+1,r);
}

int main() {
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++){
        scanf("%lld", &f[i][i]);//i点单独构成子树(i为叶节点时)的加分就是节点本来的分数 
        f[i][i-1]=1;            //空树加分为1  
        root[i][i] = i;         // i点单独构成子树,根就是自己。 
    }
    f[n+1][n]=1;
    
    for (int len = 1; len < n; ++len) //穷举区域长度(相邻节点个数) 
    {
        for (int i = 1; i + len <= n; ++i) //穷举区域的左边界 
        {
            int j = i + len;                //计算 区域的右边界 
            
            f[i][j] = f[i + 1][j] + f[i][i]; //预设i(左边第一个节点)是根,左子树为空 
            root[i][j] = i;
            printf("计算f[%d][%d]=max{",i,j);
            
            for (int k = i + 1; k <= j; k++) //穷举根k 
            {
                printf("f[%d][%d]-%d-f[%d][%d],",i,k-1,k,k+1,j);
                if (f[i][j] < f[i][k - 1] * f[k + 1][j] + f[k][k]) {
                    f[i][j] = f[i][k - 1] * f[k + 1][j] + f[k][k];
                    root[i][j] = k;
                }
            }
            cout<<"}"<
5
5 7 1 2 10
计算f[1][2]=max{f[1][1]-2-f[3][2],}
计算f[2][3]=max{f[2][2]-3-f[4][3],}
计算f[3][4]=max{f[3][3]-4-f[5][4],}
计算f[4][5]=max{f[4][4]-5-f[6][5],}

计算f[1][3]=max{f[1][1]-2-f[3][3],f[1][2]-3-f[4][3],}
计算f[2][4]=max{f[2][2]-3-f[4][4],f[2][3]-4-f[5][4],}
计算f[3][5]=max{f[3][3]-4-f[5][5],f[3][4]-5-f[6][5],}

计算f[1][4]=max{f[1][1]-2-f[3][4],f[1][2]-3-f[4][4],f[1][3]-4-f[5][4],}
计算f[2][5]=max{f[2][2]-3-f[4][5],f[2][3]-4-f[5][5],f[2][4]-5-f[6][5],}

计算f[1][5]=max{f[1][1]-2-f[3][5],f[1][2]-3-f[4][5],f[1][3]-4-f[5][5],f[1][4]-5-f[6][5],}

145
3 1 2 4 5
--------------------------------
Process exited with return value 0
Press any key to continue . . .

二:树

儿子个数可能不止两个

存储

这时候没有左右孩子的说法了。

常常使用邻接表方法存储,在静态数组T中i位置存储i的每个孩子编号。

由于每个节点的孩子数量不一定相同,所以这里一般不用二维数组存储。

用元素类型是动态数组的一维静态数组T存储。动态数组我们有称它为向量。

以下代码查看动态数组的常用方法。

#include 
using namespace std;

int n;            //定义一个整型变量n
int a[8];         //定义一个静态数组a,数组长度是8;a中元素类型为整型
vectorD;     //定义一个动态数组D,数组长度不定;D中元素类型为整型 
int main()
{
     //动态数组元素的添加
     D.push_back(4);
     D.push_back(41);
     D.push_back(2);
     D.push_back(14);
     
     //动态数组元素也是从零开始编号的
     cout<

T数组的长度就是最大节点编号

/*将以下树边存入静态数组T,
     第一行表示节点个数n(节点编号是从1到n)
     下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。 
     
     8
     1 2
     1 3
     1 4
     2 7
     3 5
     3 6
     3 8     
*/ 
#include 
using namespace std;
int n;            //定义一个整型变量n
vectorT[8];  //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型 

int main()
{
     cin>>n;
     for(int i=1;i>a>>b;
         T[a].push_back(b);
     }
     
     //输出T数组中的内容看看
     for(int i=1;i<=n;i++) {
         cout<<"节点"<

树的先序遍历

先序遍历 1 2 7 3 5 6 8 4

 /*
     第一行表示节点个数n(节点编号是从1到n)
     下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。 
     
     8
     1 2
     1 3
     1 4
     2 7
     3 5
     3 6
     3 8     
*/

#include 
using namespace std;

int n;            //定义一个整型变量n
vectorT[8];  //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型 

void dfs(int p){
    cout<>n;
     for(int i=1;i>a>>b;
         T[a].push_back(b);
     }     
     dfs(1);
    return 0;
}


1 2 7 3 5 6 8 4
--------------------------------
Process exited with return value 0
Press any key to continue . . .

3、树的层次遍历

这里需要一个队列Q:队列用来维护一个先进先出的线性表

queueQ;

queueQ;  //定义个队列Q,元素类型为int
Q.push(p);//将p加入队尾 
f=Q.empty()//判断队列Q是否空,空则返回true,不空则返回false
h=Q.front();//取Q队头元素
Q.pop();//弹(删)队头元素
/*
     第一行表示节点个数n(节点编号是从1到n)
     下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。 
     
     8
     1 2
     1 3
     1 4
     2 7
     3 5
     3 6
     3 8     
*/

#include 
using namespace std;

int n;            //定义一个整型变量n
vectorT[8];  //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型 

void bfs(int p){
    queueQ; 
    Q.push(p);//起点入队     
    while (!Q.empty()){        
        int h=Q.front();//取队头 
        Q.pop();//弹(删)队头             
        cout<>n;
     for(int i=1;i>a>>b;
         T[a].push_back(b);
     }     
     bfs(1);
    return 0;
    
   // 1 2 3 4 7 5 6 8
}

求树的深度

求一棵树的深度,根节点为1,根节点深度为1

a)bfs方法

队列中记录节点编号的同时,记录这个节点的深度。用结构体。

/*
     第一行表示节点个数n(节点编号是从1到n)
     下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。 
     
     8
     1 2
     1 3
     1 4
     2 7
     3 5
     3 6
     3 8     
*/

#include 
using namespace std;

int n,ans;            //定义一个整型变量n
vectorT[8];  //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型 
struct node{
    int bh,sd;
};

void bfs(int p,int sd){
    queueQ; 
    Q.push({p,sd});//起点入队 
    
    while (!Q.empty()){
        
        int bh=Q.front().bh;//取队头
        int sd=Q.front().sd;//取队头
        Q.pop();//弹(删)队头 
        ans=sd;//记录最大深度
         
        //将bh的孩子入队 
        for(int j=0;j>n;
     for(int i=1;i>a>>b;
         T[a].push_back(b);
     }
     bfs(1,1);
     cout<

b)用DFS

/*
     第一行表示节点个数n(节点编号是从1到n)
     下面n-1行的每一对数字表示一对父子关系。前一个是父,后一个是子。 
     
     8
     1 2
     1 3
     1 4
     2 7
     3 5
     3 6
     3 8     
*/

#include 
using namespace std;

int n,ans;            //定义一个整型变量n
vectorT[8];  //定义一个静态数组T,数组长度是8;T中元素类型为动态数组类型,动态数组中的元素为整型 

void dfs(int p,int sd){
    ans=max(ans,sd);
    for(int j=0;j>n;
     for(int i=1;i>a>>b;
         T[a].push_back(b);
     }
     dfs(1,1);
     cout<

你可能感兴趣的:(c++,c++)