二叉查找树、二叉搜索树、二叉排序树算法分析及实现

一、几个概念

二叉树(Binary Tree),是 n(n >= 0)个结点(每个结点最多只有2棵子树)的有限集合,该集合或为空集,称为空二叉树,或由一个根节点和两颗互不相交的,称为根节点的左子树和右子树组成,且其左右子树也分别是一颗二叉树。

二叉查找树(Binary Search Tree,简称 BST),又称为二叉搜索树有序二叉树(Ordered Binary Tree)。二叉查找树或是空二叉树,或是满足以下三个性质的二叉树。

  • 若其左子树非空,则左子树上所有节点的值都小于根节点的值
  • 若其右子树非空,则右子树上所有节点的值都大于根节点的值
  • 其左右子树也分别是一棵二叉查找树

前序遍历二叉树(Pre-order Traversal):从根结点出发,先访问该结点,然后前序遍历该结点的左子树,再然后前序遍历该结点的右子树

中序遍历二叉树(In-order Traversal)的规则为:从根结点出发,先中序遍历该结点的左子树,然后访问该结点,再然后中序遍历该结点的右子树

后序遍历二叉树(Post-order Traversal)的规则为:从根结点出发,先后序遍历该结点的左子树,然后后序遍历该结点的右子树,再然后访问该结点

二叉链表:是二叉树的一种链式存储结构,其中每个结点包含三个字段:一个数据字段(data)和两个指针字段(*lchild、*rchild),分别指向该结点的左孩子和右孩子。如果某个结点没有左孩子或右孩子,那么对应的指针字段为NULL。

用 C 语言可以表述为:

typedef struct BST_node {
    int data;
    struct BST_node *lchild, *rchild;
}*BST;

二、二叉查找树的特性

通过中序遍历二叉查找树,得到的序列是有序的递增序列

如下图所示的一颗二叉查找树,其中序遍历的序列为:{3,6,8,10,15,20}

三、二叉查找树的查找

因为通过中序遍历二叉查找树,得到的序列是有序的递增序列,因此二叉查找树的查找跟二分查找法类似。

假设有一棵二叉查找树:T,每个结点有数据字段(data),有两个指针字段(*lchild、*rchild),分别指向结点的左孩子和右孩子。查找 data,如果找到则返回该结点,否则返回 NULL

1、算法思路

  • 如果二叉查找树为空树,则查找失败,返回 NULL
  • 如果二叉查找树不为空

(1)如果 T->data == data,则返回该结点

(2)如果 T->data > data,则递归查找左子树

(3)如果 T->data < data,则递归查找右子树

时间复杂度

  • 最好的情况,根结点就是要查找的结点,所以最好时间复杂度为: O(1)
  • 最差的情况,二叉查找树退化为链表时,所以最差时间复杂度为: O(n)
  • 平均时间复杂度为:O(\log n)

2、实现代码

#include <stdio.h>
#include <stdlib.h>

// 扩展二叉树前序序列
char *g_bad_str = "7654321########";
char *g_average_str = "421##3##65##7##";
int g_index = 0;
char *g_str = NULL;
int g_search_times = 0;

typedef enum BST_TYPE {
    BAD,
    AVERAGE
}BST_TYPE;

typedef struct BST_node {
    int data;
    struct BST_node *lchild, *rchild;
}*BST;

// 二叉查找树查找
BST bst_search(BST const T, const char data) {
    if (!T)
        return NULL;
    
    g_search_times++;
    if (T->data == data)
        return T;
    else if (T->data > data)
        return bst_search(T->lchild, data);
    else
        return bst_search(T->rchild, data);
}

void bst_create(BST *T, BST_TYPE type) {
    if (type == BAD)
        g_str = g_bad_str;
    else
        g_str = g_average_str;
    
    if (g_str[g_index] == '#') {
        *T = NULL;
        g_index++;
    } else {
        *T = malloc(sizeof(**T));
        (*T)->data = g_str[g_index];
        (*T)->lchild = NULL;
        (*T)->rchild = NULL;
        g_index++;
        bst_create(&(*T)->lchild, type);
        bst_create(&(*T)->rchild, type);
    }
}

void bst_destroy(BST T) {
    if (!T)
        return;
    
    bst_destroy(T->lchild);
    bst_destroy(T->rchild);
    printf("free %c\n", T->data);
    free(T);
}

