数据结构:二叉树oj练习

在讲今天的题目之前,我们还需要讲一下二叉树的以下特点:

对任意一颗二叉树,如果度为0的节点个数是n0,度为2的节点个数是n2,则有n0=n2+1.

证明:二叉树总的节点个数是n,那么有n=n0+n1+n2

二叉树的度为n-1=n1*1+n2*2

结合上面两个式子,就有:n0=n2+1

完全二叉树中,度为1的结点数只有0或1两种可能

说明:因为完全二叉树的结点是连续编号的,最后一层的结点要么是满的,要么缺少右边的若干结点,所以度为1的结点最多有1个。

选择题

题目1

某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )

A 不存在这样的二叉树

B 200

C 198

D 199

解释:叶子结点的个数等于度为2的节点的个数+1,所以结果是200

题目2

在具有 2n 个结点的完全二叉树中,叶子结点个数为( )

A n

B n + 1

C n - 1

D n / 2

度为2的节点个数=度为0的节点个数-1,即n2=n0-1

由于是完全二叉树,度为1的节点个数只有0或1两种可能。

如果度为1的节点个数为1,那么1+n0+n0-1=2n,就能得到n0=n,即A可选

如果度为1的节点个数为0,那么n0+n0-1=2n,就会得到n0=(2n+1)/ 2,这是一个小数,所以不合理。

综上,这道题的答案选A。

题目 3

一棵完全二叉树的结点数为 531 个,那么这棵树的高度为( )

A 11

B 10

C 8

D 12

我们知道,二叉树第k层节点个数为2^(k-1),前k层节点个数为2^(k)-1

假设完全二叉树的高度为h,那么完全二叉树节点个数的范围是: 2^(h-1)再到2^(h)-1.

这个范围是咋来的呢?首先第一个范围,既然一共有h层,那么根据公式,前h-1层就一共有2^(h-1)-1个节点,而第h层最少有1各节点,所以前h层应该至少是有2^(h-1)个节点。当这个树为满二叉树时,节点个数达到最大,前h层节点个数一共是:2^(h)-1。

结合这个范围,我们就可以求得答案是10,即选B

题目 4

一个具有 767 个结点的完全二叉树,其叶子结点个数为( )

A 383

B 384

C 385

D 386

假设节点个数是n,那么结合结论,n=2*n0-1+n1,我们知道,完全二叉树的节点个数要么是0,要么是1,带入公式中我们可以得到,n0=384,此时n1=0

题目 5

二叉树的先序遍历和中序遍历如下:前序遍历:EFHIGJK;中序遍历:HFIEJKG。则二叉树后续遍历序列:()

如果我们知道前序遍历+中序遍历 或者 中序遍历+后序遍历,就可以推导出二叉树的结构。

题目 6

设一棵二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为____。 A. adbce

B. decab

C. debac

D. abcde

编程题

144. 二叉树的前序遍历 - 力扣(LeetCode)

这道题就是简单的前序遍历,我们之前在二叉树的实现方法中已经写过了,只需要将代码稍加修改就可以咯:

复制代码
 void preorder(struct TreeNode* root,int*res, int* returnSize)
 {
    if(root==NULL)
    {
        return ;
    }
    //先存根节点
   res[(*returnSize)++]=root->val;
   preorder(root->left,res,returnSize);
   preorder(root->right,res,returnSize);
 }
int* preorderTraversal(struct TreeNode* root, int* returnSize) 
{
    *returnSize=0;
    //树中节点的数目在100内
    int*res=(int*)malloc(sizeof(int)*100);
    //前序遍历
    preorder(root,res,returnSize);
    return res;
}

145. 二叉树的后序遍历 - 力扣(LeetCode)

复制代码
void postorder(struct TreeNode* root, int* res, int* returnSize)
{
    if (root == NULL)
    {
        return;
    }

    postorder(root->left, res, returnSize);
    postorder(root->right, res, returnSize);
    res[(*returnSize)++] = root->val;
}

int* postorderTraversal(struct TreeNode* root, int* returnSize) {
    *returnSize = 0;
    //树中节点的数目在100内
    int* res = (int*)malloc(sizeof(int) * 100);
    //后序遍历
    postorder(root, res, returnSize);
    return res;
}

