平衡树算是比较进阶一点的数据结构,这里只是对学习简单平衡树的记录
BST——二叉查找树
书上说,二叉树最重要的两种数据结构的性质有两种:堆性质 和 BST性质
二叉查找树满足以下性质:
- 每个节点都有一个关键值
- 每个节点的关键值大于其左子节点的关键值
- 每个节点的关键值小于其右子节点的关键值
由于每个节点是顺序插入的,所以我们容易知道,一个节点的左子树的所有节点一定小于该节点,右子树的所有节点一定大于该节点
后继
大于某个节点的最小节点
前驱
小于某个节点的最大节点
BST的主要操作
- 检索
- 插入
- 求前驱后继等
在学习平衡树初期,对于普通平衡树我打了一个三百多行的BST实现代码,实现的细节和原理相当复杂,不便于修改和模板式记忆,所以我在写这篇博客的同时,将根据资料书写出更为简单的BST实现
代码实现
#include <cstdio>
#include <iostream>
#include <cmath>
#include <ctime>
#define MAX_ 0x7fffffff
using namespace std;
struct BST
{
int l;
int r;
int val;
int size;
int num;
int dat;
} a[100000];
int tot;
int root;
int NEW(int val)
{
a[++tot].val = val;
a[tot].num = 1;
a[tot].size = 1;
a[tot].dat = rand();
return tot; // return the id of a new node
}
void update(int p)
{
a[p].size = a[a[p].l].size + a[a[p].r].size + a[p].num;
}
void Build()
{
NEW(-MAX_);
NEW(MAX_); // building with an infinity node
// this is a step that needed for the treap
// to avoid rotate the infinity node to go down
a[1].r = 2;
root = 1;
update(root);
}
int getVal_byRank(int p, int rank)
{
if (p == 0)
{
return -1; // no such a rank
}
if (rank <= a[a[p].l].size)
{
return getVal_byRank(a[p].l, rank);
}
else if (rank > a[p].size - a[a[p].r].size)
{
return getVal_byRank(a[p].r, rank - a[a[p].l].size - a[p].num);
}
else
{
return a[p].val;
}
}
int getRank_byVal(int p, int val)
{
if (p == 0)
{
return 0;
}
if (val > a[p].val)
{
return getRank_byVal(a[p].r, val) + a[a[p].l].size + a[p].num;
}
else if (val < a[p].val)
{
return getRank_byVal(a[p].l, val);
}
else
{
return a[a[p].l].size + 1;
}
}
void Rrotate(int &p)
{
int q = a[p].l;
a[p].l = a[q].r;
a[q].r = p;
p = q;
update(a[p].r);
update(p);
}
void Lrotate(int &p)
{
int q = a[p].r;
a[p].r = a[a[p].r].l;
a[q].l = p;
p = q;
update(a[p].l);
update(p);
}
void insert(int &p, int val) // p is the reference of the father's pointer towards this node
{
// cout << p << ' ' << a[p].l << ' ' << a[p].val << ' ' << val << endl;
// as well it's the id of this node
if (p == 0)
{
p = NEW(val); // there is no such a node so create one and set the father's pointer
return;
}
if (val == a[p].val)
{
a[p].num++;
update(p);
return;
}
else if (val < a[p].val)
{
insert(a[p].l, val); // this way,we can set the l while passing down the id of the current node' child
if (a[p].dat < a[a[p].l].dat)
{
// cout << "Y";
Rrotate(p);
}
}
else if (val > a[p].val)
{
insert(a[p].r, val);
if (a[p].dat < a[a[p].r].dat)
{
// cout << "N";
Lrotate(p);
}
}
update(p);
}
int getPre(int val)
{
int ans = -1e9;
int p = root;
while (true)
{if (p == 0)
{
return ans;
}
if (a[p].val < val)
{
ans = max(ans, a[p].val);
}
if (a[p].val == val)
{
if (a[p].l == 0)
{
return ans;
}
p = a[p].l;
while (a[p].r != 0)
{
p = a[p].r;
}
return a[p].val;
}
p = a[p].val > val ? a[p].l : a[p].r;
}
}
int getNext(int val)
{
int ans = 1e9;
int p = root;
while (true)
{
if (p == 0)
{
return ans;
}//this place must be placed before the "min"
//otherwise the mininus is must be '0'
if (a[p].val > val)
{
ans = min(ans, a[p].val);
}
if (a[p].val == val)
{
if (a[p].r == 0)
{
return ans;
}
p = a[p].r;
while (a[p].l != 0)
{
p = a[p].l;
}
return a[p].val;
}
p = a[p].val > val ? a[p].l : a[p].r;
}
}
void remove(int &p, int val)
{
if (p == 0)
{
return;
}
if (val == a[p].val)
{
if (a[p].num > 1)
{
a[p].num--;
update(p);
return;
}
if (a[p].l || a[p].r)
{
if (a[p].r == 0 || a[a[p].l].dat > a[a[p].r].dat)
{
Rrotate(p);
remove(a[p].r, val);
}
else
{
Lrotate(p);
remove(a[p].l, val);
}
update(p);
}
else
{
p = 0;
}
return;
}
a[p].val > val ? remove(a[p].l, val) : remove(a[p].r, val);
update(p);
}
void tra(int p)
{
if (p == 0)
return;
cout << a[p].val << ' ' << a[p].size << endl;
tra(a[p].l);
tra(a[p].r);
}
int main()
{
Build();
srand(time(0));
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
int op, x;
cin >> op >> x;
// cout << op << ' ';
switch (op)
{
case 1:
insert(root, x);
break;
case 2:
remove(root, x);
break;
case 3:
cout << getRank_byVal(root, x) - 1 << endl;
break;
case 4:
cout << getVal_byRank(root, x + 1) << endl;
break;
case 5:
cout << getPre(x) << endl;
break;
case 6:
cout << getNext(x) << endl;
break;
case 7:
tra(root);
cout << tot << endl;
break;
default:
break;
}
}
}
- 在以上代码中,所有的传递的p为引用时,说明传递的参数必须是对应父节点的左右节点,这样在修改p时可以一同修改父节点的子节点信息了
- 对于
insert()
,remove()
之类的操作需要更新祖先节点的size大小,所以需要采用递归的方式修改,而对于查询之类的操作不需要更新祖先节点数值,所以采用循环的方式更加快速 - 这是一道平衡树的模板题,但是仍然免不了将近三百行的代码