下图无比现实
二叉查找树又名二叉排序树、二叉搜索树、BST(Binary Search Tree)。首先知道它的基础数据结构。
它是一颗二叉树(带权),通常会用结构体实现(数组或者指针——个人比较喜欢用数组,之后的代码也是用数组来实现的)。
为什么用数组呢?
因为写的舒服,一个字爽
struct node{ int l, r, val; //左孩子,右孩子,权值 }tree[SIZE];
int tot; //记录共有几个节点
int root; //记录根节点
不过它有一个性质:对于任意一个非叶子节点,它的左子树所有的节点的权值都<=他的权值,它的右子树所有的节点的权值 >= 它的权值(等于号根据题目各异)。
其次它可以实现以下功能:
• 树的建立
·检索
• 插入关键码为x的节点
• 求关键码为x的节点的前驱
• 求关键码为x的节点的后继
• 删除关键码为x的节点
1,BST的建立
通常会插入两个虚拟节点INF和-INF,防止越界。
//存放一个权值为v的节点 int make_new(int v) { tree[++tot].val = v; return tot; //返回存放的节点编号 } //建树,插入-INF,INF两个节点 void build() { root = make_new(-INF); tree[root].r = make_new(INF); //INF > -INF 所以INF应是-INF的右孩子 }
2,BST的检索
在BST中检索是否存在权值为v的节点。
设变量p等于根节点root,执行以下过程:
1.若p的权值等于v,则已经找到
2.若p的权值大于v
a.若p的左子节点为空,则说明不存在v
b.若p的左子节点不空,在p的左子树中递归进行检索
3.若p的权值小于v
a.若p的右子节点为空,则说明不存在v
b.若p的右子节点不空,在p的右子树中递归进行检索
void find(int p, int v) { if (p == 0) { return 0; // 没有找到 } if (tree[p].val == v) return p; //找到了 else if (tree[p].val > v) find(tree[p].l, v); // p的权值 > v 在左子树中找 else find(tree[p].r, v); // p的权值 < v 在右子树中找 }
3,BST的插入
插入其实跟检索差不多,只不过找的合适的位置后要把元素插入进去。
用一个引用记录其父节点的l或r值,这样就可以轻松插入了。
void insert(int &p, int v) { if (p == 0) { //插入 p = make_new(v); // p是引用,其父节点的l或r值会随着改变 return 0; } if (tree[p].val == v) return p; //找到了 else if (tree[p].val > v) find(tree[p].l, v); // p的权值 > v 在左子树中插 else find(tree[p].r, v); // p的权值 < v 在右子树中插 }
特别提醒:这个递归不能用while循环替代,否则root的值会改变
4,BST的后继
首先检索v,然后开始分类讨论:
设v的节点编号为p。
I p有右子树,则它的后继一定在它的右子树中否则它的右子树将不是“它的右子树”。
而且一定是它右孩子后一直往左孩子找。
II 若p没有右子树,则它的后继就是它的直系祖先中 > 它且最小的一个(在检索的时候记录)
int get_next(int v) { int ret = 2;//tree[2].val == INF //检索 int p = root; while (p) { if (v == tree[p].val) { //找到了 if (tree[p].r) { //有右子树 p = tree[p].r; while (tree[p].l) p = tree[p].l; //一直往左找 ret = p; } break; } if (tree[p].val > v && tree[p].val < tree[ret].val) ret = p; //检索时记录 p = tree[p].val > v ? tree[p].l : tree[p].r; } //这里不是引用所以可以用while循环 return tree[ret].val; }
温馨提示:没有后继(最大值会返回INF)
5,BST找前驱
更后继差不多,不过是左子树一直往右走,纪录时也是<它最大的一个。
int get_pre(int v) { int ret = 1;//tree[1].val == -INF int p = root; while (p) { if (v == tree[p].val) { if (tree[p].l) { p = tree[p].l; while (tree[p].r) p = tree[p].r; //一直网友找 ret = p; } break; } if (tree[p].val < v && tree[p].val > tree[ret].val) ret = p; //小于且最大 p = tree[p].val > v ? tree[p].l : tree[p].r; } return tree[ret].val; //若没有前驱返回-INF }
6,BST中删除节点
首先也是先检索它得到节点p
若p的子节点个数小于2,则直接删除p,并令p的子节点代替p的位置,与p 的父节点相连。
若p既有左子树又有右子树,则在BST中求出v的后继节点neat。
因为neat 没有左子树,所以可以直接删除next,并令next的右子树代替next的位置。
最后, 再让next节点代替p节点,删除p即可。
void remove(int &p, int v) { if (p == 0) { // not exit return; } if (tree[p].val == v && tree[tree[p].r].val != v) { // found it // remove if (!tree[p].l) { p = tree[p].r; } else if (!tree[p].r) { p = tree[p].l; } else { int next = tree[p].r; while (tree[next].l) next = tree[next].l; remove(root, tree[next].val); tree[next].l = tree[p].l, tree[next].r = tree[p].r; p = next; } return; } if (tree[p].val <= v) { remove(tree[p].r, v); } else { remove(tree[p].l, v); } }
六项基本操作就讲完了,贴一份完整代码,有建议大家可以提出来我再改进。
#include <bits/stdc++.h> using namespace std; const int SIZE = 100000; struct node{ int l, r, val; }tree[SIZE]; int tot, root; int make_new(int v) { tree[++tot].val = v; return tot; } void build() { root = make_new(-INF); tree[root].r = make_new(INF); } void find(int p, int v) { if (p == 0) { return 0; } if (tree[p].val == v) return p; else if (tree[p].val > v) find(tree[p].l, v); else find(tree[p].r, v); } void insert(int &p, int v) { if (p == 0) { p = make_new(v); return 0; } if (tree[p].val == v) return p; else if (tree[p].val > v) find(tree[p].l, v); else find(tree[p].r, v); } int get_next(int v) { int ret = 2; int p = root; while (p) { if (v == tree[p].val) { if (tree[p].r) { p = tree[p].r; while (tree[p].l) p = tree[p].l; ret = p; } break; } if (tree[p].val > v && tree[p].val < tree[ret].val) ret = p; p = tree[p].val > v ? tree[p].l : tree[p].r; } return tree[ret].val; } int get_pre(int v) { int ret = 1; int p = root; while (p) { if (v == tree[p].val) { if (tree[p].l) { p = tree[p].l; while (tree[p].r) p = tree[p].r; ret = p; } break; } if (tree[p].val < v && tree[p].val > tree[ret].val) ret = p; p = tree[p].val > v ? tree[p].l : tree[p].r; } return tree[ret].val; } void remove(int &p, int v) { if (p == 0) { return; } if (tree[p].val == v && tree[tree[p].r].val != v) { if (!tree[p].l) { p = tree[p].r; } else if (!tree[p].r) { p = tree[p].l; } else { int next = tree[p].r; while (tree[next].l) next = tree[next].l; remove(root, tree[next].val); tree[next].l = tree[p].l, tree[next].r = tree[p].r; p = next; } return; } if (tree[p].val <= v) { remove(tree[p].r, v); } else { remove(tree[p].l, v); } } int read() { int ret = 0, f = 1; char ch = getchar(); while ('0' > ch || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while ('0' <= ch && ch <= '9') { ret = ret * 10 + ch - '0'; ch = getchar(); } return ret * f; } void write(int x) { if (x >= 10) write(x / 10); putchar(x % 10 + '0'); } int main() { build(); int n; n = read(); while (n--) { int opt, x; opt = read(); x = read(); switch (opt) { case 1: insert(root, x); break; case 2: remove(root, x); break; case 3: write(get_pre(x)); putchar('\n'); break; case 4: write(get_next(x)); putchar('\n'); break; } } }