94. 二叉树的中序遍历 - 力扣(LeetCode)

复制代码
 void inorder(struct TreeNode* root, int* res, int* returnSize)
{
    if (root == NULL)
    {
        return;
    }

    inorder(root->left, res, returnSize);
    res[(*returnSize)++] = root->val;
    inorder(root->right, res, returnSize);
    
}


int* inorderTraversal(struct TreeNode* root, int* returnSize) {
        *returnSize = 0;
    //树中节点的数目在100内
    int* res = (int*)malloc(sizeof(int) * 100);
    //中序遍历
    inorder(root, res, returnSize);
    return res;
    
}

965. 单值二叉树 - 力扣(LeetCode)

这一道题的简单思路也是递归,如果一棵树是单值树,那么他的子树也是单值树,那我们就可以把大问题拆分成若干个子问题了:

复制代码
bool isUnivalTree(struct TreeNode* root) 
{
    if(root==NULL)
    {
        return true;
    }
    if(root->left)
    {
        if(root->left->val!=root->val)
        {
            return false;
        }
    }
    if(root->right)
    {
        if(root->right->val!=root->val)
        {
            return false;
        }
    }
    return  isUnivalTree(root->left) &&  isUnivalTree(root->right);
}

我们可以模拟一下递归过程:

100. 相同的树 - 力扣(LeetCode)

这道题的思路也是利用递归,如果两颗树的根节点相同,那就只需要再比较两颗树的左右子树是否相同即可:

复制代码
bool isSameTree(struct TreeNode* p, struct TreeNode* q) 
{
    if(p==NULL&&q==NULL)
    {
        return true;
    }
    if(p==NULL)
    {
        return false;
    }
    if(q==NULL)
    {
        return false;
    }
    if(p->val!=q->val)
    {
        return false;
    }
    //表示根节点不为空且指向的值相等
    return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}

我们可以模拟一下递归过程:

101. 对称二叉树 - 力扣(LeetCode)

如图,判断一个树是否是对称二叉树,就是要比较根节点的左右子树,我们可以把根节点的左右子树看成两棵独立的二叉树,问题就转换为比较这两棵树是否对称。

两棵树是否互为对称树,就是比较树A上某个节点的左孩子节点的值与树B上对应节点的右孩子节点的值以及树A上某个节点的右孩子节点的值与树B上对应节点的左孩子节点的值是否相等,如果相等的话,就继续递归比较孩子节点的值。

复制代码
 bool isSameTree(struct TreeNode*p,struct TreeNode*q)
 {
    if(p==NULL&&q==NULL)
    {
        return true;
    }
    if(p==NULL||q==NULL)
    {
        return false;
    }
    if(p->val!=q->val)
    {
        return false;
    }
    //两棵树根节点相同,再比较树A的左孩子与树B的右孩子是否相同,以及树A的右孩子与树B的左孩子是否相同
    return isSameTree(p->left,q->right)&&isSameTree(p->right,q->left);
 }
bool isSymmetric(struct TreeNode* root)
{
  return  isSameTree(root->left,root->right);
}

模拟递归过程:

572. 另一棵树的子树 - 力扣(LeetCode)

这一体的思路比较好想,我们可以先比较subroot是否与整棵树相同,如果相同,那就可以直接返回了,如果不相同,说明subroot可能是树的子树,就需要遍历整棵树的节点,看是否存在以某一个节点为根的树与subroot相同:

复制代码
bool isSameTree(struct TreeNode* p, struct TreeNode* q) 
{
    if(p==NULL&&q==NULL)
    {
        return true;
    }
    if(p==NULL)
    {
        return false;
    }
    if(q==NULL)
    {
        return false;
    }
    if(p->val!=q->val)
    {
        return false;
    }
    //表示根节点不为空且指向的值相等
    return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) 
{
    
    if(subRoot==NULL)
    {
        return true;
    }
    if(root==NULL)
    {
        return false;
    }
    if(isSameTree(root,subRoot))
    {
        return true;
    }
    return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}

二叉树遍历_牛客题霸_牛客网

