- 性质:①左子树 < 根结点;②右子树 > 根结点
- 用途:解决与排名相关的检索需求
- 时间复杂度最优/平均:O(nlogn)
- 时间复杂度最差:O(n)【当从小到大插入数据,退化为链表】
- 树的插入根据第一点的性质进行左右子树的插入
- 树的删除:
①删除叶子结点:直接删除
②删除出度为1的结点:将孙子结点直接挂到爷爷结点上
③删除出度为2的点:利用中序遍历(从小到大排序),度为2的结点一定是一个根结点,此根结点的前驱是左子树中的最大值,后继结点是右子树中的最小值。而前驱和后继一定是度为1或度为0的结点,要删除当前结点只需将它和前驱(或后继)进行交换,转换成度为1或度为0的结点,然后进行删除。 - 查找固定值x:根据第一点——性质进行查找
- 查找第k小的元素:
维护结点个数。如果当前结点左子树中的结点个数(lcnt) > k,说明第k个元素在左子树中。如果lcnt < k:①lcount = k - 1:根结点就是第k小的树;②否则答案在右子树中,在右子树中找到排名为k - lcnt - 1的数 - 输出二叉排序树中一个范[l, r]的值:转换为有序序列(中序遍历)查找有序序列中的范围
- 二叉排序树的特性:
①有序。不仅能查找元素,而且能维护元素的有序性。
②稳定。二叉排序树的进化版平衡二叉排序树(红黑树)和AVL树对于每一次的查找操作时间复杂度能稳定在O(nlogn)
树的构建代码演示
typedef struct Node{
int key;//结点的值
Node *lchild, *rchild; //指向左右子树的两个指针
} Node;
//传入一个key,返回一个包含当前key的结点
Node *getNewNode(int key){
Node *p = (Node *)malloc(sizeof(Node));
p->key = key;
p->lchild = p->rchild = NULL;
return p;
}
void clear(Node *root){
if(root == NULL) return ;
//分别销毁左右子树
clear(root->lchild);
clear(root->rchild);
free(root);
return ;
}
树的插入代码演示
//把key插入二叉树中,返回插入新结点后这棵树的根结点地址
//如果向一个NULL树中插入一个新的结点,返回的是一个新的树的地址
Node *insert(Node *root, int key){
if(root == NULL) return getNewNode(key);//如果树为空,直接创建新结点
if(root->key == key) return root; //如果要插入的key已经存在,直接返回这棵树
if(root->key > key) root->lchild = insert(root->lchild, key); //如果当前树结点的值大于要插入的key,向左子树插入
else root->rchild = insert(root->rchild, key); //否则向右子树插入
return root;
}
树的先序、中序遍历代码演示
//先序遍历
void output(Node *root){
if(root == NULL) return ;
printf_node(root); //打印当前根结点的内容
//先序遍历左右子树的内容
output(root->lchild);
output(root->rchild);
return ;
}
//中序遍历,左根右。是一个有序序列
void inorder_output(Node *root){
if(root == NULL) return ;
inorder_output(root->lchild); //中序遍历当前结点的左子树
printf("%d ", root->key); //输出当前结点的值
inorder_output(root->rchild); //中序遍历当前结点的右子树
return ;
}
删除结点代码演示
//返回当前结点的前驱结点
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 root;
if(root->key > key) root->lchild = erase(root->lchild, key); //到左子树中删除
else if(root->key < key) root->rchild = erase(root->rchild, key); //到右子树中删除
else { //root所指向的结点就是要删除的结点
if(root->lchild == NULL || root->rchild == NULL){ //优化,把n=0和n=1的情况合并处理
Node *temp = root->lchild ? root->lchild : root->rchild; //找到当前结点的唯一子孩子
free(root); //释放当前结点
return temp; //返回找到的子孩子的结点
} else { //n = 2
Node *temp = predecessor(root); //找到当前结点的前驱(也可以是后继)
root->key = temp->key; //前驱的值直接赋给当前结点
root->lchild = erase(root->lchild, temp->key); //到左子树中找到前驱结点的值并删掉
}
}
return root;
}
查找固定值x代码演示
//查找元素x
Node *search(Node *root, int key){
if(root == NULL) return NULL;
if(root->key == key) return root;
if(root->key > key) return search(root->lchild, key); //到左子树找
else return search(root->rchild, key); //到右子树中找
}
查找第k小的数代码演示
首先要在原本的结构中加上cnt
typedef struct Node{
int key, cnt; //cnt是结点数量
Node *lchild, *rchild;
} Node;
//在root中查找第k小的数
Node *__find_k(Node *root, int k){
if(CNT(root->lchild) >= k) return __find_k(root->lchild, k); //如果lcount >= k,则结果就在左子树中
if(CNT(root->lchild) + 1 == k) return root; //根结点就是结果
return __find_k(root->rchild, k - CNT(root->lchild) - 1); //lcount < k + 1,结果在右子树中,最后的答案需 要减去左子树和根结点的数量
}
//不写在__find_k的第一行是因为它是一个递归函数,如果写在里面,每次递归都会进行一次判断
Node *find_k(Node *root, int k){
if(k <= 0 || k > CNT(root)) return NULL; //如果k<=0或者k>整棵树的结点数量,直接返回空
return __find_k(root, k); //否则再去进行__find_k的运算
}
查找二叉排序树中一个范[l, r]的值代码演示
//输出一个范围内的元素。把它当成一个有序序列去遍历
void output_range(Node *root, int l, int r){
if(root == NULL) return ;
output_range(root->lchild, l, r); //中序遍历左子树
//会从小到大遍历每一个数,所以当满足条件就输出
if(root->key <= r && root->key >= l) printf("%d ", root->key);
output_range(root->rchild, l, r); //中序遍历右子树
return ;
}