二叉树题目

二叉树题目

1、选择题

1.1、二叉树的一点性质

二叉树有这样一条性质:
度为 0 的节点个数 = 度为 2 的节点个数 + 1 度为0的节点个数=度为2的节点个数+1 度为0的节点个数=度为2的节点个数+1这是怎么得到的?

我们在前面对于树的简单介绍中提到,树的节点个数与边数有这样一种关系:
树的边数 = 树的节点个数 − 1 树的边数=树的节点个数-1 树的边数=树的节点个数−1这很好理解,因为两个节点中间连接一条边。

我们假设,度为0节点的个数为n0,度为1节点的个数为n1,度为2节点的个数为n2

那么边数可以表示为:
边数 = n 0 + n 1 + n 2 − 1 边数=n_0+n_1+n_2-1 边数=n0+n1+n2−1我们再来看一棵二叉树:

不难看出,度为0的节点,其下面有0条边;度为1的节点,其下面有1条边;度为2的节点,其下面有2条边。

所以边数又可以表示为:
边数 = 2 ∗ n 2 + n 1 边数=2*n_2+n_1 边数=2∗n2+n1联立上面边数与n0、n1、n2的关系式,得到:
n 0 = n 2 + 1 n_0=n_2+1 n0=n2+1得证。

1.2、一些例题

这道题中,我们假设度分别为0、1、2的节点个数分别为:
n 0 、 n 1 、 n 2 n_0、n_1、n_2 n0、n1、n2那么 2 n = n 0 + n 1 + n 2 . . . . . . ① 2n=n_0+n_1+n_2......① 2n=n0+n1+n2......①我们在前面知道 n0 与 n2 的关系:
n 0 = n 2 + 1...... ② n_0=n_2+1......② n0=n2+1......②联立①②,得:
2 n = 2 n 0 + n 1 − 1 2n=2n_0+n_1-1 2n=2n0+n1−1那么关键来了, n1 是多少?

我们知道,在一个有k 层的完全二叉树中,第k层都是叶子节点,前k-2层中所有节点度为2。

对于k-1 层中的节点,如果第k 层的节点个数为偶数,假设有 2x 个,意味着在第k-1层中,有 x 个节点度为2,剩下 (2^(k-2)-x)个节点度为0。也就是说,当第k层的节点个数为偶数,即整个完全二叉树节点个数为奇数时,整个完全二叉树没有度为1的节点。

然而,对于k-1 层中的节点,如果第k 层的节点个数为奇数,意味着在第k-1层中,有且只有一个节点,它的度为1。也就是说,当第k层的节点个数为奇数,即整个完全二叉树节点个数为偶数时,整个完全二叉树有且只有1个度为1的节点。

在这道题中,完全二叉树结点的个数为偶数,所以 n1 == 1 。那么n0==n,即叶子节点有n个。

题中提到了完全二叉树,而我们知道满二叉树中,总结点个数n与树的深度h存在关系:
n = 2 k − 1 n=2^k-1 n=2k−1

所以,一个完全二叉树节点的个数,应该是介于,深度比其小一层的满二叉树(节点个数),和深度相同的满二叉树(节点个数),之间的。

那么可以做一个表:

深度 对应满二叉树的节点个数
8 255
9 511
10 1023

不难得出,这棵树的高度是10。

设⼀课二叉树的中序遍历序列: b a d c e ,后序遍历序列: b d e c a ,求二叉树前序遍历序列 设⼀课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,求二叉树前序遍历序列 设⼀课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,求二叉树前序遍历序列

在这道题中,我们根据后序遍历序列可以知道,二叉树的根节点为a。

那么,我们回到中序遍历序列,以a为界,b是a的左子树(部分),dce是a的右子树(部分)。

对于c、d、e,中序遍历序列为dce(左根右),后序遍历序列为dec(左右根),所以,d、e分别为c的左、右孩子。

那么树为:

前序遍历序列: a − > b − > c − > d − > e a->b->c->d->e a−>b−>c−>d−>e

我们看到后序遍历序列,F在末尾,所以F为根节点。

那么在中序遍历序列中,F只有左子树ABCDE,没有右子树。

同理,在后序序列ABCDE 中,E在末尾,E只有左子树ABCD

在后序序列ABCD 中,D在末尾,D只有左子树ABC

