二叉搜索树第二期
对于 BST 的大小特性,可以在二叉树中做类似二分搜索的操作,搜索一个元素的效率很高。
代码逻辑如下:
void BST(TreeNode* root, int target) {if (root->val == target) {// 找到目标,逻辑} else if (root->val < target) {BST(root->right, target);} else {BST(root->left, target);}}
01 判断 BST 的合法性
注意,BST 的每个节点应该小于右子树的所有节点。按照下面代码逻辑是不正确的。
bool isValidBST(TreeNode* root) {if (root == nullptr)return true;if (root->left != nullptr && root->val <= root->left->val)return false;if (root->right != nullptr && root->val >= root->right->val)return false;return isValid(root->left) && isValid(root->right);}
出现的问题在于,对于每一个节点 root,代码只检查了它的左右孩子节点是否符合左小右大的原则;但是根据 BST 的定义,root 的整个左子树都要小于 root->val,整个右子树都要大于 root->val。
关键在于,对于某一个节点 root,它只能管得了自己的左右节点,怎么把 root 的约束传递给左右子树?
使用辅助函数,增加函数参数列表,在参数中携带额外信息,将这种约束传递给子树的所有节点。
// 限定以 root 为根的子树节点必须满足 max->val > root->val > min->valbool isValidBST(TreeNode* root, TreeNode* min, TreeNode* max) {// base caseif (root == nullptr)return true;// 若 root->val 不符合 max 和 min 的限制,说明不是合法的 BSTif (min != nullptr && min->val >= root->val)return false;if (max != nullptr && man->val <= root->val)reutrn false;// 限定左子树的最大值是 root->val,右子树的最小值为 root->valbool left = isValidBST(root->left, min, root);bool right = isValidBST(root->right, root, max);return left && right;}
02 二叉搜索树中的搜索
让你在 BST 中搜索值为 target 的节点。函数签名如下:
TreeNode* searchBST(TreeNode* root, int target);
如果是在一棵普通的二叉树中寻找,可以这样写代码:
TreeNode* searchBST(TreeNode* root, int target) {if (root == nullptr)return nullptr;if (root->val == target)return root;// 当前节点没找到就递归去找左右子树TreeNode* left = searchBST(root->left, target);TreeNode* right = searchBST(root->right, target);return left != nullptr ? left : right;}
这样写是正确的,但是这段代码穷举了所有节点,适用于所有普通二叉树。
其实不需要递归地搜索两边,类似二分查找思想,根据 target 和 root->val 大小比较,就能排除一边:
TreeNode* searchBST(TreeNode* root, int target) {if (root == nullptr)return nullptr;if (root->val > target) {return searchBST(root->left, target);}if (root->val < target) {return searchBST(root->right, target);}return root;}
03 在 BST 中插入一个数
对数据结构的操作无非遍历+访问,遍历就是「找」,访问就是「改」。具体到这个问题,插入一个数,就是先找到插入的位置,然后进行插入操作。一旦涉及「改」,函数就要返回 TreeNode* 类型,并且对递归调用的返回值进行接收
TreeNode* insertIntoBST(TreeNode* root, int val) {// 找到空位置插入新节点if (root == nullptr) {TreeNode* node = new TreeNode(val);return node;}if (root->val < val) {root->right = insertIntoBST(root->right, val);}if (root->val > val) {root->left = insertIntoBST(root->left, val);}return root;}
04 在 BST 中删除一个数
这个问题较为复杂,跟插入一样,先找再改,框架如下:
TreeNode* deleteNode(TreeNode* root, int key) {if (root->val == key) {// 进行删除} else if (root->val > key) {// 去左子树找root->left = deleteNode(root->left, key);} else if (root->val < key) {// 去右子树找root->right = deleteNode(root->right, key);}return root;}
找到目标节点,比如说是 A,如何删除这个节点,这是个难点。因为删除节点的同时不能破坏 BST 的性质
情况 1:A 恰好是末端节点,两个子节点都为空,那么可以直接删除

if (root->left == nullptr && root->right == nullptr)return nullptr
情况 2:A 只有一个非空子节点,那么让这个孩子接替自己的位置

// 排除情况 1 之后if (root->left == nullptr) {return root->right;}if (root->right == nullptr)reutrn root->left;
情况 3:A 有两个子节点。为了不破坏 BST 性质,A 必须找到左子树中最大的那个节点,或者右子树最小的节点来接替字节。以第二种为例:

if (root->left != nullptr && root->right != nullptr) {// 找到右子树中最小节点TreeNode* minNode = getMin(root->right);minNode->left = root->left;TreeNode* tmp = root;root = root->right;delete tmp;return root;}
三种情况分析完毕,填入框架:
TreeNode* getMin(TreeNode* root) {while (root->left != nullptr)root = root->left;return root;}TreeNode* deleteNode(TreeNode* root, int key) {if (root == nullptr) {return nullptr;}if (root->val == key) {// 两种 if 将情况 1 和情况 2 都处理了if (root->left == nullptr)return root->right;if (root->right == nullptr)return root->left;// 处理情况 3TreeNode* minNode = getMin(root->right);minNode->left = root->left;TreeNode* tmp = root;root = root->right;delete tmp;return root;} else if (root->val > key) {root->left = deleteNode(root->left, key);} else if (root->val < key) {root->right = deleteNode(root->right, key);}return root;}
