平衡二叉排序树(查找树)

  1. 性质:①左子树 < 根结点;②右子树 > 根结点
  2. 用途:解决与排名相关的检索需求
  3. 时间复杂度最优/平均:O(nlogn)
  4. 时间复杂度最差:O(n)【当从小到大插入数据,退化为链表】
  5. 树的插入根据第一点的性质进行左右子树的插入
  6. 树的删除
    ①删除叶子结点:直接删除
    ②删除出度为1的结点:将孙子结点直接挂到爷爷结点上
    ③删除出度为2的点:利用中序遍历(从小到大排序),度为2的结点一定是一个根结点,此根结点的前驱是左子树中的最大值,后继结点是右子树中的最小值。而前驱和后继一定是度为1或度为0的结点,要删除当前结点只需将它和前驱(或后继)进行交换,转换成度为1或度为0的结点,然后进行删除。
  7. 查找固定值x:根据第一点——性质进行查找
  8. 查找第k小的元素
    维护结点个数。如果当前结点左子树中的结点个数(lcnt) > k,说明第k个元素在左子树中。如果lcnt < k:①lcount = k - 1:根结点就是第k小的树;②否则答案在右子树中,在右子树中找到排名为k - lcnt - 1的数
  9. 输出二叉排序树中一个范[l, r]的值:转换为有序序列(中序遍历)查找有序序列中的范围
  10. 二叉排序树的特性:
    ①有序。不仅能查找元素,而且能维护元素的有序性。
    ②稳定。二叉排序树的进化版平衡二叉排序树(红黑树)和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 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值