二叉树递归题目
本篇博客由CSDN@先搞面包再谈爱原创,转载请标注清楚,请勿抄袭。
oj题前的基础题目
二叉树主打的就是一个分治的思想,用递归可以解决很多的问题。
二叉树节点个数
大致思路:总结点个数 = 当前根节点(1)+左子树的结点个数+右子树的结点个数
递归,当根节点为空时停止
int BinaryTreeSize(BTNode* root)
{
return root == NULL ? 0 : BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}
二叉树叶子节点个数
大致思路:根节点的叶子节点的个数 = 左子树的叶子结点的个数 + 右子树的叶子结点的个数。
递归,当根节点为空时返回0(当某一结点的左右中有一个为空,那么不会返回数值,会访问左右子树,此时就会访问到空树;或者是整个树本身就是空的),当根节点的左右子树为空时返回1(叶子节点)。
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
else if (root->_left == NULL && root->_right == NULL)
{
return 1;
}
int LeftLeafSize = BinaryTreeLeafSize(root->_left);
int RightLeafSize = BinaryTreeLeafSize(root->_right);
return LeftLeafSize + RightLeafSize;
}
二叉树第k层节点个数
大致思路:根节点的第K层结点个数 = 左右子树的第k-1层结点个数。
递归,当根为空时就返回0,当k==1时就返回1。
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
assert(k >= 1);
if (root == NULL)
{
return 0;
}
else if (k == 1)
{
return 1;
}
return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}
二叉树查找值为x的节点
大致思路:先看根是不是,不是了看左子树里有没有x,再看右子树里有没有x,如果都没找到就返回空。
递归,当根节点为空时就返回NULL,否则对比根节点与x的值,如果相等,则返回根节点,如果不相等,则去左子树和右子树中找。
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
{
return NULL;
}
if (root->_data == x)
{
return root;
}
BTNode* LeftNode = BinaryTreeFind(root->_left, x);
if (LeftNode)
return LeftNode;
BTNode* RightNode = BinaryTreeFind(root->_right, x);
return RightNode;
}
二叉树的高度(深度)
大致思路:二叉树的高度 = max(左子树高度,右子树高度) + 1;
递归,当根节点为NULL时返回0,否则就求左右子树的高度。
int BinaryTreeDepth(BTNode* root)
{
if (root == NULL)
return 0;
int leftDepth = BinaryTreeDepth(root->_left);
int rightDepth = BinaryTreeDepth(root->_right);
int max = leftDepth > rightDepth ? leftDepth : rightDepth;
return max + 1;
}
力扣
力扣965
大致思路:先看根是否等于左,再看根是否等于右,相等则继续,不等则停止。
递归,当根为空的时候返回true,不为空时先判断根和左(左不为空的前提下),如果不相等则返回false,如果相等则继续判断根和右(右不为空的前提下),如果不相等则返回false,如果相等,则返回左右子树的判断结果。
bool isUnivalTree(struct TreeNode* root)
{
if(root == NULL)
return true;
//如果左不空,就判断左和根是否相等。
if(root->left && root->val != root->left->val)
return false;//不相等就返回false
//相等就继续判断根和右
//如果右不为空,就判断根和右是否相等
if(root->right && root->val != root->right->val)
return false;//不相等就返回false
//相等就继续判断左右子树是否为单值二叉树
return isUnivalTree(root->left) && isUnivalTree(root->right);
}
力扣100
大致思路:先判断两树的根是否对称,再判断这两个数的左右子树是否对称。
递归,先看两树的根是否同时为空,若都为空,说明这两个节点是对称的,然后判断二者是否一个为空一个不为空,这样的话就不是对称的,然后就是两个不是空的情况,这时候先判断值是否相同,若不相同,说明不对成,若相同,则说明当前结点是对称的,但是还不能返回true,还要判断当前结点的左右是否对称。
bool isSameTree(struct TreeNode* p, struct TreeNode* q)
{
//左右都为空true
if(p == NULL && q == NULL)
return true;
//左为空且右不为空 || 右为空且左不为空 false
if((p == NULL && q != NULL) || (p != NULL && q == NULL))
return false;
//左右都不为空 先判断二者值是否相等
if(p->val != q->val)
return false;//不相等返回false
//相等则继续判断二者的左右子树
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
力扣101
大致思路:与前一个题目非常相似,先判断根是否为空,不为空的话就和上面那个题一样了,判断左右是否同时为空,同时为空返回true,一个为空返回false,两个都不为空先判断值是否相同,然后再判断左左是否等于右右,左右是否等于右左是否是对称的。
这里的递归要自己创建一个子函数才能进行判断,不然本题目的函数的参数没法同时将两个结点进行判断。
bool _isSymmetric(struct TreeNode* left, struct TreeNode* right)
{
//左右都为空 true
if(left == NULL && right == NULL)
return true;
//左右两个结点一个为空一个不为空 false
if((left == NULL && right != NULL)
|| (left != NULL && right == NULL))
return false;
//都不为空,左右值不同 false
if(left->val != right->val)
return false;
return _isSymmetric(left->left, right->right)
&& _isSymmetric(left->right, right->left);
}
bool isSymmetric(struct TreeNode* root)
{
//根为空 true
if(root == NULL)
return true;
return _isSymmetric(root->left, root->right);
}
力扣144
大致思路:根打印的一模一样,只不过是把printf改成了数组赋值。但是这个题不能直接在一个函数里把递归写出来。因为题目所给的函数要返回一个数组,你若过这样递归的话返回值是没法搞定的,所以要搞一个子函数来进行递归。
下面这句话是题目当中的,意思就是当前题目所给的函数的返回数组必须得是动态开辟出来的,而且调用该函数的函数会自动释放掉你所开辟的空间。
那么前序遍历树,并将前序遍历数据的顺序放到一个数组中,就得要先决定一下数组的大小,数组的大小是由树来直接决定的,那么我们可以先写一个统计树的结点个数的函数,然后开辟空间的时候开辟节点个数个int类型就行了。
int TreeSize(struct TreeNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
void _preorderTraversal(struct TreeNode* root, int* res, int* pi)
{
if(root == NULL)
return;
res[(*pi)++] = root->val;
_preorderTraversal(root->left, res, pi);
_preorderTraversal(root->right, res, pi);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
*returnSize = TreeSize(root);
int* res = (int*)malloc(*returnSize * sizeof(int));
int i = 0;
_preorderTraversal(root, res, &i);
return res;
}
力扣572
大致思路:遇到root->val和subRoot->val相等的时候就判断一下当前root的位置是否为和subRoot相等,若相等就说明有该子树,不相等就继续判断root的左和右。
本题递归也要创建一个函数,用来判断两树是否相等的子函数。
//判断两树是否相等
bool isSameTree(struct TreeNode* root, struct TreeNode* subRoot)
{
if(root == NULL && subRoot == NULL)
return true;
if((root == NULL && subRoot != NULL) || (root != NULL && subRoot == NULL))
return false;
if(root->val != subRoot->val)
return false;
return isSameTree(root->left, subRoot->left)
&& isSameTree(root->right, subRoot->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot)
{
if(root == NULL)
return false;
//遇到根节点和sub的根节点的值相同了就对比,如果是子树就说明成立,返回true,无需再对比
if(root->val == subRoot->val && isSameTree(root, subRoot))
return true;
//遇到根结点不等或者等了但不是子树,就要判断root的左右是否有该子树
return isSubtree(root->left, subRoot)
|| isSubtree(root->right, subRoot);
}
牛客
牛客KY11
大致思路:数组中的每一个元素就是一个结点,遇到 ‘#’ 就是NULL,其他的就是自身的值创建的结点。
本题目需要理解透彻的话看一下这张图解就行了:
画的有点丑,凑合着看。
其实本质上还是递归构建树。
核心代码就是下面这点:
BTNode* TreeCreat(char* arr, int* pi)
{
//遇到#了或者是字符串走完了就返回NULL
if(arr[*pi] == '#' || arr[*pi] == 0)
{
(*pi)++;
return NULL;
}
BTNode* node = BuyNode(arr[(*pi)++]);
//递归构建左子树
node->left = TreeCreat(arr, pi);
//递归构建右子树
node->right = TreeCreat(arr, pi);
//返回当前结点
return node;
}
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char BTDataType;
typedef struct TreeeNode
{
BTDataType data;
struct TreeeNode* left;
struct TreeeNode* right;
} BTNode;
//创建新结点
BTNode* BuyNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
node->data = x;
node->left = NULL;
node->right = NULL;
return node;
}
//创建树
BTNode* TreeCreat(char* arr, int* pi)
{
if(arr[*pi] == '#' || arr[*pi] == 0)
{
(*pi)++;
return NULL;
}
BTNode* node = BuyNode(arr[(*pi)++]);
node->left = TreeCreat(arr, pi);
node->right = TreeCreat(arr, pi);
return node;
}
//中序遍历
void InOrder(BTNode* root)
{
if(root == NULL)
return;
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
//销毁树
void DestroyTree(BTNode* root)
{
if(root == NULL)
return;
DestroyTree(root->left);
DestroyTree(root->right);
free(root);
}
int main()
{
char arr[101] = {0};
scanf("%s", arr);
int i = 0;
BTNode* root = TreeCreat(arr, &i);
InOrder(root);
DestroyTree(root);
return 0;
}
二叉树的销毁
直接用后续遍历就行。
//销毁树
void DestroyTree(BTNode* root)
{
if(root == NULL)
return;
DestroyTree(root->left);
DestroyTree(root->right);
free(root);
}
判断二叉树是否是完全二叉树
大致思路:用队列,和层序遍历思路一样,只不过入队的时候不管空不空都入队,每次循环之前都要先得到队头,再判断一下队头是否为空,对头不为空就入左右,如果为空那么这个位置就是队列里的第一个NULL,然后从这个位置往后都不能出现空,如果出现空了就说明不是完全二叉树,如果都为空的话就说明是完全二叉树。
看下图解:
完全二叉树
非完全二叉树
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
if (root == NULL)
return 1;
Queue q;
QueueInit(&q);
QueuePush(&q, root);
//判断队列中找到NULL后面的元素是否有非空
int flag = 0;
while (!QueueEmpty(&q))
{
BTNode* tmp = QueueFront(&q);
QueuePop(&q);
if (tmp != NULL)
{
//tmp不为空就先入左右
QueuePush(&q, tmp->_left);
QueuePush(&q, tmp->_right);
}
else
{
//tmp为NULL,就判断其后方是否有非空
while (!QueueEmpty(&q))
{
BTNode* node = QueueFront(&q);
QueuePop(&q);
if (node != NULL)
{
//遇到非空的结点了
flag = 1;
break;
}
}
}
if (flag == 1)
break;//遇到非空结点了,不用再判断了
}
if (flag == 1)
return false;
else
return true;
QueueDestroy(&q);
}
int main()
{
BTNode* root = CreatBinaryTree();
if (BinaryTreeComplete(root))
{
printf("YES\n");
}
else
{
printf("NO\n");
}
return 0;
}
二叉树的递归题目就到这里。这几道题好好搞一搞。能很大程度上提高你对于递归的了解。
结束。