二叉排序树
又称二叉检索树、二叉搜搜索树
一、性质
1、左节点 < 根结点
2、右节点 > 根结点
3、中序遍历的结果,是一个有序序列
二、用途
解决与排名相关的检索需求
点与边的关系:点是集合,边是关系,根结点相当于全集
三、插入
与根结点相比较,小于根结点,递归左边,大于根结点,递归右边,若是递归到叶子结点,变成叶子结点的左孩子或右孩子。
1.插入新节点,一定会作为叶子结点
四、删除
三种情况:
一、度为0:
直接删除,返回空节点
二、度为1:
删除节点后,将子孩子交给父亲结点
三、度为2:
找到前驱后或后继结点进行替换,问题转换为删除度为一的问题。
注意:度为2结点的前驱或者后继的一定为度为1的结点
对于度为二的节点:
1.前驱:左子树中的最大值
2.后继:右子树中的最小值
练习-1
按照如下插入数字,画出对应的二叉树搜索树
1 :[5, 9, 8, 3, 2, 4, 1, 7]
2 : [1, 2, 3, 4, 5]
1.插入顺序会影响最终的树形结构
2.不同的树形结构,查找效率不同
平均查找效率:节点查找的期望值 总 次 数 节 点 数 量 \frac{总次数}{节点数量} 节点数量总次数 , 假设每个结点等概率的被查找
五、代码实现
#include <stdio.h>
#include <stdlib.h>
#define KEY(n) (n ? n->key : 0)
typedef struct Node {
int key;
struct Node *lchild, *rchild;
} Node;
Node *getNewNode (int val) {
Node *p = (Node *)malloc(sizeof(Node));
p->key = val;
p->lchild = p->rchild = NULL;
return p;
}
int search_Node(Node *root, int val) {
if (root == NULL) return 0;
if (root->key == val) return 1;
if (root->key < val) return search_Node(root->rchild, val);
return search_Node(root->lchild, val);
}
Node *insert(Node *root, int val) {
if (root == NULL) return getNewNode(val);
if (root->key == val) return root;
if (root->key < val) root->rchild = insert(root->rchild, val);
else root->lchild = insert(root->lchild, val);
return root;
}
Node *predecessor(Node *root) {
Node *temp = root->lchild;
while (temp->rchild) temp = temp->rchild;
return temp;
}
Node *erase(Node * root, int key) {
if (root == NULL) return NULL;
if (key < root->key) {
root->lchild = erase(root->lchild, key);
} else if (key > root->key) {
root->rchild = erase(root->rchild, key);
} else {
if (root->lchild == NULL && root->rchild == NULL) {
free(root);
return NULL;
} else if (root->lchild == NULL || root->rchild == NULL) {
Node *temp = root->lchild ? root->lchild : root->rchild;
free(root);
return temp;
} else {
Node *temp = predecessor(root);
root->key = temp->key;
root->lchild = erase(root->lchild, root->key);
}
}
return root;
}
void clear(Node *root) {
if (root == NULL) return ;
clear(root->lchild);
clear(root->rchild);
free(root);
return ;
}
void output(Node *root) {
if (root == NULL) return ;
output(root->lchild);
printf("(%d, %d, %d)\n", KEY(root), KEY(root->lchild), KEY(root->rchild));
output(root->rchild);
return ;
}
int main() {
int op, val; // 操作:0:查找 1:插入 2:删除
Node *root = NULL;
while (~scanf("%d%d", &op, &val)) {
switch (op) {
case 0: printf("search %d, result : %d\n", val, search_Node(root, val)); break;
case 1: root = insert(root, val); break;
case 2: root = erase(root, val); break;
}
if (op) {
output(root);
printf("-------------\n");
}
}
return 0;
}
六、拓展内容
1.二叉排序树的删除代码优化
1.删除掉处理度为0的代码逻辑,不影响代码整体整体功能
2.如何解决排名相关的代码需求
1. 修改二叉树的结构定义,增加size字段,记录每棵树的节点数量
2. k = L − 1 k = L - 1 k=L−1,根结点就是排名第K位的元素
3. k ≤ L S k \le LS k≤LS, 排名第K位的元素在左子树中
4. k > L S , s e a r c h _ k ( r o o t − > r c h i l d , k − L S − 1 ) k \gt LS, search\_k(root->rchild, k - LS - 1) k>LS,search_k(root−>rchild,k−LS−1)
3.解决Top-k问题(找到小于第k位的所有元素)
1.根结点就是第k位元素的话,就把左子树中的值全部输出出来
2.第K位在左子树中,前k位元素全都在左子树中
3.第k位在右子树中,说明根结点和左子树中的元素,都是前k位元素里的值
4.二叉排序树与快速排序之间的关系
1. 二叉排序树是快速排序在逻辑结构层面用的数据结构
2. 思考1:快速排序的时间复杂度和二叉排序树建树时间复杂度之间的关系
3. 思考2:快速选择算法和二叉排序树之间的关系
4. 程序=算法+数据结构
所谓算法设计及分析能力:分类讨论及归纳总结的能力
七、最终代码
#include <stdio.h>
#include <stdlib.h>
#define KEY(n) (n ? n->key : 0)
#define SIZE(n) (n ? n->size : 0)
#define L(n) (n ? n->lchild : NULL)
typedef struct Node {
int key;
int size;
struct Node *lchild, *rchild;
} Node;
Node *getNewNode (int val) {
Node *p = (Node *)malloc(sizeof(Node));
p->key = val;
p->lchild = p->rchild = NULL;
p->size = 1;
return p;
}
void update_size(Node *root) {
root->size = SIZE(root->lchild) + SIZE(root->rchild) + 1;
return ;
}
int search_Node(Node *root, int val) {
if (root == NULL) return 0;
if (root->key == val) return 1;
if (root->key < val) return search_Node(root->rchild, val);
return search_Node(root->lchild, val);
}
Node *insert(Node *root, int val) {
if (root == NULL) return getNewNode(val);
if (root->key == val) return root;
if (root->key < val) root->rchild = insert(root->rchild, val);
else root->lchild = insert(root->lchild, val);
update_size(root);
return root;
}
int search_k(Node *root, int k) {
if (root == NULL) return -1;
if (SIZE(L(root)) == k - 1) return root->key;
if (k <= SIZE(L(root))) return search_k(root->lchild, k);
return search_k(root->rchild, k - SIZE(L(root)) - 1);
}
Node *predecessor(Node *root) {
Node *temp = root->lchild;
while (temp->rchild) temp = temp->rchild;
return temp;
}
Node *erase(Node * root, int key) {
if (root == NULL) return NULL;
if (key < root->key) {
root->lchild = erase(root->lchild, key);
} else if (key > root->key) {
root->rchild = erase(root->rchild, key);
} else {
if (root->lchild == NULL && root->rchild == NULL) {
free(root);
return NULL;
} else if (root->lchild == NULL || root->rchild == NULL) {
Node *temp = root->lchild ? root->lchild : root->rchild;
free(root);
return temp;
} else {
Node *temp = predecessor(root);
root->key = temp->key;
root->lchild = erase(root->lchild, root->key);
}
update_size(root);
}
return root;
}
void clear(Node *root) {
if (root == NULL) return ;
clear(root->lchild);
clear(root->rchild);
free(root);
return ;
}
void print(Node *root) {
printf("(%d [%d], %d, %d)\n", KEY(root), SIZE(root), KEY(root->lchild), KEY(root->rchild));
return ;
}
void output(Node *root) {
if (root == NULL) return ;
output(root->lchild);
print(root);
output(root->rchild);
return ;
}
void output_k(Node *root, int k) {
if (root == NULL || k == 0) return ;
if (k <= SIZE(L(root))) {
output_k(root->lchild, k);
} else {
output(root->lchild);
print(root);
output_k(root->rchild, k - SIZE(L(root)) - 1);
}
return ;
}
int main() {
int op, val; // 操作:0:查找 1:插入 2:删除 3:查找第k元素 4:打印前Top-k元素
Node *root = NULL;
while (~scanf("%d%d", &op, &val)) {
switch (op) {
case 0: printf("search %d, result : %d\n", val, search_Node(root, val)); break;
case 1: root = insert(root, val); break;
case 2: root = erase(root, val); break;
case 3: printf("search_k = %d, result : %d\n", val, search_k(root, val)); break;
case 4: { output_k(root, val); printf("------------\n"); }break;
}
if (op == 1 || op == 2) {
output(root);
printf("-------------\n");
}
}
return 0;
}