二叉树OJ题

带值的多层递归

对二叉树的递归性质做一个更好的补充。

提到二叉树的递归,我们首相想到的就是二叉树的深度优先遍历(根遍历)。对于求二叉树结点的个数 ,同样可以用递归来实现(带值的多层递归)。

1、二叉树的结点数
cpp 复制代码
int TreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	else
		return 1 + TreeSize(root->_left) + TreeSize(root->_right);
}

同理可以补充:求叶子结点数、树的深度。

2、二叉树的叶子结点数
cpp 复制代码
int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	else if(root->_left == NULL && root->_right == NULL)
	{
		return 1;
	}
	else
	{
		return TreeLeafSize(root->_left) + TreeLeafSize(root->_right);
	}
}
3、二叉树深度
cpp 复制代码
int TreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	else
	{
		return 1 + (TreeDepth(root->_left) > TreeDepth(root->_left) ? 
			TreeDepth(root->_left) : TreeDepth(root->_left));
	}
}

OJ题

  1. 对于OJ题的VS调试,我们通常造一颗伪树.
  2. 对于二叉树的OJ题解法又不懂的地方,可以通过代码一步步画递归图来理解,我在这里只画较为复杂的几个。
1、二叉树的前序遍历

144. 二叉树的前序遍历 - 力扣(LeetCode),递归算法,迭代在C++中。

cpp 复制代码
int TreeSize(struct TreeNode* root) {
    if (root == NULL)
        return 0;
    else
        return 1 + TreeSize(root->left) + TreeSize(root->right);
}//求Tree的大小,用与内存开辟

void _PreOrder(struct TreeNode* root, int* arr, int* pi) {
    if (root == NULL)
        return;
    /*
        假设树:
         A
        / \ 
       B   C
    */
        
    //第一次:arr[0]=根节点的值 i=i+1
    //第二次:arr[1]=左孩子的值 i++
    //第三次:左孩子的左孩子=NULL i不变
    //第四次:左孩子的右孩子=NULL i不变
    //第五次:arr[2]=右孩子的值 i++

    arr[*pi] = root->val;
    (*pi)++;//这两句相当于对结点的访问

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

//递归的话不能直接用 preorderTraversal来递归,这样会开辟很多空间
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
    int sz = TreeSize(root);
    int* retArr = (int*)malloc(sizeof(int) * sz);
    int i = 0;
    _PreOrder(root, retArr, &i);//i传地址才会改变否则返回时i的值不会改变
    *returnSize = sz;
    return retArr;
}

类似的:

2、二叉树的中序遍历

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

3、二叉树的后序遍历

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

4、单值二叉树

如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。

只有给定的树是单值二叉树时,才返回 true;否则返回 false

965. 单值二叉树 - 力扣(LeetCode)(把一颗二叉树看作 当前树、左子树、右子树

cpp 复制代码
bool isUnivalTree(struct TreeNode* root) {
    if (root == NULL)
        return true; //树空即单值
    if (root->left != NULL && root->val != root->left->val)
        return false;//当前树与左不等
    if (root->right != NULL && root->val != root->right->val)
        return false;//当前树与右不等
    return isUnivalTree(root->left) && isUnivalTree(root->right);
    //判断左子树和右子树
}
5、最大深度

104. 二叉树的最大深度 - 力扣(LeetCode)

相比于直接返回递归函数的计算,用两个变量存储,减少了重复的计算。

cpp 复制代码
int maxDepth(struct TreeNode* root) {
    if(root==NULL)return 0;
    int leftDepth=maxDepth(root->left);
    int rightDepth=maxDepth(root->right);
    //return maxDepth(root->left)?maxDepth(root->right)...
    return leftDepth>rightDepth?leftDepth+1:rightDepth+1;
}
6、翻转二叉树

给定一棵二叉树的根节点 root,请左右翻转这棵二叉树,并返回其根节点

LCR 144. 翻转二叉树 - 力扣(LeetCode)(求二叉树的镜像)

cpp 复制代码
//写法一:交换左右子树
struct TreeNode* flipTree(struct TreeNode* root) {
    if (root == NULL || (root->left == NULL && root->right == NULL))
        return root;
    struct TreeNode* tmp = root->left;
    root->left = root->right;
    root->right = tmp;
    flipTree(root->left);
    flipTree(root->right);

    return root;
}

//解法二:利用返回值
struct TreeNode* flipTree(struct TreeNode* root) {
    if (root == NULL)
        return NULL;

    struct TreeNode* left = flipTree(root->left);
    struct TreeNode* right = flipTree(root->right);
    root->left = right;
    root->right = left;
    return root;
}
7、相同的树

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的

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

cpp 复制代码
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    if (p == NULL && q == NULL)
        return true;

    if (p == NULL && q != NULL)
        return false;
    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);
}
8、另一棵树的子树

