二叉查找树

二叉查找树


吐槽下,csdn的markdown有点虚

二叉树是一种很有趣的数据结构,因为大多数情况下它都够简单,却相当强大。今天回顾下一种相对来说比较简单但又非常有用的数据结构–二叉查找树。同时给出c实现和Java的完整实现



树是一种数据结构,一棵树是n个节点和n-1条边的集合(这个后面解释)
1. 一棵树有这样的特点:

  • 一个节点可以有0个或多个子节点
  • 没有父节点的节点称为根结点
  • 每个非根节点有且只有一个父节点
  • 每个节点及其子节点构成一棵子树。即树是递归定义的。

2. 假设有n个节点,则除根结点外,所有节点都有且只有一个父节点,即意味者这些节点都和一条边对应,即有n-1条边。



二叉树
相对树而言,二叉树更加简单,更加通用。
1. 二叉树是这样一种数据结构:

  • 它本质是一棵树
  • 每个节点最多仅有两个子节点
  • 二叉树的子节点具有方向,分为左子节点和右子节点

2. 二叉树具有一些比较重要的性质,这些性质基本可以根据一种“极端”条件的二叉树简单求得。这种二叉树称为满二叉树,即该二叉树除了最后一层的叶子节点,其它结点均有左右子节点,其满足“k深度的树有2^k-1个节点”。

  • 性质1:第n层最多有 2^(n-1) 个节点。通过满二叉树很容易求得
  • 性质2:n层的二叉树最多有 2^n-1 个节点。通过列出满二叉树的前几项即可求得
  • 性质3:n个节点的二叉树至少有 log(n-1) 层。通过性质2就可求得




二叉查找树
二叉树非常简单,但要其发挥作用,通常都需要对其添加一些限制条件,比如添加了元素顺序限制后,就构成一棵二叉查找树。
1. 二叉查找树是满足下列条件的二叉树

  • 任意父节点的值要大于其左儿子
  • 任意父节点的值要小于其右儿子

2. 二叉查找树的平均深度为 O(log N)




二叉查找树的基本操作
1. 首先定义树节点。包含三个域,分别存储数据,左子树,右子树。

typedef struct TreeNode TreeNode; // 树节点
typedef TreeNode *ProToTreeNode; // 指向树节点的指针
typedef ProToTreeNode TreeNodePosition; 
typedef ProToTreeNode SearchTree; // 指向二叉树的指针。(即该节点指向二叉树根结点)
struct TreeNode
{
    int Element;
    // 根据递归定义我们这里的left指向的也是一棵树
    SearchTree left; 
    SearchTree right;
};



2. 我们创建二叉树是通过不断插入新的节点来实现的,所以现在我们先定义一个插入函数

SearchTree insert(int x, SearchTree T)
{
    if (T == NULL) {
        T = createTreeNode(x); // 具体实现见文末完整代码
    } else if(x < T->Element) {
        T->left = insert(x, T->left);
    } else if(x > T->Element) {
        T->right = insert(x, T->right);
    }
    return T;
}

这里我们需要注意几点

  • 我们并不考虑重复点,即重复点会被忽略
  • 最终插入的效果是生成一个新的节点(在节点不重复时这是必然的)。这是非常重要的一点,因为这种情况意味着最终 T 一定会为NULL。
  • 从根结点开始,如果待出入节点大于当前节点,则新插入节点应该位于当前节点的左子树,否则位于其右子树。我们是通过返回子树的根结点来确定新节点与原二叉树的关系的。(即将新节点返回后赋值给父节点的左子节点或右子节点)

实际上有关树的操作大多都依赖于递归,并且一般都能很好的工作。


3. 接下来我们实现二叉查找树最终要的一个操作,查找操作。通过递归,你会发现查找操作简直简单得有点吓人。当大于当前值时,则在右子树查找,当小于当前值时,则在左子树查找。仅此而已。

代码如下

TreeNodePosition find(int x, SearchTree T)
{
    TreeNodePosition p;
    if (T == NULL)
        p = NULL; 
    else if (T->Element > x)
        p = find(x, T->right);
    else if (x < T->Element)
        p = find(x, T->left);
    else
        p = T;
    return p;
}



4. 下面我们来实现一个比较复杂的操作。删除操作。该操作如下

  • 查找到该节点
  • 如果该节点是叶子,则直接删除
  • 如果该节点有左子树,无右子树,则其左子树接替其位置,然后删除该节点。另一种对称情况类似
  • 如果该存在左右子树,我们需要找到其右子树的最小节点,代替待删除节点(此时并不会影响二叉查找树的性质),然后递归删除该最小节点。

代码如下:

// 《数据结构与算法分析》中的实现,逻辑非常清晰!
SearchTree delete(int x, SearchTree T)
{
    TreeNodePosition p;
    if (T == NULL) {
        printf("找不到待删除元素\n");
        exit;
    } else if (x < T->Element)
        T->left = delete(x, T->left);
    else if (x > T->Element)
        T->right = delete(x, T->right);
    // 左右子树都存在,替换后删除用来替换的元素
    else if (T->left != NULL && T->right != NULL) {
        p = findMin(T->right); 
        T->Element = p->Element;
        // 这里注意使用新的元素进行查找
        T->right = delete(p->Element, T->right); 
    } else {
        p = T;
        if (T->left == NULL)
            T = T->right;
        else if (T->right == NULL)
            T = T->left;
        free(p);
    }
    return T;
}

5. 其他操作诸如遍历之类的方法以及测试代码见文末链接。

c语言完整实现:BinarySearchTree.c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值