...

那么,树为:

层序序列: F − > E − > D − > C − > B − > A F->E->D->C->B->A F−>E−>D−>C−>B−>A

2、编程题

2.1、单值二叉树

题目链接



思路是利用递归:

  1. 判断当前节点是否为空,如果为空,返回true
  2. 判断当前节点的左孩子是否存在 ,以及值是否与父节点的不相等
  3. 判断当前节点的右孩子是否存在 ,以及值是否与父节点的不相等
  4. 若能走到这一步,递归到左、右子树
c 复制代码
//等号的传递性
//为空,返回真
//根节点与左右子树比较
//如果左右子树存在且与根节点不同,就不是
//继续找左右子树的

bool isUnivalTree(struct TreeNode* root) {
    //判空
    if (root == NULL)
    {
        return true;
    }
    //左孩子
    if (root->left && root->left->val != root->val)
    {
        return false;
    }
    //右孩子
    if (root->right && root->right->val != root->val)
    {
        return false;
    }
    //            到左子树                 到右子树
    return isUnivalTree(root->left) && isUnivalTree(root->right);
}

可以将每一个节点看做一个函数栈帧,然后画图走一遍看看。

2.2、相同的树

题目链接



在示例中,我们可以看出,两个二叉树是否相同,除了看节点存储的 是否相同,还要看树的结构是否相同,即会不会出现一个树当前位置有节点,而另一个树当前位置无节点的情况。

所以思路:

  1. 判断当前两个节点是否为空,若为空,说明一致,返回真
  2. 判断当前两个节点是否一个空,一个非空,若是,返回假
  3. 走到这一步,说明结构相同,则比较存储的值
  4. 走到这,当前位置相同,递归到左、右子树
c 复制代码
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;
    }

    //走到这,说明目前相同,递归
    return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

2.3、对称二叉树

对称二叉树

这里我们对上面这棵对称二叉树进行分析。

我们不难知道,分析的其实是二叉树根节点的左、右子树是否对称。此时我们很容易想到利用上面相同的树 代码实现,只不过需要注意,当左子树节点递归到其左孩子时,右子树节点就要递归到其右孩子。

c 复制代码
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;
    }
    //走到这,说明当前节点没问题,则进入左子树、右子树
    return isSameTree(p->left, q->right) && isSameTree(p->right, q->left);
}

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

2.4、另一棵树的子树

另一棵树的子树


根据题目看两个示例。第一个示例中,根节点的左子树恰好与另一个树相同,所以返回true

但在第二个示例,尽管根节点的左子树与另一个子树非常相似,但是根节点左子树2节点上多了一个0节点,这是不同的,所以返回false

根据以上分析,我们可以推理得知,需要另外构建一个判断两树是否相等的函数,这在之前已经实现过了。

继续分析,当,当前节点为空,也就不存在子树了,返回false

当,当前节点满足要求(两树是否相等函数返回true ),则返回true

走到这一步,则继续比较左、右子树与另一个树。