int main(int argc, char *argv[]) {
    BST tree_bad = NULL;
    BST tree_average = NULL;
    BST tree_search = NULL;
    
    g_index = 0;
    bst_create(&tree_bad, BAD);

    g_index = 0;
    bst_create(&tree_average, AVERAGE);

    g_search_times = 0;
    char data = '1';
    tree_search = bst_search(tree_bad, data);
    if (tree_search)
        printf("bad search_times = %d, data = %c\n", g_search_times, tree_search->data);
    else
        printf("can't find %c, bad search_times = %d\n", data, g_search_times);

    g_search_times = 0;
    data = '7';
    tree_search = bst_search(tree_average, data);
    if (tree_search)
        printf("average search_times = %d, data = %c\n", g_search_times, tree_search->data);
    else 
        printf("can't find %c, average search_times = %d\n", data, g_search_times);

    printf("----------\n");
    bst_destroy(tree_bad);
    printf("----------\n");
    bst_destroy(tree_average);

    return 0;
}

四、二叉查找树的插入

因为通过中序遍历二叉查找树,得到的序列是有序的递增序列,因此二叉查找树的插入跟二分插入法类似。

假设有一个二叉查找树指针:T,每个结点有数据字段(data),有两个指针字段(*lchild、*rchild),分别指向结点的左孩子和右孩子,并将数据 data 插入到其中。

1、算法思路

(1)如果二叉查找树指针 T 为 NULL,则创建新结点,并插入到当前位置

(2)如果 T->data > data,则递归插入到左子树中

(3)如果 T->data <= data,则递归插入到右子树中

时间复杂度

  • 最好的情况,根结点为空时,所以最好时间复杂度为: O(1)
  • 最差的情况,二叉查找树退化为链表时,所以最差时间复杂度为: O(n)
  • 平均时间复杂度为:O(\log n)

2、实现代码

#include <stdio.h>
#include <stdlib.h>

int g_insert_times = 0;

typedef struct BST_node {
    int data;
    struct BST_node *lchild, *rchild;
}*BST;

// 二叉查找树插入
void bst_insert(BST *T, int data) {
    g_insert_times++;
    if (!(*T)) {
        *T = malloc(sizeof(**T));
        (*T)->data = data;
        (*T)->lchild = NULL;
        (*T)->rchild = NULL;
        printf("insert %c success, insert times = %d\n", data, g_insert_times);
    } else if ((*T)->data > data) {
        bst_insert(&(*T)->lchild, data);
    } else {
        bst_insert(&(*T)->rchild, data);
    }
}

void bst_destroy(BST T) {
    if (!T)
        return;
    
    bst_destroy(T->lchild);
    bst_destroy(T->rchild);
    printf("free %c\n", T->data);
    free(T);
}

void visit_node(BST T) {
    printf("%c\n", T->data);
}

void in_order_tree(BST T) {
    if (!T)
        return;

    in_order_tree(T->lchild);
    visit_node(T);
    in_order_tree(T->rchild);
}

void bst_test(BST *T) {
    g_insert_times = 0;
    bst_insert(T, '4');
    g_insert_times = 0;
    bst_insert(T, '2');
    g_insert_times = 0;
    bst_insert(T, '6');
    g_insert_times = 0;
    bst_insert(T, '1');
    g_insert_times = 0;
    bst_insert(T, '5');
    g_insert_times = 0;
    bst_insert(T, '3');
    g_insert_times = 0;
    bst_insert(T, '7');
}

void bst_test1(BST *T) {
    g_insert_times = 0;
    bst_insert(T, '7');
    g_insert_times = 0;
    bst_insert(T, '6');
    g_insert_times = 0;
    bst_insert(T, '5');
    g_insert_times = 0;
    bst_insert(T, '4');
    g_insert_times = 0;
    bst_insert(T, '3');
    g_insert_times = 0;
    bst_insert(T, '2');
    g_insert_times = 0;
    bst_insert(T, '1');
}

int main(int argc, char *argv[]) {
    BST T = NULL;
    BST T1 = NULL;

    bst_test(&T);
    printf("-------------\n");
    bst_test1(&T1);
    printf("---------\n");
    bst_destroy(T);
    printf("---------\n");
    bst_destroy(T1);

    return 0;
}

五、二叉查找树的删除

1、算法思路

(1)如果被删除结点的左子树为空时,将该结点的父结点中,让指向该结点的指针域指向该结点的右子树,然后删除该结点

(2)如果被删除结点的右子树为空时,将该结点的父结点中,让指向该结点的指针域指向该结点的左子树,然后删除该结点

(3)如果被删除结点的左右子树都不为空时,情况复杂的多,因为父结点的左指针或右指针不能同时指向被删除结点的左右子树。我们可以采用

  • 合并删除法

合并删除法:将被删除结点的左右子树合并得到一棵树,然后把这棵树挂到被删除结点的父结点中。

如何将被删除结点的左右子树合并得到一棵树呢?

