【二叉树】(四)二叉搜索树的基础修改构造及属性求解1
(四)二叉搜索树的基础修改构造及属性求解1
- 二叉搜索树中的搜索
- 递归法
- 迭代法
- 验证二叉搜索树(模板题)
- 递归 + 数组
- 递归 + 双指针
- 迭代法
- 二叉搜索树的最小绝对差
- 递归 + 数组
- 递归 +双指针
- 迭代法
- 二叉搜索树中的众数
- 求二叉树的众数
- 二叉搜索树的众数
- 递归法
- 迭代法
二叉搜索树中的搜索
力扣原题链接:700.二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。

输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]
核心思路
二叉搜索树是一个有序树:
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树
这就决定了,二叉搜索树,递归遍历和迭代遍历和普通二叉树都不一样。
递归法
1. 确定递归函数的参数和返回值
递归函数的参数传入的就是根节点和要搜索的数值,返回的就是以这个搜索数值所在的节点,代码如下:
TreeNode* traversal(TreeNode* root, int val)
2. 确定终止条件
如果root为空,或者找到这个数值了,就返回root节点。
if(root == NULL || root->val == val)return root;
3. 确定单层递归的逻辑
因为二叉搜索树的节点是有序的,所以可以有方向的去搜索。如果root->val > val
,搜索左子树;如果root->val < val
,就搜索右子树,最后如果都没有搜索到,就返回NULL,代码如下:
TreeNode* result = NULL;
if(val < root->val)result = traversal(root->left, val);
elseresult = traversal(root->right, val);
return result;
整体代码如下:
class Solution {
public://递归法TreeNode* traversal(TreeNode* root, int val){if(root == NULL || root->val == val)return root;TreeNode* result = NULL;if(val < root->val)result = traversal(root->left, val);elseresult = traversal(root->right, val);return result;}TreeNode* searchBST(TreeNode* root, int val) {return traversal(root,val);}
};
因为搜索到了目标节点,所以要立即返回,这样才符合找到节点就返回的逻辑(搜索某一条边),如果不加return,那么就是遍历整棵树了。
迭代法
-
因为二叉搜索树的特殊性,也就是节点的有序性,可以不使用辅助栈或者队列就可以写出迭代法。
-
对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。
-
例如要搜索元素为3的节点,我们不需要搜索其他节点,也不需要做回溯,查找的路径已经规划好了。
-
中间节点如果大于3就向左走,如果小于3就向右走,如图:

所以迭代法代码如下:
//迭代法
TreeNode* searchBST(TreeNode* root, int val) {while(root != NULL){if(val < root->val)root = root->left;else if(val > root->val)root = root->right;elsereturn root;}return NULL;
}
小结: 基于二叉搜索树的有序性,遍历的时候要充分利用二叉搜索树的特性。
验证二叉搜索树(模板题)
这是一题解决二叉搜索树中的三种常见思路题
力扣原题链接:98. 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例1:
输入:root = [2,1,3]
输出:true
示例2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
核心思路
- 中序遍历下,输出的二叉搜索树节点的数值是有序序列。
- 有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。
递归 + 数组
思路1:可以递归中序遍历将二叉搜索树转变成一个数组,然后只要比较一下,这个数组是否是有序即可,注意二叉搜索树中不能有重复元素。
递归遍历把数据存入数组:
vector<int>result; //中序遍历 结果数组
//思路1:中序遍历 数据值是有序的 中序遍历数据存数组 ,判断数组是否单增
//递归法
bool traversal(TreeNode* root)
{//假设为空嘛 if(root == NULL)return true;traversal(root->left); //左result.push_back(root->val); //中traversal(root->right); //右
}
判断数组是否单增来判断二叉树是否为二叉搜索树:
bool isValidBST(TreeNode* root)
{traversal(root);//判断数组是否单增for(int i = 1; i < result.size(); i++){if(result[i] <= result[i-1])return false;}return true;
}
以上代码中,把二叉树转变为数组来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序。
这道题目比较容易陷入两个陷阱:
陷阱1
不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了,写出了类似这样的代码:
if (root->val > root->left->val && root->val < root->right->val)return true;
else return false;
我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。 所以以上代码的判断逻辑是错误的,例如:

陷阱2
样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的,此时可以初始化比较元素为longlong的最小值。
递归 + 双指针
思路2:在递归遍历的过程中直接判断是否有序
递归三部曲:
1. 确定递归函数,返回值以及参数
要定义一个longlong的全局变量,用来比较遍历的节点是否有序(即上一个节点的值),递归函数返回bool类型,供上一个节点,因此递归函数为:
//相当于记录上一节点的数值
long long maxVal = LONG_MIN;
bool isValidBST(TreeNode* root)
2. 确定终止条件
遍历到空节点返回,假设如果就是空节点,那是二叉搜索树,因此返回true
,代码如下:
//假设为空嘛
if(root == NULL) return true;
3. 确定单层递归的逻辑
中序遍历,一直更新maxVal
,一旦发现maxVal >= root->val
,就返回false,注意元素相同时候也要返回false,代码如下:
bool left = isValidBST(root->left); //左
if(root->val > maxVal) //中maxVal = root->val;
elsereturn false;
bool right = isValidBST(root->right); //右return left && right;
整体迭代代码如下:
long long maxVal = LONG_MIN; //相当于记录上一节点的数值
bool isValidBST(TreeNode* root)
{//假设为空嘛 if(root == NULL)return true;bool left = isValidBST(root->left); //左if(root->val > maxVal) //中maxVal = root->val;elsereturn false;bool right = isValidBST(root->right); //右return left && right;}
递归优化
那如果后台测试数据有longlong的最小值呢,无法再初始化一个更小的值了吧,建议避免初始化最小值,使用双指针的办法,快慢指针首先取到最左面节点数值,每次快指针与之前的值做比较即可。
/**************双指针优化****************/
TreeNode* pre = NULL;
bool isValidBST2(TreeNode* root)
{//假设为空嘛 if(root == NULL)return true;bool left = isValidBST(root->left); //左if(pre != NULL && pre->val >= root->val) //中return false;pre = root;bool right = isValidBST(root->right); //右return left && right;
}
迭代法
这是一个二叉搜索树中序遍历的模板,需熟记
可以用迭代法模拟二叉树中序遍历,且与遍历普通二叉树模板类似,迭代法中序遍历稍加改动就可以了,变成二叉搜索树迭代模板,代码如下:
bool isValidBST3(TreeNode* root)
{stack<TreeNode*> st;TreeNode* cur = root; //当前遍历指针TreeNode* pre = NULL; //慢一步的指针while(cur != NULL || !st.empty()){if(cur != NULL){st.push(cur);cur = cur->left; //左}else{cur = st.top(); //中st.pop(); if(pre != NULL && cur->val <= pre->val)return false;pre = cur; //跟新慢指针,保存前一个访问的结点cur = cur->right; //右}}return true;
}
二叉搜索树的最小绝对差
力扣原题链接:530. 二叉搜索树的最小绝对差
给定一个二叉搜索树的根节点 root
,返回树中任意两不同节点值之间的最小差值 ,差值是一个正数,其数值等于两值之差的绝对值。
示例:
输入: root = [4, 2, 6, 1, 3]
输出: 1
继续延用上述三种思路:
- 思路1 递归遍历数据到数组后求最小差值
- 思路2 利用双指针在递归遍历时候比较最小差值
- 思路3 利用迭代法与双指针遍历二叉树时比较最小差值
递归 + 数组
转化为: 递归中序遍历到数组,求有序数组中两个数的最小差值
思路1程序实现:
/****************递归法 数据存入数组******************/
vector<int> res;
//中序 递归遍历数据到数组
void traversal(TreeNode* node)
{if(node == NULL)return;traversal(node->left); //左res.push_back(node->val); //中traversal(node->right); //右
}
//求解有序数组的最小差值
int getMinimumDifference(TreeNode* root)
{traversal(root);if(res.size() < 2)return 0;int minDiff = abs(res[1] - res[0]);for(int i = 2; i < res.size(); i++)minDiff = min(minDiff, res[i] - res[i-1]);return minDiff;
}
递归 +双指针
其实在二叉搜素树中序遍历的过程中,我们就可以直接计算了,需要用一个pre节点记录一下cur节点的前一个节点。
代码如下:
/*******************递归双指针法 ***************/
TreeNode* pre = NULL;
int minDiff = INT_MAX;
void traversal(TreeNode* node)
{if(node == NULL)return;traversal(node->left); //左//中if(pre != NULL)minDiff = min(minDiff, node->val - pre->val);pre = node; //跟新慢指针traversal(node->right); //右
}int getMinimumDifference(TreeNode* root)
{traversal(root);return minDiff;
}
迭代法
可以用迭代法模拟二叉树中序遍历,且与遍历普通二叉树模板类似,迭代法中序遍历稍加改动就可以了,在遍历中加入前一个节点的指针,并不断判断更新最小差值即可,代码如下:
/****************迭代法***************/
int getMinimumDifference(TreeNode* root)
{ if(root == NULL)return 0;stack<TreeNode*>st;TreeNode* pre = NULL;TreeNode* cur = root;int minDiff = INT_MAX;while(cur != NULL || !st.empty()){if(cur != NULL){st.push(cur);cur = cur->left; //左}else{cur = st.top(); //中st.pop();if(pre != NULL)minDiff = min(minDiff, cur->val - pre->val);pre = cur;cur = cur->right; //右}}return minDiff;
}
总结
-
遇到在二叉搜索树上求什么最值,求差值之类的,都要思考一下二叉搜索树可是有序的,要利用好这一特点。
-
同时要学会在递归遍历的过程中如何记录前后两个指针,这也是一个小技巧,学会了还是很受用的。
二叉搜索树中的众数
力扣原题链接:501. 二叉搜索树中的众数
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素),如果树中有不止一个众数,可以按 任意顺序 返回。

输入:root = [1,null,2,2]
输出:[2]
如果对于普通的二叉树而言呢?
求二叉树的众数
思路: 如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。
具体步骤如下:
1. 节点数据存map: 至于用前中后序哪种遍历也不重要,因为就是要全遍历一遍,这里采用前序遍历,代码如下:
// map<int, int> key:元素,value:出现频率
void traversal(TreeNode* node,unordered_map<int, int>& mp)
{if(node == NULL)return;mp[node->val]++; //中 统计元素频率traversal(node->left, mp); //左 traversal(node->right, mp); //右return;
}
2. 把统计的出来的出现频率(即map中的value)排个序
- C++中只能根据map的key进行排序,无法对value排序
- 所以要把map转化数组即vector,再进行排序,当然vector里面放的也是对组 pair<int, int>类型的数据,第一个int为元素,第二个int为出现频率。
代码实现如下:
//排序仿函数
bool static cmp(const pair<int,int>& a, const pair<int, int> & b)
{return a.second > b.second;
}//把map转化为vector,其中存放的数据类型是对组
vector<pair<int,int>> vec(resMap.begin(), resMap.end());
sort(vec.begin(), vec.end(), cmp); // 给频率排个序
3. 取前面高频的元素
此时数组vector中已经是存放着按照频率排好序的pair,那么把前面高频的元素取出来就可以了,代码如下:
for(int i = 0; i < vec.size(); i++)
{if(vec[i].second == vec[0].second) //判断出现频率最大的几个数result.push_back(vec[i].first); //把频率最大的几个数存入结果数组elsebreak;
}
整体C++代码如下:
/*思路1: 遍历树 将数值存入map, 并统计出现的次数,根据次数排序,返回频率最大的几个数*/
// map<int, int> key:元素,value:出现频率
void traversal(TreeNode* node,unordered_map<int, int>& mp)
{if(node == NULL)return;mp[node->val]++; //中 统计元素频率traversal(node->left, mp); //左 traversal(node->right, mp); //右return;
}
//排序仿函数
bool static cmp(const pair<int,int>& a, const pair<int, int> & b)
{return a.second > b.second;
}vector<int> findMode(TreeNode* root)
{vector<int> result; //结果容器unordered_map<int, int> resMap; //存放键值对的mapif(root == NULL)return result;traversal(root, resMap);//map 转 对组类型的vectorvector<pair<int,int>> vec(resMap.begin(), resMap.end());sort(vec.begin(), vec.end(), cmp); // 给频率排个序for(int i = 0; i < vec.size(); i++){if(vec[i].second == vec[0].second) //判断出现频率最大的几个数result.push_back(vec[i].first); //把频率最大的几个数存入结果数组elsebreak;}return result;
}
二叉搜索树的众数
递归法
既然是搜索树,它中序遍历就是有序的。
中序遍历代码如下:
void searchBST(TreeNode* cur) {if (cur == NULL) return ;searchBST(cur->left); // 左(处理节点) // 中searchBST(cur->right); // 右return ;
}
-
遍历有序数组的元素出现频率,从头遍历,相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。
-
弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。
-
而且初始化的时候 pre = NULL,这样当 pre 为 NULL 时候,我们就知道这是比较的第一个元素。
代码如下:
if (pre == NULL) { // 第一个节点count = 1; // 频率为1
} else if (pre->val == cur->val) { // 与前一个节点数值相同count++;
} else { // 与前一个节点数值不同count = 1;
}
pre = cur; // 更新上一个节点
如何知道最大频率呢?
应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合,但这种方式遍历了两遍数组。
如何遍历一遍,把频率最大的放到数组里面呢?
每次频率最大的数都放进结果集,若出现更大的频率的数,更新整个结果集(清空结果集),并把最大频率的数放到结果集
- 频率
count
等于maxCount
(最大频率),把这个元素加入到结果集中if (count == maxCount) { // 如果和最大值相同,放进result中result.push_back(cur->val); }
- 频率
count
大于maxCount
的时候,不仅要更新maxCount
,而且要清空结果集,因为结果集之前的元素都失效了if (count > maxCount) { // 如果计数大于最大值maxCount = count; // 更新最大频率result.clear(); // 很关键的一步,不要忘记清空result,之前result里的元素都失效了result.push_back(cur->val); }
完整代码如下:(只需要遍历一遍二叉搜索树,就求出了众数的集合)
/********思路2 利用二叉搜索树 递归遍历 + 双指针遍历********/TreeNode* pre = NULL;
int maxCnt = 0; //最大频率
int cnt = 0; //单个元素频率
vector<int> res; //结果集
void traversal(TreeNode* node)
{if(node == NULL)return;traversal(node->left); //左if(pre == NULL) //最初cnt = 1;else if(pre->val == node->val) //数值相同 累加cnt++; else //新数据cnt = 1;pre = node; //更新旧指针/**********核心细节代码区****************/if(cnt == maxCnt) //收获频率最大的数res.push_back(node->val);else if(cnt > maxCnt) //更新最大频率{maxCnt = cnt;res.clear(); //☆有更长的频率 之前存入的结果都作废res.push_back(node->val); //当前新的长度的数记得存进去}traversal(node->right); //右
}vector<int> findMode(TreeNode* root)
{traversal(root);return res;
}
迭代法
只要把中序遍历转成迭代,中间节点的处理逻辑完全一样(直接粘贴代码的即可)
/********思路3 迭代法********/
vector<int> findMode(TreeNode* root)
{int maxCnt = 0; //最大频率int cnt = 0; //单个元素频率vector<int> res;if(root == NULL)return res;stack<TreeNode*> st;TreeNode* pre = NULL;TreeNode* cur = root;while(cur != NULL || !st.empty()){if(cur != NULL){st.push(cur);cur = cur->left; //左}else{cur = st.top(); //中st.pop(); if(pre == NULL) //最初cnt = 1;else if(pre->val == cur->val) //数值相同 累加cnt++; else //新数据cnt = 1;pre = cur; //更新旧指针/**********核心细节代码区****************/if(cnt == maxCnt) //收获频率最大的数res.push_back(cur->val);else if(cnt > maxCnt) //更新最大频率{maxCnt = cnt;res.clear(); //☆有更长的频率 之前存入的结果都作废res.push_back(cur->val); //当前新的长度的数记得存进去}cur = cur->right; //右}}return res;
}