c 复制代码
typedef struct TreeNode TreeNode;
bool isSametree(TreeNode* p, TreeNode* q)
{
    //判断是否都为空
    if (p == NULL && q == NULL)
    {
        return true;
    }
    //判断结构是否相同
    if (p == NULL || 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 (root == NULL)
    {
        return false;
    }
    //当当前节点之下的树与另一树相同,返回真
    if (isSametree(root, subRoot))
    {
        return true;
    }

    //到这里,说明当前节点以下的树不满足要求,找左右子树
    //只要有一个递归函数返回true,就满足条件
    return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}

2.5、二叉树的前、中、后序遍历

这里的前、中、后序遍历,可能与之前我们在实现数据结构的遍历不太一样。

以前序遍历为例。

二叉树的前序遍历


我们看到第三幅图,图中除了注释要求我们动态开辟 数组空间存储节点数据,还在函数中添加了参数int* returnSize,说明对于二叉树结点个数,我们事先是不知道的。

要开辟空间,就要知道节点个数;而要知道节点个数,我们可以利用之前学过的二叉树求节点个数的知识,构建函数。

开辟完空间。由于前序遍历涉及递归,所以我们不妨另外构建一个真正实现前序遍历的函数。

在这个函数中,由于需要向数组中存储数据,所以我们可以添加数组参数 。而每存完一个节点数据,数组下标都要向前进一位 。这时我们又知道,不能定义局部变量或全局变量,那么就再添加一个(指针)参数,做数组下标。

c 复制代码
typedef struct TreeNode TreeNode;
int BinaryTreeSize(TreeNode* root)
{
    //节点为空,返回0
    if (root == NULL)
    {
        return 0;
    }

    return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

void preOrder(TreeNode* root, int* arr, int* pi)
{
    //为空,直接返回
    if (root == NULL)
    {
        return;
    }
    //入数据时,下标不能使用局部变量,也不能使用全局变量
    //所以,另外添加一个参数
    arr[(*pi)++] = root->val;//修改顺序

    preOrder(root->left, arr, pi);
    preOrder(root->right, arr, pi);
}

int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    //首先,节点的个数未知,先构建函数求节点个数
    *returnSize = BinaryTreeSize(root);
    //动态申请数组
    int* arr = (int*)malloc(sizeof(int) * (*returnSize));
    //构建函数入数组
    int i = 0;
    preOrder(root, arr, &i);

    return arr;
}

中、后序遍历,就不再赘述,只要在函数preOrder()改变遍历与存值的顺序即可。

2.6、二叉树遍历

二叉树遍历

在这里,我们需要根据一个二叉树的前序遍历序列,构建二叉树,再输出这个二叉树的中序遍历序列。

首先,我们设计代码,输入一串字符,代表二叉树的前序序列。

首先设计还原二叉树 的函数,那么函数的返回值为二叉树节点类型 。前序序列中,当读取到 # ,说明在二叉树中读取到了NULL,返回 NULL ,但要数组下标前进一个元素空间大小;

当读到字符,创建节点,数组下标前进,再进入到左、右子树。

其次是中序遍历函数了,前面已经学习过了,这里也不再赘述。

当然,我们还是要在这道题中,另外实现二叉树的定义节点的创建这两种功能。

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

//创建二叉树结构
typedef struct TreeNode
{
    char val;
    struct TreeNode* left;
    struct TreeNode* right;
}TreeNode;

//创建节点
TreeNode* BuyNode(char val)
{
    TreeNode* newnode = (TreeNode*)malloc(sizeof(TreeNode));
    if (newnode == NULL)
    {
        perror("malloc failed\n");
        exit(1);
    }

    newnode->val = val;
    newnode->left = newnode->right = NULL;

    return newnode;
}

//根据前序遍历,构建二叉树
TreeNode* createTree(char* str, int* pi)
{
    //遇到 # ,说明遇到NULL,返回NULL
    if (str[(*pi)] == '#')
    {
        (*pi)++;//跳过当前 #
        return NULL;
    }
    //创建根节点
    TreeNode* root = BuyNode(str[(*pi)++]);
    //创建左子树
    root->left = createTree(str, pi);
    //创建右子树
    root->right = createTree(str, pi);

    return root;
}

//中序遍历
void InOrder(TreeNode* root)
{
    if (root == NULL)
    {
        return;
    }

    InOrder(root->left);
    printf("%c ", root->val);
    InOrder(root->right);
}

int main()
{
    char str[100];
    scanf("%s", str);
    //创建二叉树结构
    //根据前序遍历,构建二叉树
    int i = 0;
    TreeNode* root = createTree(str, &i);
    //输出中序遍历
    InOrder(root);

    return 0;
}
相关推荐
优宁维生物2 小时前
DNA 提取的基础方法
人工智能·算法
@Aurora.2 小时前
优选算法【专题二:滑动窗口】
算法
小石头 100862 小时前
【Java】String类(超级详细!!!)
java·开发语言·算法
.柒宇.2 小时前
力扣hot100---42.接雨水(java版)
java·算法·leetcode
youngee112 小时前
hot100-41验证二叉搜索树
算法
迈巴赫车主2 小时前
蓝桥杯20534爆破 java
java·数据结构·算法·职场和发展·蓝桥杯
坚持就完事了2 小时前
数据结构之链表
数据结构·python·算法·链表
c#上位机3 小时前
halcon图像去噪—均值滤波
图像处理·算法·均值算法·halcon
曾几何时`3 小时前
347. 前 K 个高频元素 分别使用sort和priority_queue 对哈希结构自定义排序
算法