浅谈BST
前言
最近不顺心的事情有点多,再加上赶ptaL2 的题单,很久没做知识总结了 ,现在pta的题目告一段落,参考了某大佬 (某卷王) 总结的知识点,鸣谢大佬!总结一下BST问题的知识点,供以后参考。
封面:
站在巨人 (卷王) 的肩上,才能看 (卷) 的更远
二叉搜索树的性质
一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点
- 其左子树中所有结点的键值小于该结点的键值;
- 其右子树中所有结点的键值大于等于该结点的键值;
- 其左右子树都是二叉搜索树。
乍一看,其实感觉和二叉堆很像,不过二叉堆左右儿子之间没有可比性,因为二叉堆是根据与父亲节点大小关系建堆的,兄弟节点似乎无法比较,所以同一个序列,以不同顺序输入的生成的二叉堆序列也自然不同。
二叉搜索树也有类似的性质,但是二叉搜索树输入的节点不会发生位置交替,这和二叉堆边插入边排序不同,所以,同一组数字不同顺序输入二叉搜索树也会导致二叉搜索树结构不同。假如输入的数字过于玄学,会导致树的左右子树不平衡,如果将两个子树平衡,就变成了平衡树(Treap)
二叉搜索树的操作
必要的初始化
#define maxn 100000
#define INF -1
int BST[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
for (int i = 0; i < maxn; i++)
BST[i] = INF;
}
建树
void build(int root, int x)
{
if (BST[root] == INF)
{
BST[root] = x;
return;
}
if (BST[root] >= x) //说明该节点应该在其左边
build(root * 2, x); //左儿子比父亲小,右儿子更大
else
build(root * 2 + 1, x); //右儿子比父亲大,往右边放
}
前序遍历
void pre_order(int root)
{
if (BST[root] == INF)
return;
cout << BST[root] << " ";
pre_order(root * 2);
pre_order(root * 2 + 1);
}
中序遍历
中序遍历其实就是序列从小到大排序
void in_order(int root)
{
if (BST[root] == INF)
return;
in_order(root * 2);
cout << BST[root] << " ";
in_order(root * 2 + 1);
}
后序遍历
void post_order(int root)
{
if (BST[root] == INF)
return;
post_order(root * 2);
post_order(root * 2 + 1);
cout << BST[root] << " ";
}
找最大
int get_max(int root) //一直往右走
{
if (BST[root * 2 + 1] == INF)
return BST[root];
else
return get_max(root * 2 + 1);
}
找最小
int get_min(int root) //一直往左走
{
if (BST[root * 2] == INF)
return BST[root];
else
return get_min(root * 2);
}
删除节点
void modify(int root, int x) //删除操作
{
if (x > BST[root]) //往右找
modify(root * 2 + 1, x);
else if (x < BST[root]) //往左找
modify(root * 2, x);
else //找到了,开始修改
{
if (BST[2 * root] == INF && BST[root * 2 + 1] == INF)
{
BST[root] = INF; //没有儿子,直接封杀
}
else if (BST[2 * root] == INF && BST[root * 2 + 1] != INF) //左子树存在,右子树无
{
BST[root] = BST[root * 2 + 1];
modify(root * 2 + 1, BST[root * 2 + 1]);
}
else if (BST[2 * root] != INF && BST[root * 2 + 1] == INF) //左子树空,右子树存在
{
BST[root] = BST[root * 2];
modify(root * 2, BST[root * 2]);
}
else if (BST[root * 2] != INF && BST[root * 2 + 1] != INF)
{
//有两种修改方式!
//第一种是修改用后驱节点(右子树最小节点更换)
//第二种是用前驱节点修改(左子树最大节点更换)
//这里演示前者
int post_point = get_min(root * 2 + 1);
BST[root] = post_point;
modify(root * 2 + 1, post_point);
}
}
}
汇总代码,实弹演习
样例
7
8 10 11 8 6 7 5
#include <bits/stdc++.h>
using namespace std;
#define maxn 100000
#define INF -1
int BST[maxn];
void build(int root, int x)
{
if (BST[root] == INF)
{
BST[root] = x;
return;
}
if (BST[root] >= x) //说明该节点应该在其左边
build(root * 2, x); //左儿子比父亲小,右儿子更大
else
build(root * 2 + 1, x); //右儿子比父亲大,往右边放
}
void pre_order(int root)
{
if (BST[root] == INF)
return;
cout << BST[root] << " ";
pre_order(root * 2);
pre_order(root * 2 + 1);
}
void in_order(int root)
{
if (BST[root] == INF)
return;
in_order(root * 2);
cout << BST[root] << " ";
in_order(root * 2 + 1);
}
void post_order(int root)
{
if (BST[root] == INF)
return;
post_order(root * 2);
post_order(root * 2 + 1);
cout << BST[root] << " ";
}
void floor_order(int root)
{
queue<int> q;
q.push(root);
while (q.empty() == false)
{
int fa = q.front();
cout << BST[fa] << " ";
q.pop();
if (BST[fa * 2] != INF)
q.push(fa * 2);
if (BST[fa * 2 + 1] != INF)
q.push(fa * 2 + 1);
}
}
int get_min(int root) //一直往左走
{
if (BST[root * 2] == INF)
return BST[root];
else
return get_min(root * 2);
}
int get_max(int root) //一直往右走
{
if (BST[root * 2 + 1] == INF)
return BST[root];
else
return get_max(root * 2 + 1);
}
int search(int now, int x) //查找某一个特殊的元素
{
if (x > BST[now])
return search(now * 2 + 1, x);
else if (x < BST[now])
return search(now * 2, x);
else
return now;
}
void modify(int root, int x) //删除操作
{
if (x > BST[root]) //往右找
modify(root * 2 + 1, x);
else if (x < BST[root]) //往左找
modify(root * 2, x);
else //找到了,开始修改
{
if (BST[2 * root] == INF && BST[root * 2 + 1] == INF)
{
BST[root] = INF; //没有儿子,直接封杀
}
else if (BST[2 * root] == INF && BST[root * 2 + 1] != INF) //左子树存在,右子树无
{
BST[root] = BST[root * 2 + 1];
modify(root * 2 + 1, BST[root * 2 + 1]);
}
else if (BST[2 * root] != INF && BST[root * 2 + 1] == INF) //左子树空,右子树存在
{
BST[root] = BST[root * 2];
modify(root * 2, BST[root * 2]);
}
else if (BST[root * 2] != INF && BST[root * 2 + 1] != INF)
{
//有两种修改方式!
//第一种是修改用后驱节点(右子树最小节点更换)
//第二种是用前驱节点修改(左子树最大节点更换)
//这里演示前者
int post_point = get_min(root * 2 + 1);
BST[root] = post_point;
modify(root * 2 + 1, post_point);
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
for (int i = 0; i < maxn; i++)
BST[i] = -1;
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
int remp;
cin >> remp;
build(1, remp);
}
pre_order(1);
cout << endl;
post_order(1);
cout << endl;
in_order(1);
cout << endl;
cout << get_max(1) << endl;
cout << get_min(1) << endl;
modify(1, 8);
in_order(1);
cout << endl;
return 0;
}
运行结果
插一道题
这道题我是参考LP大佬的解法,鸣谢大佬!解法很巧妙,我们看题,它题目告诉我们它输入的序列有可能是正常的二叉搜索树,也可能是镜像的,那咋办捏?
判断两遍就可以了!先用当正常的遍历一下,试试水,如果成了就直接pass,不成了的话就用镜像的方式遍历一次
那么,我们怎么判断一个遍历是不是一个二叉搜索树捏?
以正常的为例子!
看图!
字有点丑不好意思我的锅
它生成的前序遍历应该是:
你会发现:对一段前序遍历,第一个元素是根节点,蓝色箭头之前到根节点都是小于根节点的!而绿色箭头都是大于根节点的!
而这两个箭头指的也是根节点的左右子树!
所以直接按这个规律进行判断!判断成功,就判断它的子树是否满足!
AC代码
#include <bits/stdc++.h>
using namespace std;
#define maxn 100000
vector<int> ans;
int pre[maxn];
void judge1(int start, int end) //判断是否是正序
{ // start是根节点
if (start > end)
return;
int node1 = start + 1;
int node2 = end;
while (node1 <= end && pre[node1] < pre[start])
node1++; //从前往后寻找比根节点小的最后一个值
while (node2 > start && pre[node2] >= pre[start])
node2--; //从后往前寻找比根节点大的最后一个值
node1--; //多加了
node2++; //多减了
// cout<<node1<<" "<<node2<<endl;
if (node2 != node1 + 1) //只有两个指针中间只差一个(根节点)
return;
judge1(start + 1, node1); //验证左子树
judge1(node2, end); //查右子树
ans.push_back(pre[start]); //后序遍历,最后放入
}
void judge2(int start, int end) //判断是否是镜像
{
if (start > end)
return;
int node1 = start + 1;
int node2 = end;
while (node1 <= end && pre[node1] >= pre[start])
node1++;
while (node2 >= start && pre[node2] < pre[start])
node2--;
node1--;
node2++;
if (node2 != node1 + 1)
return;
judge2(start + 1, node1);
judge2(node2, end);
ans.push_back(pre[start]);
}
int main()
{
int num;
scanf("%d", &num);
for (int i = 1; i <= num; i++)
{
scanf("%d", &pre[i]);
}
judge1(1, num); //先正序构造一下,试一下水
if (ans.size() != num) //试水失败,说明可能是镜像,再试一次水
{
ans.clear();
judge2(1, num);
}
if (ans.size() == num)
{
printf("YES\n");
for (int i = 0; i < num; i++)
{
printf("%d", ans[i]);
if (i != num - 1)
printf(" ");
}
}
else
{
printf("NO\n");
}
return 0;
}