详解二叉搜索树(BinarySearchTree)

I 二叉搜索树特点

  1. 每个结点有唯一的值,且每个结点的值均不相同
  2. 若它的左子树不为空,则它的左子树的所有结点均小于根节点的值
  3. 若它的右子树不为空,则它的右子树的所有结点均大于根结点的值
  4. 它的左右子树均为二叉搜索树。

II 二叉搜索树的操作

二叉查找树由节点组成,所以需要一个 Node 类

public class Node
{
    public int Data;
    public Node left;
    public Node right;
    public void DisplayNode()
    {
        Console.Write(iData);
    }
}

1 插入

插入的步骤

1.创建一个 Node 对象,并且把 Node 存储的数据赋值给 iData 变量

2.查看 BST 是否有根节点。如果没有,那么说明这是一个新的 BST,并且要插入的节点就是根节
点。如果是这种情况,那么就结束这个方法。

3.如果要添加的节点不是根节点,那么为了找到合适的插入点需要遍历 BST(BinarySearchTree),确定节点正确位置的算法如下:

       1)把父节点设置为当前节点,即根节点。
       2)如果新节点内的数据值小于当前节点内的数据值,那么把当前节点设置为当前节点的左子节点。如果新节
点内的数据值大于当前节点内的数据值,那么就跳到步骤 4。
       3)如果当前节点的左子节点的数值为空(null),就把新节点插入在这里并且退出循环。否则,跳到 while 循
环的下一次循环操作中。
       4)把当前节点设置为当前节点的右子节点。
       5)如果当前节点的右子节点的数值为空(null),就把新节点插入在这里并且退出循环。否则,跳到 while 循
环的下一次循环操作中。

代码如下:

public class BinarySearchTree
{
    public Node root;
    public BinarySearchTree()
    {
        root = null;
    }

    public void Insert(int i)
    {
        Node newNode = new Node();
        newNode.Data = i;
        if (root == null)
            root = newNode;
        else
        {
            Node current = root;
            Node parent;
            while (true)
            {
                parent = current;
                if (i < current.Data)
                {
                    current = current.Left;
                    if (current == null)
                    {
                        parent.Left = newNode;
                        break;
                    }
                }
                else
                {
                    current = current.Right;
                    if (current == null)
                    {
                        parent.Right = newNode;
                        break;
                    }
                }
            }
       }
    }
}

2 查找

这个方法首先创建一个Node 节点,并且把它设置为 BST 的根节点。接下来方法会查看关键字(要查找的数据)是否在这个节点内。如果在,那么这个方法就简单地返回当前节点并且退出。如果在根节点内没有找到该数据,就把要查找的数据与存储在
当前节点内的数据进行比较。如果关键字小于当前数据值,就把当前节点设置为左子节点。如果关键字大于当前数据值,就把当前节点设置为右子节点。如果当前节点为空(null),那么这个方法的最后一段就会返回空(null)作为方法的返回值,这表明到达 BST 的末尾也没有找到关键值。当 while 循环结束的时候,在 current 中存储的数值就是要查找的数值。

public Node Find(int key)
{
    Node current = root;
    while (current.iData != key)
    {
        if (key < current.iData)
            current = current.Left;
        else
            current = current.Right;
        if (current == null) return null;
    }
    return current;
}

3 删除

1)删除叶子节点

因为不需要考虑子节点的问题,所以移除叶子是最简单的事情。唯一要做的就是把目标节点的父节点的每一个子节点设置为null。当然,节点始终存在,只是与该节点没有任何连接了。

public boolNode Delete(int key)
{
    Node current = root;
    Node parent = root;
    bool isLeftChild = true;
    while (current.Data != key)
    {
        parent = current;
        if (key < current.Data)
        {
            isLeftChild = true;
            current = current.Left;
        }
        else
        {
            isLeftChild = false;
            current = current.Right;
        }

        if (current == null)
            return false;
    }

    if ((current.Left == null) & (current.Right == null))
    {
        if (current == root)
            root == null;
        else if (isLeftChild)
            parent.Left = null;
        else
            parent.Right = null;
    }
    
    return true;
}

2)删除带有一个子节点的节点

当要删除的节点有一个子节点的时候,需要检查四个条件:1.这个节点的子节点可能是左子节点;2.这个节点的子节点可能是右子节点;3.要删除的这个节点可能是左子节点;4.要删除的这个节点可能是右子节点。

代码:

public boolNode Delete(int key)
{
    Node current = root;
    Node parent = root;
    bool isLeftChild = true;
    while (current.Data != key)
    {
        parent = current;
        if (key < current.Data)
        {
            isLeftChild = true;
            current = current.Right;
        }
        else
        {
            isLeftChild = false;
            current = current.Right;
        }

        if (current == null)
            return false;
    }

    if ((current.Left == null) & (current.Right == null))
    {
        if (current == root)
            root == null;
        else if (isLeftChild)
            parent.Left = null;
        else
            parent.Right = null;
    }
    else if (current.Right == null)
    {
        if (current == root)
            root = current.Left;
        else if (isLeftChild)
            parent.Left = current.Left;
        else
            parent.Right = current.Right;
    }
    else if (current.Left == null)
    {
        if (current == root)
            root = current.Right;
        else if (isLeftChild)
            parent.Left = parent.Right;
        else
            parent.Right = current.Right;
    }

    return true;
}

3)删除带有两个子节点的节点

要删除拥有2个子节点的节点时,需要重构以子节点为根的子树。做法是找到这个子节点在中序遍历的后一个节点,成为后继节点,把后继节点移动到要删除的位置上,来保证二叉搜索树的结构规则。

(还有另外一种方法删除带有2个子节点的节点,也是我后来才看到的,思路就是把后继节点的值赋值给要删除的节点,然后问题就转变为删除后继节点了,而此时后继节点肯定是没有左树的,因为找后继节点就是要删除节点右子树中最小的一个。所以最终就转变为了删除带有右子节点的问题。以下代码理解起来没有这个简单,但也是一个正确的方法)

这里不再单独写2个子节点的代码,直接贴出综合以上几种情况的完整delete方法的代码:

public Node GetSuccessor(Node delNode)
{
    Node successorParent = delNode;
    Node successor = delNode;
    Node current = delNode.Right;
    while (!(current == null))
    {
        successorParent = current;
        successor = current;
        current = current.Left;
    }

    if (!(successor == delNode.Right))
    {
        successorParent.Left = successor.Right;
        successor.Right = delNode.Right;
    }
    return successor;
}


public bool Delete(int key)
{
    Node current = root;
    Node parent = root;
    bool isLeftChild = true;
    while (current.Data != key)
    {    
        parent = current;
        if (key < current.Data)
        {
            isLeftChild = true;
            current = current.Right;
        }
        else
        {
            isLeftChild = false;
                current = current.Right;
        }

        if (current == null) return false;
    }

    if ((current.Left == null) & (current.Right == null))//当删除叶子节点
    {
        if (current == root)
            root = null;
        else if (isLeftChild)
            parent.Left = null;
        else if (isRightChild)
            parent.Right = null;
    }
    else if (current.Right == null)//当删除带有左节点的节点
    {
        if (current == root)
            root = current.Left;
        else if (isLeftChild)
            parent.Left = current.Left;
        else
            parent.Right = current.Right;
    }
    else if (current.Left == null)//当删除带有右节点的节点
    {
        if (current == root)
            root = current.Right;
        else if (isLeftChild)
            parent.Left = current.Right;
        else
            parent.Right = current.Right;
    }
    else//删除2个子节点的节点
    {
        Node successor = GetSuccessor(current);
        if (current == root)
            root = successor;
        else if (isLeftChild)
            parent.Left = successor;
        else
            parent.Right = successor;

        successor.Left = current.Left;
    }

    return true;
}

III 二叉搜索树的性能分析

二叉搜索树的高度计算:假设是一颗完美二叉树,n为节点的总个数,那么(n+1)/2为最后一层节点数,除以h-1次的2就是第一层的节点数即为1.

例如,如果是一颗高度为5的满二叉树,每层节点数如下:
       1
       2
       4
       8
      16

那么整棵树的节点数为2^h-1=31, 最后一层节点数为(31+1)/2=16,因为有5层,所以要向上除以4次2,得到第一层的节点数目为1.即计算公式为

(n+1)/2 / 2^(n-1) = 1

h = log2(n+1)

1.时间复杂度

查找效率:循环体内一共2次判断操作 一次赋值操作 

如果是执行n次就是 3n + 1 这个1是    while (current.iData != key) 这句

但是二叉搜索树是一共log2(n+1)层,所以深度是log2n,循环是log2n次

最后=3log2n =O(log2n)

也就是说以上述假定完美二叉树的情况下,时间复杂度是 O(log2n) 如果二叉搜索树完全不平衡则其深度可达到n,查找效率为O(n),退化为顺序查找。如下图:

 

2.空间复杂度

要构建二叉搜索树,是需要通过一个个插入排序的,所以空间复杂度为O(n)

3 平均查找长度

公式:ASL = [(n+1)/n] * log2(n+1) - 1

总结

二叉搜索树的出现是为了优化查找效率,但当一个二叉树不平衡时,甚至为单支树,它的查找效率会退化到与顺序查找效率相近,为了解决这个问题,有了AVL树(二叉平衡树)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值