因为二叉查找树的特性是:左子树的每个结点的值都比右子树每个结点的值小,所以,可以把被删除结点的左子树中值最大的结点(左子树中,沿着右结点找,结点的右指针为空的结点)作为被删除结点的右子树的父结点 被删除结点的右子树中,值最小的结点(右子树中,沿着左结点找,结点的左指针为空的结点)作为被删除结点的左子树的父结点,从而合并得到一棵树,然后挂到被删除结点的父结点中

时间复杂度

  • 最好的情况,被删除的结点是根结点,且二叉查找树退化为链表时,此时查找的时间复杂度为 O(1),删除的时间复杂度为 O(1),所以最好时间复杂度为: O(1)
  • 最差的情况,二叉查找树退化为链表时,此时查找的时间复杂度为 O(n),删除的时间复杂度为 O(1),所以最差时间复杂度为: O(n)
  • 平均时间复杂度为:O(\log n)

2、代码实现

#include <stdio.h>
#include <stdlib.h>

typedef struct BST_node {
    int data;
    struct BST_node *lchild, *rchild;
}*BST;

// 二叉查找树查找
BST* bst_search(BST* const T, int data) {
    if (!(*T))
        return NULL;
    
    if ((*T)->data == data)
        return T;
    else if ((*T)->data > data)
        return bst_search(&(*T)->lchild, data);
    else 
        return bst_search(&(*T)->rchild, data);
}

// 二叉查找树插入
void bst_insert(BST *T, int data) {
    if (!(*T)) {
        *T = malloc(sizeof(**T));
        (*T)->data = data;
        (*T)->lchild = NULL;
        (*T)->rchild = NULL;
    } else if ((*T)->data > data) {
        bst_insert(&(*T)->lchild, data);
    } else {
        bst_insert(&(*T)->rchild, data);
    }
}

void bst_destroy(BST T) {
    if (!T)
        return;
    
    bst_destroy(T->lchild);
    bst_destroy(T->rchild);
    free(T);
}

void visit_node(BST T) {
    printf("%d\n", T->data);
}

void in_order_tree(BST T) {
    if (!T)
        return;

    in_order_tree(T->lchild);
    visit_node(T);
    in_order_tree(T->rchild);
}

void bst_create1(BST *T) {
    // 2346
    bst_insert(T, 4);
    bst_insert(T, 2);
    bst_insert(T, 6);
    bst_insert(T, 3);

    in_order_tree(*T);
    printf("----------\n");
}

void bst_create2(BST *T) {
    // 1246
    bst_insert(T, 4);
    bst_insert(T, 2);
    bst_insert(T, 6);
    bst_insert(T, 1);

    in_order_tree(*T);
    printf("----------\n");
}

void bst_create3(BST *T) {
    // 12345678
    bst_insert(T, 7);
    bst_insert(T, 4);
    bst_insert(T, 8);
    bst_insert(T, 2);
    bst_insert(T, 6);
    bst_insert(T, 1);
    bst_insert(T, 3);
    bst_insert(T, 5);

    in_order_tree(*T);
    printf("----------\n");
}

void bst_delete_by_merge(BST *T, int data) {
    BST* del_node = bst_search(T, data);
    if (!(*del_node))
        return;
    
    BST tmp = (*del_node);

    if (!(*del_node)->lchild) // 左孩子为空
        *del_node = (*del_node)->rchild;
    else if (!(*del_node)->rchild) // 右孩子为空
        *del_node = (*del_node)->lchild;
    else { // 左右子树都不为空
        // 把被删除结点左子树中值最大的结点(左子树中,沿着右结点找,结点的右指针为空的结点)作为被删除右子树的父结点
        BST rmax = (*del_node)->lchild;
        while(rmax->rchild) {
            rmax = rmax->rchild;
        }
        rmax->rchild = (*del_node)->rchild;

        // 挂到被删除结点的父结点中
        *del_node = (*del_node)->lchild;
    }

    printf("delete %d success\n", tmp->data);
    free(tmp);
}

int main(int argc, char *argv[]) {
    BST T1 = NULL;
    BST T2 = NULL;
    BST T3 = NULL;

    bst_create1(&T1);
    bst_delete_by_merge(&T1, 2);
    in_order_tree(T1);
    printf("--------\n");

    bst_create2(&T2);
    bst_delete_by_merge(&T2, 2);
    in_order_tree(T2);
    printf("--------\n");

    bst_create3(&T3);
    bst_delete_by_merge(&T3, 4);
    in_order_tree(T3);
    printf("--------\n");


    bst_destroy(T1);
    bst_destroy(T2);
    bst_destroy(T3);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值