二叉树相关问题
判断二叉树是否轴对称
根节点若为null,返回true即可
根节点若不为null,只需判断其左右子树是否为轴对称即可
核心是:给定两个子树根节点r1和r2,判定这两个子树是否为轴对称
-
r1 r2均为null
必定对称,return true
-
r1 r2均不为null
-
r1->key != r2->key
必定不对称,return false
-
r1->key == r2->key(⭐此处重点理解)
需要再进一步判断,看对称方位的子树是否对称,即(r1->left,r2->right)和(r1->right,r2->left)
-
-
其他(一个为null,一个不为null)
必定不对称,return false
图示理解一下⭐处
关键代码如下
bool fun(struct TreeNode *r1, struct TreeNode *r2){
if(!r1 && !r2){ //r1 r2均为空 --对称
return true;
} else if(r1 && r2){ //r1 r2均不空
if(r1->val == r2->val) //根节点key相同 --再比较对称方位的子树是否对称
return fun(r1->left, r2->right) && fun(r1->right, r2->left);
else //根节点key不相同 -- 不对称
return false;
} else { //一空 一不空 --不对称
return false;
}
}
bool isSymmetric(struct TreeNode* root){
if(!root)
return true;
else
return fun(root->left, root->right);
}
高效求完全二叉树节点个数
222. 完全二叉树的节点个数 - 力扣(LeetCode)
简单解法:遍历二叉树,时间复杂度O(n)
高效解法:利用完全二叉树中存在很多满二叉子树的特性,获取满二叉子树高度后直接使用公式计算出该满二叉子树的节点数量,时间复杂度O(logn * logn)
如何判断完全二叉树中某个子树是满二叉树?
结合完全二叉树的性质,除最后一层之外,上面各层构成满二叉树。最后一层可能填满,也可能没填满,若没满则所有节点集中在左侧位置。
因此沿着左子树走下去,求高度lh,沿着右子树走下去,求高度rh,若lh等于rh则为满二叉树,否则不是满二叉树
代码实现
int countNodes(struct TreeNode* root){
if(root == NULL)
return 0;
struct TreeNode *lnode = root->left, *rnode = root->right;
int lh = 1, rh = 1;
while(lnode){ //沿着左子树走并求高度
lnode = lnode->left;
lh++;
}
while(rnode){ //沿着右子树走并求高度
rnode = rnode->right;
rh++;
}
if(lh == rh){ //以root为根节点的子树是满二叉树
return (1 << lh) - 1; //高度为h的满二叉树节点个数2^h-1
}
else{ //以root为根节点的子树不是满二叉树,不能使用公式,需递归求解
return countNodes(root->left) + countNodes(root->right) + 1;
}
}
中序+后序建立二叉树
106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
中序+先序同理
代码如下
//in[l1...h1]为中序序列,post[l2...h2]为后序序列
BTree buildTree(int in[], int l1, int h1, int post[], int l2, int h2){
BTree root = (BTree) malloc(sizeof(struct BNode));
root->val = post[h2];
int i;
for(i = l1; i <= h1; ++i) //在中序序列中找根节点位置i
if(in[i] == root->val)
break;
int llen = i - l1; //左子树节点数
int rlen = h1 - i; //右子树节点数
if(llen) //左子树不为空
root->left = buildTree(in, l1, i-1, post, l2, l2+llen-1); //确定起止点,举例好理解
else
root->left == NULL;
if(rlen) //右子树不为空
root->right = buildTree(in, i+1, h1, post, l2+llen, h2-1);
else
root->right = NULL;
return root;
}
验证二叉搜索树
给定一个二叉树根节点,验证其是否是二叉排序树BST
BST定义:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
注意:BST定义中不能有相同key
两种方法:从定义出发用递归、利用BST性质
1、从定义出发用递归
根据定义,当前子树是否为BST
-
其左子树中的所有节点key是否都小于根节点key
只需遍历左子树求其中的最大key,若大于根节点key则不是BST
-
其右子树中的所有节点key是否都小于根节点key
只需遍历右子树求其中的最小key,若小于根节点key则不是BST
若满足左子树max<根 并且 右子树min>根,则递归检查左右子树是否为BST
显然,这种思路求子树中最大最小key,需要遍历整个子树,而在判断BST的过程中,要对树中的所有节点求左右子树的最大最小key,算法效率不高。
代码实现如下
int getMax(struct TreeNode *root){ //层序遍历求树中最大key
int max = 0x80000000;
struct TreeNode *que[20010], *p = NULL;
int front = 0, rear = 0;
if(root){
que[rear++] = root;
}
while(front != rear){
p = que[front++];
if(p->val > max){
max = p->val;
}
if(p->left){
que[rear++] = p->left;
}
if(p->right){
que[rear++] = p->right;
}
}
return max;
}
int getMin(struct TreeNode *root){ //层序遍历求树中最小key
int min = 0x7fffffff;
struct TreeNode *que[20010], *p = NULL;
int front = 0, rear = 0;
if(root){
que[rear++] = root;
}
while(front != rear){
p = que[front++];
if(p->val < min){
min = p->val;
}
if(p->left){
que[rear++] = p->left;
}
if(p->right){
que[rear++] = p->right;
}
}
return min;
}
bool isValidBST(struct TreeNode* root){
if(root == NULL){
return true;
}
if(root->left){ //若左子树存在,检查左子树最大key是否小于根节点key
int lmax = getMax(root->left);
if(lmax >= root->val){ //注意 >=
return false;
}
}
if(root->right){ //若右子树存在,检查右子树最小key是否大于根节点key
int rmin = getMin(root->right);
if(rmin <= root->val){ //注意 <=
return false;
}
}
//递归检查左右子树是否为BST
return isValidBST(root->left) && isValidBST(root->right);
}
2、利用BST性质
BST中序遍历是一个递增的序列,中序遍历给定的二叉树,得出其中序遍历序列,检查是否为递增序列即可
代码实现如下
int in[10010], k = 0;
void inOrder(struct TreeNode *root){ //递归中序遍历
if(root ==NULL){
return ;
}
inOrder(root->left);
in[k++] = root->val;
inOrder(root->right);
}
bool isValidBST(struct TreeNode* root){
k = 0; //leetcode要求使用全局变量前先初始化
inOrder(root);
int i;
for(i = 1; i < k; ++i){
if(in[i] <= in[i-1]){ //注意<=
return false;
}
}
return true;
}
二叉搜索树树插入节点
Loading Question... - 力扣(LeetCode)
根据二叉搜索树的性质,查找插入位置,每次插入的必是叶子节点
注意节点返回的理解:每个子树插入完成后将其返回给上一层
代码实现如下
struct TreeNode* insertIntoBST(struct TreeNode* root, int val){
if(root == NULL){ //创建新节点之后,将其返回给上一层
struct TreeNode *node = (struct TreeNode*) malloc(sizeof(struct TreeNode));
node->val = val;
node->left = node->right = NULL;
return node;
}
if(root->val < val){
root->right = insertIntoBST(root->right, val);
}
else{
root->left = insertIntoBST(root->left, val);
}
return root;
}
二叉搜索树删除节点
450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
-
删除叶子节点
直接删除即可
-
删除只有左子树或只有右子树的节点
直接将左子树或者右子树上移到被删节点的位置
-
删除既有左子树又有右子树的节点
一种处理方法是将左子树接到右子树最左节点的左孩子上,同理可将右子树放到左子树上
下图所示为删除节点7的具体流程
在③完成后,就变成了删除只有右孩子的情况,直接将右孩子上移,具体代码表现为return root->right,完成后即为④
代码实现如下
注意理解递归代码中节点返回的含义,每次递归在一个子树中删除节点,完成后将子树根节点返回
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
struct TreeNode* deleteNode(struct TreeNode* root, int key){
if(root == NULL){ //树中不存在此节点
return NULL;
}
if(root->val == key){ //递归出口,要删除当前节点
if(!root->left && !root->right){ //叶节点
return NULL; //直接删除 返回给上一层NULL
}
else if(root->left && !root->right){ //左不空 右空 左子树直接上移
return root->left;
}
else if(!root->left && root->right){ //左空 右不空 右子树直接上移
return root->right;
}
else{ //左右均不为空
//将左子树接在右子树最左端节点的左子树上
struct TreeNode *t = root->right;
while(t->left){ //t定位到root右子树中最左端节点
t = t->left;
}
t->left = root->left;
//转换为删除只有右子树的情况
return root->right;
}
}
else if(root->val > key){ //key若存在必在左子树中,递归取左子树删除key
root->left = deleteNode(root->left, key);
}
else{ //root-val < key key若存在必在右子树中,递归取右子树删除key
root->right = deleteNode(root->right, key);
}
//删除完成后返回根节点
return root;
}
修建二叉搜索树
给定BST,给定一个范围[low,high],在这个范围内的节点保留,在这个范围外的节点删除
两个思路:
-
遍历BST记录下所有要删除的节点,之后再调用BST删除节点函数依次删除
遍历和记录都需要开辟额外的存储空间,且效率较低(不是边遍历边删除)
-
利用BST特性,边遍历边删除,相对不需要额外的存储空间
如果当前节点<low,则该节点的左子树肯定不在范围内(BST特性),可以不用考虑,但右子树可能存在不在范围内的节点,需把当前节点右子树递归修剪之后返回给上一层,同理
如果当前节点>high,则该节点的右子树肯定不在范围内,可以不用考虑,需把当前节点左子树递归修剪之后返回给上一层
方法一:
代码实现如下,核心是BST删除节点和二叉树遍历(这里使用层序遍历)
//BST删除节点
struct TreeNode *delete(struct TreeNode *root, int key){
if(root == NULL){
return NULL;
}
if(root->val == key){
if(!root->left && !root->right){
return NULL;
}
else if(root->left && !root->right){
return root->left;
}
else if(!root->left && root->right){
return root->right;
}
else{
struct TreeNode *t = root->right;
while(t->left){
t = t->left;
}
t->left = root->left;
return root->right;
}
}
else{
if(root->val > key){
root->left = delete(root->left, key);
}
else{
root->right = delete(root->right, key);
}
}
return root;
}
struct TreeNode* trimBST(struct TreeNode* root, int low, int high){
//可以看到开辟了两个与问题规模n有关的辅助存储空间
struct TreeNode *que[20000], *deleteNodes[10000], *p = NULL;
int front = 0, rear = 0;
int k = 0;
if(root){
que[rear++] = root;
}
while(front != rear){ //层序遍历
p = que[front++];
if(p->val < low || p->val > high){ //记录下所有要删除的节点
deleteNodes[k++] = p;
}
if(p->left){
que[rear++] = p->left;
}
if(p->right){
que[rear++] = p->right;
}
}
int i;
for(i = 0; i < k; ++i){ //调用delete() 依次删除所有要删除的节点
root = delete(root, deleteNodes[i]->val);
}
return root;
}
方法二:
代码实现如下,重点理解递归处理子树并返回上一层的过程
struct TreeNode* trimBST(struct TreeNode* root, int low, int high){
if(root == NULL){
return NULL;
}
if(root->val < low){ //左子树必定不在范围内,修剪右子树并返回给上一层
return trimBST(root->right, low, high);
}
if(root->val > high){ //右子树必定不在范围内,修剪左子树并返回给上一层
return trimBST(root->left, low, high);
}
root->left = trimBST(root->left, low, high); //递归修剪左子树
root->right = trimBST(root->right, low, high); //递归修剪右子树
return root;
}
将有序数组转化为二叉搜索树
108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
一开始想复杂了,想到二叉平衡树(AVL)的调整操作:RR/LL/RL/LR
只需要每次从有序数组的中间位置二分,将中间位置作为根节点构建BST即可
代码实现如下:
struct TreeNode *build(int *a, int low, int high){
if(low > high){ //递归出口
return NULL;
}
struct TreeNode *root = (struct TreeNode*) malloc(sizeof(struct TreeNode));
int mid = (low + high) / 2;
root->val = a[mid]; //数组中间元素作为根节点
root->left = build(a, low, mid - 1); //递归建立左子树
root->right = build(a, mid + 1, high); //递归建立右子树
return root;
}
struct TreeNode* sortedArrayToBST(int* nums, int numsSize){
return build(nums, 0, numsSize-1);
}