树表查询简介
常见的树表查询算法为,二叉排序树(BST)、平衡二叉树(AVL)、红黑树、B树、B+树、B*树等
参考思维导图,这里循序渐进,逐渐介绍到红黑树即可,其他的可参考前面二叉排序树基本思想加以理解,或者参考其他文章更详细学习
本节主要讲解二叉排序树
二叉排序树(BST)
二叉排序树,又称二叉查找树,还称二叉搜索树
注意:其他排序树都是基于其逻辑基础进行改进的
特征
1.从根节点开始向两侧生成
2.左节点的值永远小于其父节点,右节点值永远大于其父节点(反向设置意义不大)
3.一般情况下查询效率要高于链表
4.插入和删除需要查找到指定位置方可操作
节点基本数据结构
typedef struct TreeNode {
int data; //节点数据
struct TreeNode *l, *r; //左右孩子
}LSTreeNode;
查询
所有二叉排序树的查询过程都是一致的,包括后面的平衡二叉树和红黑树,都是通过二分法向下查询
通过排序二叉树的特征:左节点的值永远小于其父节点,右节点值永远大于其父节点
因此:只需要从根节点开始,当前节点的值与查找值一致,则找到;比当前节点小,就到左孩子找;比当前节点大,则去右节点找;一直找到叶子节点为止
案例图
下图为查找节点11的过程
代码实现
//在排序树、平衡树查找返回指定内容节点
LSTreeNode * search(LSTreeNode *node, int value) {
while (node) {
if (node->data == value) return node;
if (node->data > value) {
node = node->l;
}else {
node = node->r;
}
}
return NULL; //到这里就是没找到了
}
插入
插入的过程与查找类似,也是比父节点小的,去左节点插入,比父节点大的去右节点插入,直到左右节点为空时,插入即可
插入步骤如下:
1.记录父节点,和当前节点内容,用于更新节点
2.从根节点开始,向下进行对比遍历
3.查看当前节点是否存在,如果不存在(空树,或者遍历到的子节点为空),则进入步骤7
4.保存当前节点为父节点,以便后续使用
5.对比当前节点是否与查找节点一致,如果一致则更新内容,结束整个步骤;
6.如果节点值比查找的值小,则向左节点开始向下查找,即左节点作为当前节点;如果节点值比查找的大,则向右节点开始向下查找,右节点作为当前节点,然后回到步骤3
7.通过预留的父节点(最后应插入的节点的父节点),比插入数据大,就放到左孩子出,否则放到右孩子处,插入结束
案例图
下图为插入节点12的过程
代码实现
//二叉排序树的插入(不允许重复,如果重复就是替换更新功能了这里就不讨论了),如果比根节点大
LSTreeNode * insertBinSortNode(LSTreeNode *root, int data) {
LSTreeNode *node = (LSTreeNode *)malloc(sizeof(LSTreeNode));
node->data = data;
if (!root) return node;
LSTreeNode *p = root, *q = NULL;
while (p) {
q = p; //记录待插记录的父节点
if (p->data == data) {
free(node);
return root;
}; //该节点已经存在,结束(这一步可以进行替换更新功能)
//只要比根节点大,就一直往左侧找,否则往右找,不停缩小范围,一直到叶子节点为止
if (p->data > data) {
p = p->l;
}else {
p = p->r;
}
}
//比插入数据大,就放到左侧,否则右侧
if (q->data > data) {
q->l = node;
}else {
q->r = node;
}
return root;
}
删除
同查找一样,删除需要找到要删除的点,然后删除,如果删除的是叶子节点直接删除,否则,查找逻辑上一个节点或者逻辑下一个节点(该节点为叶子节点或单孩子节点)进行补位,即:将补位节点内容替换到被删除节点,然后删除补位节点接口
注意:无论采用上一个节点还是下一个节点,替换后虽然树有所不同,但不影响最终查询
删除步骤:
1.检查是否为空树,如果是结束
2.查找要删除节点的位置,查找过程保存父节点,找到后进行下一步,找不到结束
3.如果要删除的节点是叶子节点,则直接删除;如果只有一个孩子,则将孩子节点赋值到要删除的节点位置,然后结束
4.其他情况就是在中间,这种情况直接查找其逻辑上一个节点(即左子树中序遍历的最后节点),然后将其值覆盖到被删除节点处
5.逻辑上一个节点如果是叶子节点,直接删除该节点;否则只有左孩子,如果父节点是原删除节点,则直接将原删除节点的左节点指针指向替换删除节点的左孩子即可,否则将替换删除节点其父节点右孩子指针指向删除节点的左孩子
6.删除该替换节点结束
案例图
下面是逻辑上一个节点替换的删除图
下面是逻辑下一个节点替换的删除图
代码实现
//删除二叉排序树某个节点(方案思路,叶子节点直接删除即可,非叶子节点将改替换的节点数据替换成上一个节点,上一个节点一般都是叶子节点或者没有右节点的节点)
LSTreeNode *deleteBinSortNode(LSTreeNode *root, int data) {
if (!root) return NULL;
LSTreeNode *p = root, *s = NULL;
//查看现有二叉树是否存在data节点
while (p) {
if (p->data == data) break;
s = p; //保存搜索到节点的上一个节点
if (p->data > data) {
p = p->l;
}else {
p = p->r;
}
}
if (!p) return root;//没找到对应的点,结束
if (!p->l && !p->r) {
//为叶子节点
if (p == root) root = NULL; //只有根节点置空
else if (s->l == p) s->l = NULL; //不是根节点左右节点为空
else s->r = NULL;
}else if (!p->l) {
//只有右侧分支, 那么吧p的父节点指向p的指针指向p的右节点
if (p == root) root = p->r;
else if (s->r == p) s->r = p->r;
else s->l = p->r;
}else if (!p->r) {
//同右侧指向左侧
if (p == root) root = p->l;
else if (s->r == p) s->r = p->l;
else s->l = p->l;
}else {
//两侧都存在的
LSTreeNode *q = p; //保存查找替换点的数值逻辑的前一个节点的父节点
s = p; //保存找到的原始节点
p = p->l;
while (p->r) {
q = p;
p = p->r;
}
q->data = p->data;
if (s == q) s->l = p->l;
else q->r = p->l;
}
free(p);
return root;
}
最后
删除插入过程中会发现,排序二叉树会出现两极分化的情况,例如下图所示
这样查询效率会大大降低,排序二叉树演化成了链表,不符合树表查询设计初衷,因此引出了平衡二叉树、红黑树等