给你两棵二叉树 rootsubRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

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

cpp 复制代码
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {
    if (root == NULL)
        return false;

    if (isSameTree(root, subRoot))
        return true;

    return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot);
}
9、平衡二叉树

**平衡二叉树:**是指该树所有节点的左右子树的高度相差不超过 1。

给定一个二叉树,判断它是否是平衡二叉树。

110. 平衡二叉树 - 力扣(LeetCode):

cpp 复制代码
bool isBalanced(struct TreeNode* root) {
    if (root == NULL)
        return true;
    int lefth = maxDepth(root->left);
    int righth = maxDepth(root->right);
    int gap = abs(lefth - righth);
    if (gap <= 1) {
        return isBalanced(root->left) && isBalanced(root->right);
    } else {
        return false;
    }
}
//对于N个节点的二叉树:只要遍历所有的结点,时间复杂度就是O(N)
//这个算法
//最好的时间复杂度是:O(N)
//最坏的时间复杂度是:O(N^2)

可以把判断平衡二叉树的算法优化到O(N)吗?

1、时间复杂度大的原因:

  • 用的是前序判断,每次调用都会重复计算高度。
  • 存在大量的重复计算

2、优化

  • 用后序判断。
  • 在判断的同时计算数的高度。

(这里的优化是:后序求高度,减少了重复计算。较为复杂需要画图补充...)。

cpp 复制代码
//后序判断优化
bool _isBalanced(struct TreeNode* root, int* pDepth) {
    if (root == NULL) {
        *pDepth = 0;
        return true;
    } else {
        int leftDepth = 0;
        if (_isBalanced(root->left, &leftDepth) == false)
            return false;

        int rightDepth = 0;
        if (_isBalanced(root->right, &rightDepth) == false)
            return false;

        if (abs(leftDepth - rightDepth) > 1)
            return false;

        *pDepth = leftDepth > rightDepth ? leftDepth+1 : rightDepth+1;
        return true;
    }
}

bool isBalanced(struct TreeNode* root) {
    int depth = 0;
    return _isBalanced(root, &depth);
}
10、构建二叉树

即:还原二叉树,给定一个先序遍历的字符串序列,要求还原二叉树,并中序遍历该二叉树。

这道题的重点在于还原二叉树(二叉树的构建是递归的)。

对于一个先序序列能否还原二叉树:(#代表空格)

  • 完整的先序序列:abc##de#g##f###,可以还原整个二叉树。
  • 不完整的先序序列:abcdegf,无法还原二叉树,需要和中序序列联合还原。
cpp 复制代码
typedef struct TreeNode {
    char val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

TreeNode* CreateTree(char* str, int* pi) {
    if (str[*pi] == '#') {
        (*pi)++;
        return NULL;
    } else {
        TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
        if (root == NULL) {
            printf("%s\n", strerror(errno));
            exit(-1);
        }
        root->val = str[*pi];
        (*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);
}

二叉树的核心思想就是:(当前树解决不了的问题交给左右子树)

分治算法:把大问题分解为小问题,小问题在进一步分解,直到不可再分解的子问题。

相关推荐
小雄abc21 分钟前
决定系数R2 浅谈三 : 决定系数R2与相关系数r的关系、决定系数R2是否等于相关系数r的平方
经验分享·笔记·深度学习·算法·机器学习·学习方法·论文笔记
uyeonashi39 分钟前
【C++】刷题强训(day14)--乒乓球匡、组队竞赛、删除相邻数字的最大分数
开发语言·c++·算法·哈希算法
机器学习之心1 小时前
一区正弦余弦算法!SCA-SVM正弦余弦算法优化支持向量机多特征分类预测
算法·支持向量机·分类·sca-svm·正弦余弦算法优化
a栋栋栋2 小时前
刷算法心得
算法
華華3552 小时前
读程序题...
开发语言·c++·算法
宸码3 小时前
【机器学习】手写数字识别的最优解:CNN+Softmax、Sigmoid与SVM的对比实战
人工智能·python·神经网络·算法·机器学习·支持向量机·cnn
太空漫步113 小时前
逆序对个数
java·算法·排序算法
23级二本计科3 小时前
分治_归并_归并排序(逆序对)
数据结构·算法·排序算法
CHENWENFEIc3 小时前
基础排序算法详解:冒泡排序、选择排序与插入排序
c语言·数据结构·算法·排序算法·学习方法·冒泡排序
养佳工具人3 小时前
Java各种排序
java·算法·排序算法