二叉搜索树的概念:
- 若它的左子树不空,则左子树上所有结点的值均小于等于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于等于它的根结点的值。
- 任意结点的左、右子树也分别为二叉搜索树。
由上可知,二叉搜索树中序遍历结果是递增的。
依据上面的概念,那如何构建一颗二叉搜索树呢?
确定一个根结点,然后递归去构建其左右子树。
// build(1, n); // [1, n]
TreeNode* build(int start, int end) {
if (start > end) return nullptr;
int root_val = start + (end - start) / 2; // 根节点的值
TreeNode* root = new TreeNode(root_val);
root->left = build(start, root_val - 1); // 递归构建左右子树
root->right = build(root_val + 1, end);
return root;
}
下面重点讲讲几道leetcode上关于二叉搜索树的经典例题吧。
二叉搜索树的增删改查
对于插入新的节点或者删除某个节点,我们比照这个节点值和当前搜索的节点值,通过判断大小关系,来移动搜索节点向左孩子或者右孩子移动。
递归三要素: 递归函数含义,递归退出条件, 递归状态转移。
701. 二叉搜索树中的插入操作
// 递归函数含义: 向root为根节点的二叉搜索树中插入值为val的节点,并返回根节点
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
if (val > root->val) root->right = insertIntoBST(root->right, val);
else root->left = insertIntoBST(root->left, val);
return root;
}
// 迭代
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (root == nullptr) return new TreeNode(val);
TreeNode* node = root;
while (node) {
if (val > node->val) {
if (node->right == nullptr) {
node->right = new TreeNode(val);
return root;
}
node = node->right;
} else {
if (node->left == nullptr) {
node->left = new TreeNode(val);
return root;
}
node = node->left;
}
}
return root;
}
450. 删除二叉搜索树中的节点
// 递归函数含义: 在以root为根节点的二叉搜索树上删除值为key的节点,
// 并保存二叉搜索树性质不变,返回根节点引用
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == nullptr) return root;
if (root->val == key) {
if (root->left == nullptr && root->right == nullptr) return nullptr;
// 左孩子右孩子一边存在另一边不存在时,返回存在的一边
else if (root->left == nullptr) return root->right;
else if (root->right == nullptr) return root->left;
else {
// 左右孩子都存在时,优先让右孩子来替换(右孩子中的最小值)
// 也可以选左孩子中最大值
TreeNode* min_node = getMin(root->right);
root->val = min_node->val;
root->right = deleteNode(root->right, min_node->val);
}
} else if (root->val > key) {
// 在root的左子树中删除值为key的节点。
// root的左子树需要调整,root->left指向节点会变化。
// 在以root->left为根节点的子树中删除值为key的节点,并返回根节点
root->left = deleteNode(root->left, key);
} else {
root->right = deleteNode(root->right, key);
}
return root;
}
TreeNode* getMin(TreeNode* node) {
if (node == nullptr) return node;
TreeNode* cur = node;
while (cur->left) cur = cur->left;
return cur;
}
96. 不同的二叉搜索树
95. 不同的二叉搜索树 II
给定一个整数n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 96题需要满足条件的二叉搜索树个数,95题需要所有满足条件的二叉搜索树的结构情况。
在构建二叉搜索树时,我们只要确定好根节点,就可以依据左孩子右孩子和根节点值得大小关系递归构建左子树和右子树。
以1为根节点,则2..n构成右子树。
以2为根节点,则1构成左子树,3..n构成右子树。
...
在求解96题时,只要遍历1到n,看左子树和右子树分别由多少种构成情况,左子树构成情况数乘右子树构成情况数就是确定了根节点后的值。
同理95题中,我们就要把看左子树和右子树分别由多少种构成情况提前保存起来,类似笛卡儿积重组。
// 以下为95题代码
vector<TreeNode*> generateTrees(int n) {
vector<TreeNode*> res;
if (n < 1) return res;
return build(1, n);
}
// 递归函数含义: 返回以[start, end]为节点的二叉搜索树 构成情况
vector<TreeNode*> build(int start, int end) {
vector<TreeNode* > res;
if (start > end) {
res.push_back(nullptr);
return res;
}
for (int i = start; i <= end; i++) {
vector<TreeNode*> left = build(start, i - 1);
vector<TreeNode*> right = build(i + 1, end);
for (auto left_item : left) {
for (auto right_item : right) { // 笛卡儿积
TreeNode* root = new TreeNode(i);
root->left = left_item;
root->right = right_item;
res.push_back(root);
}
}
}
return res;
}
99. 恢复二叉搜索树
题目中说明二叉搜索树中的两个节点被错误地交换。先前我们知道二叉搜索树中序遍历结果是递增的,那说明现在错误交换后的二叉搜索树中序遍历结果不是递增的。
假设原先正确的二叉搜索树中序遍历结果: [1, 2, 3, 4, 5,6]
交换两个节点,则有以下两种情况:
- 交换相邻的两个节点: 例如 [1, 2, 3, 4, 6, 5] // 需要交换 6 5
- 交换不相邻的两个节点: 例如 [1, 5, 3, 4, 6, 2] // 需要交换 5 2
综上,我们只要找到两个需要交换的值。按照题目要求不改变二叉搜索树原来的结果,那我们直接交换这两个值就解决了这个问题。
class Solution {
public:
void recoverTree(TreeNode* root) {
// 二叉搜索树中序遍历结果是递增的
TreeNode* node = root;
getInOrder(node);
// 两个节点被错误交换,通过num来判断这两个节点是否是相邻的
int n = in_order.size(), value1 = 0, value2 = 0, num = 0;
for (int i = 0; i < n; i++) {
if (i + 1 < n && in_order[i] > in_order[i + 1]) {
if (num == 0) {
num++;
value1 = in_order[i];
value2 = in_order[i + 1];
} else {
value2 = in_order[i + 1];
break;
}
}
}
// 遍历二叉搜索树,不改变其结构的情况下恢复这棵树,则直接交换value1 value2的值
recBuild(root, value1, value2);
}
void recBuild(TreeNode* node, int value1, int value2) {
if (node == nullptr) return;
recBuild(node->left, value1, value2);
if (node->val == value1) node->val = value2;
else if (node->val == value2) node->val = value1;
recBuild(node->right, value1, value2);
}
void getInOrder(TreeNode* root) {
if (root == nullptr) return;
getInOrder(root->left);
in_order.emplace_back(root->val);
getInOrder(root->right);
}
private:
vector<int> in_order;
};