我们之前说过,如果知道前序遍历+中序遍历 或者 中序遍历+后序遍历,就可以推导出二叉树的结构。但是现在题目中只给了我们前序遍历的结果,我们还能根据这个去构建二叉树的结构吗?

在这一题中是可以的,因为题中给我们的字符串是带有'#'的,这表示空指针,前序遍历的顺序是按照根节点->左子树->右子树,当我们遇到'#'的时候,就无法继续往下遍历,而是要回到原来子树的根,这没说这可能有点抽象,我们利用题目中给的测试样例来手动还原二叉树的结构:

利用遍历结果构建二叉树听起来好像挺难的,但其实就是对二叉树进行遍历,我们在写前序遍历的代码的时候,将访问二叉树的节点的操作写成了打印二叉树的节点值,在这里,无非就是让我们在前序遍历过程中将访问二叉树的节点的操作写成了创建节点呀,具体思路还是没有变呀,那我们就继续写一下代码吧:

复制代码
//二叉树的构建与遍历
#include<stdio.h>
#include<stdlib.h>
//二叉树的节点的定义
typedef struct btnode {
    char data;
    struct btnode* left;
    struct btnode* right;
} btnode;

btnode* buynode(char x) {
    btnode* newnode = (btnode*)malloc(sizeof(btnode));
    newnode->data = x;
    newnode->left = newnode->right = NULL;
    return newnode;
}

//最后返回根节点
//pi表示指向的字符在字符数组中的下标,由于形参的改变需要影响实参,所以我们这里采用传址调用
btnode* precreattree(char* ch, int* pi) {
    if (ch[*pi] == '#') {
        (*pi)++;
        return NULL;
    }
    //先创建根节点
    btnode* root = buynode(ch[(*pi)++]);
    //再创建左子树
    root->left = precreattree(ch, pi);
    //创建右子树
    root->right = precreattree(ch, pi);
    //返回根节点
    return root;
}
//中序遍历树的节点
void inorder(btnode* root) {
    if (root == NULL) {
        return;
    }
    //先遍历左子树
    inorder(root->left);
    //再访问根节点
    printf("%c ", root->data);
    //再遍历右子树
    inorder(root->right);
}
int main() {
    char ch[100];
    //输入字符串
    scanf("%s", ch);
    //输入的字符串是二叉树前序遍历的结果,我们需要根据这个结果去创建二叉树
    int i = 0;
    btnode* root = precreattree(ch, &i);
    //中序遍历
    inorder(root);
    return 0;
}

今天的内容还是比较丰富的,这些算法题也是比较经典的,能看到这里的兄弟们我真的觉得你们很棒,大家在自己的电脑上也要好好把代码敲一下,把图画一下,把思路整理一下哦!!

相关推荐
Coovally AI模型快速验证2 小时前
农田扫描提速37%!基于检测置信度的无人机“智能抽查”路径规划,Coovally一键加速模型落地
深度学习·算法·yolo·计算机视觉·transformer·无人机
RaymondZhao343 小时前
【全面推导】策略梯度算法:公式、偏差方差与进化
人工智能·深度学习·算法·机器学习·chatgpt
zhangfeng11333 小时前
DBSCAN算法详解和参数优化,基于密度的空间聚类算法,特别擅长处理不规则形状的聚类和噪声数据
算法·机器学习·聚类
啊阿狸不会拉杆4 小时前
《算法导论》第 32 章 - 字符串匹配
开发语言·c++·算法
小学生的信奥之路4 小时前
洛谷P3817题解:贪心算法解决糖果分配问题
c++·算法·贪心算法
曙曙学编程5 小时前
stm32——GPIO
c语言·c++·stm32·单片机·嵌入式硬件
你知道网上冲浪吗5 小时前
【原创理论】Stochastic Coupled Dyadic System (SCDS):一个用于两性关系动力学建模的随机耦合系统框架
python·算法·数学建模·数值分析
地平线开发者6 小时前
征程 6 | PTQ 精度调优辅助代码,总有你用得上的
算法·自动驾驶
Tisfy7 小时前
LeetCode 837.新 21 点:动态规划+滑动窗口
数学·算法·leetcode·动态规划·dp·滑动窗口·概率