目录
二叉查找树
➢查找、插入、删除平均时间复杂度O(logn)
➢最坏时间复杂度O(n)
//基本操作
//插入
void insert(BSTnode* &t, int K){
if (t == NULL) t = new BSTnode(K); //插入尾结点的孩子指针的引用
else if (K < t->key) insert(t->left, K);
else if (K > t->key) insert(t->right, K);
}
//删除:调整K结点的值,删除替代K的结点;
//1:K只有1个孩子,则子承父业(让其子结点替换K)。
//2:K有2个孩子,则让其右子树最小结点s替换K , 然后删除原结点s
//3:K是叶结点,直接删除
void remove(BSTNode* &t, int K) {//在以t为根的BST中删关键词等于K的结点
if(t==NULL) return;
if(K<t->key) remove(t->left, K); //在左子树中删K
else if(K>t->key) remove(t->right, K); //在右子树中删K
else if(t->left!=NULL && t->right!=NULL){
BSTNode* r=t, *s=t->right;
while(s->left!=NULL) {//寻找右子树的最小结点
// 让s为t右子树中根序列第一个结点,满足顺序性
r=s; r指向s的祖先
s=s->left;
}
t->key=s->key;//让右子树最小结点的值取代t
if(r==t) r->right=s->right;
else r->left=s->right;
delete s;
}
else {
// K是叶子结点/只有左子树/只有右子树;
BSTNode* temp=t;
t=(t->left!= NULL)? t->left:t->right;
delete temp;
}
}
AVL树:
➢一棵满足如下条件的二叉查找树:任意结点的左子树和右子树的高度最多差1。
➢高度平衡树中任意结点p的平衡系数为:p的右子树高度减去左子树高度。高度平衡树任意结点的平衡系数只可能为:-1, 0, 1。
➢AVL树的高度最多比满二叉树高44%
➢AVL树的平均高度为O(logn),因此使插入、删除、查找的最坏时间复杂性均为O(logn)。但删除操作最多需要做O(logn)次旋转
struct AVLnode {
int key;
int height; //以该结点为根的子树高度
AVLnode* left;
AVLnode* right;
AVLnode(int K){
key = K;
height = 0;
left = right = NULL;
}
};
int height(AVLnode *t){return(t==NULL)?-1:t->height;}
int max(int a, int b){return (a>b)? a:b;}
B树-
1.结点结构:包含j个关键词和j+1 个指针;关键词递增排序,pi指向Ki+1
2.m阶B树
① 每个结点中的关键词从左到右递增排列,最多包含m-1个关键词。
② 每个非叶结点有「m/2」 ~ m个子结点;m=7 有4~7个子结点,对应有3-6个关键词
③ 若根结点不是叶结点,至少有2个子结点[保证高度相同] ,有2 ~ m个子结点;包含1 ~ m-1 个关键词
④ 有k个子结点的结点恰好包含k-1个关键词;
⑤ 所有的叶结点在同一层,且不带有信息,叶结点是虚拟的结点, 指向叶结点的指针是空指针。
3.插入
➢在一个包含 j <m−1 个关键词 (子结点数< m )中插入一个新关键词,则把新关键词直接插入该结点中即可
➢若把一个新关键词插入一个已包含m−1个关键词的结点(结点已满),则插入将造成此结点关键词上溢,需进行“分裂”操作。
4.删除:先换后删,下溢借位,不够借就合并
➢先要找到要删除的关键词Ki的位置。
➢换:若Ki不在最底层,将Ki与其“右子树”最小关键词交换。【保证有序】
即真正的删除只发生在最底层
➢ 借:当删除关键词后,若该结点目前包含的关键词个数小于[m/2]-1 (称为下溢)。则要从左兄弟结点中借最大关键词(连同兄弟结点最右方的指针)。若左兄弟不够借,从右兄弟借最小关键词。
➢ 不是直接从兄弟结点借,而是通过父结点中转。
➢ 借的顺序:左顾右盼(优先从左兄弟借)。
➢ 合并
若兄弟结点不够借,即兄弟结点包含的关键词个数等于[m/2]−1,则执行合并操作:把两个兄弟结点的关键词、父结点中指向这两个结点的指针之间的关键词按递增顺序合并到一个新结点中。
➢ 合并的顺序:左顾右盼(优先与左兄弟合并)
➢ 若“合并”操作使上一层结点(父结点)所包含的关键词个数出现不够(下溢)的情况,则对上一层结点继续“借”或“合并”。
➢ “合并”可能会导致上一层发生“合并”,从而可能使“合并”不断向上传播,直至根结点,进而使整个B树减少一层。
--
例1m=5,删除120:
先交换120和134,然后删除120;这时右子树只有1个关键词<2;
向左子树借118 移动到父结点,将父结点的134借给右子树;
150和156交换;发现177所在子树只有一个关键词,向左树借146,发现左子树只剩下134 不够借;没有右兄弟了;只能合并,把父结点的156移下来;发现这时候父结点只剩下118 且不够借,所以把103移下来, 将25 50 103 118合并;
验证二叉查找树
二叉查找树数据特性:左子树<根<右子树 进行check;
思路一:利用中序遍历
存储中根序列后检查是否递增;
利用前驱pre,检查递增;
class Solution
{
public:
void midorder(TreeNode *root, vector<int> &arr)
{
if (root)
{
midorder(root->left, arr);
arr.push_back(root->val);
midorder(root->right, arr);
}
}
bool isValidBST(TreeNode *root)
{
vector<int> arr;
midorder(root, arr);
for (int i = 1; i < arr.size(); i++)
{
if (arr[i] <= arr[i - 1])
return false;
}
return true;
}
};
class Solution
{
public:
TreeNode *pre;
bool isValidBST(TreeNode *root)
{
if (!root)
return true;
if (!isValidBST(root->left))
return false;
if (pre && pre->val >= root->val)
return false;
pre = root;
if (!isValidBST(root->right))
return false;
return true;
}
};
思路2:递归:用lower和upper建立约束条件;
class Solution {
public:
bool helper(TreeNode* root, long long lower, long long upper) {
if (root == nullptr) {
return true;
}
if (root -> val <= lower || root -> val >= upper) {
return false;
}
return helper(root -> left, lower, root -> val) && helper(root -> right, root -> val, upper);
}
bool isValidBST(TreeNode* root) {
return helper(root, LONG_MIN, LONG_MAX);
}
};
不同的二叉查找树
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
举例:给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1 \ / / / \ \ 3 2 1 1 3 2 / / \ \ 2 1 2 3
初始化 dp[0] = 1;
当 n = 2时,dp[2] = 2 = dp[0]dp[1] + dp[1]dp[0];
-root结点有1个左孩子和0个右孩子+root结点有0个左孩子和1个右孩子;
当 n = 3时,dp[3] = 5 = dp[0]dp[2] + dp[1]dp[1] + dp[2]dp[0];
-root结点有0个左孩子和2个右孩子+有2个左孩子和2个右孩子+ 1个左孩子1个右孩子的组合
n个结点有k个孩子和n-k-1个右孩子,每个子树的数量递归求解
int numTrees(int n)
{
int dp[n + 1];
memset(dp, 0, sizeof(dp));
dp[0] = dp[1] = 1;
for (int i = 2; i <= n; ++i)
{
for (int j = 0; j < i; ++j)
{
dp[i] += dp[j] * dp[i - j - 1];
}
}
return dp[n];
}
class Solution
{
public:
int numTrees(int n)
{
if (n < 2)
return 1;
else
{
int i, sum = 0, left, right;
for (i = 0; i < n; i++)
{
left = i;
right = n - i - 1;
sum += numTrees(left) * numTrees(right);
}
return sum;
}
}
};
leetcode 99:
给你二叉搜索树的根节点
root
,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?
示例 1:
输入:root = [1,3,null,null,2] 输出:[3,1,null,null,2] 解释:3 不能是 1 左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。示例 2:
输入:root = [3,1,4,null,null,2] 输出:[2,1,4,null,null,3] 解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 2 和 3 使二叉搜索树有效。提示:
- 树上节点的数目在范围
[2, 1000]
内-231 <= Node.val <= 231 - 1
思路:找到交换的结点:在中根序列中找到非递增的两个结点
第一个节点:中序遍历第一次出现前一个节点大于后一个节点,选取前一个节点。
第二个节点:在第一个节点找到之后,后面出现前一个节点大于后一个节点,选择后一个节点。
class Solution
{
public:
TreeNode *pre = NULL;
TreeNode *one = NULL;
TreeNode *two = NULL;
bool search(TreeNode *root)
{
if (root == NULL)
return false;
if (search(root->left))
return true;
if (pre != NULL && pre->val > root->val)
{
if (one == NULL)
{
one = pre;
two = root;
}else{
two = root;
}
// 1 3 2 4
//3 2 1
}
pre = root;
if (search(root->right))
return true;
return false;
}
void recoverTree(TreeNode *root)
{
search(root);
swap(one->val, two->val);
}
};