3.2.41

上次更新:2019-11-07
发现了题解错误/代码缺陷/排版问题?请点这里:如何:提交反馈

解答

大体上和标准 BST 实现差不多,做如下变换即可:

x.Key => _nodes[x].Key;
x.Value => _nodes[x].Value;
x.Left => _left[x];
x.Right => _right[x];

由于使用了数组,在正常「删除」二叉树结点之后,还需要手工「垃圾回收」,如下图所示:

性能比较:

可见数组实现在删除节点时有巨大的性能差距。

代码

private readonly Node[] _nodes;
private readonly int[] _left;
private readonly int[] _right;
private int _size;
private int _root;

/// <summary>
/// 二叉搜索树的结点。
/// </summary>
private class Node
{
    public TKey Key { get; set; }
    public TValue Value { get; set; }
}

/// <summary>
/// 建立一个以数组为基础的二叉搜索树。
/// </summary>
/// <param name="maxSize">二叉搜索树中的结点数。</param>
public BSTArray(int maxSize)
{
    _nodes = new Node[maxSize];
    _left = new int[maxSize];
    _right = new int[maxSize];
    for (var i = 0; i < maxSize; i++)
    {
        _left[i] = -1;
        _right[i] = -1;
    }
    _size = 0;
    _root = 0;
}

/// <summary>
/// 向符号表插入键值对。
/// </summary>
/// <param name="key">键。</param>
/// <param name="value">值。</param>
public void Put(TKey key, TValue value)
{
    if (_size == _nodes.Length)
    {
        throw new InvalidOperationException("BST is full");
    }

    if (IsEmpty())
    {
        _nodes[_size] = new Node{Key = key, Value = value};
        _size++;
        return;
    }

    Put(key, value, null, _root);
}

/// <summary>
/// 向二叉树插入键值对。
/// </summary>
/// <param name="key">键。</param>
/// <param name="value">值。</param>
/// <param name="treeSide">子树数组。</param>
/// <param name="parent">父结点下标。</param>
private void Put(TKey key, TValue value, int[] treeSide, int parent)
{
    int now;
    if (treeSide == null)               // init
    {
        now = parent;
    }
    else if (treeSide[parent] == -1)    // finish
    {
        _nodes[_size] = new Node { Key = key, Value = value };
        treeSide[parent] = _size;
        _size++;
        return;
    }
    else
    {
        now = treeSide[parent];
    }

    var cmp = _nodes[now].Key.CompareTo(key);
    if (cmp > 0)
    {
        Put(key, value, _left, now);
    }
    else if (cmp < 0)
    {
        Put(key, value, _right, now);
    }
    else
    {
        _nodes[now].Value = value;
    }
}

/// <summary>
/// 获取键 <paramref name="key"/> 对应的值,不存在则返回 <c>default(Value)</c>。
/// </summary>
/// <param name="key">键。</param>
/// <returns>键 <paramref name="key"/> 对应的值,不存在则返回 <c>default(Value)</c>。</returns>
public TValue Get(TKey key)
{
    var indices = Get(key, _root);
    if (indices == -1)
    {
        return default;
    }

    return _nodes[indices].Value;
}

/// <summary>
/// 获取 <paramref name="key"/> 对应的下标,不存在则返回 -1。
/// </summary>
/// <param name="key">键。</param>
/// <param name="start">起始搜索下标。</param>
/// <returns>找到则返回对应下标,否则返回 -1。</returns>
private int Get(TKey key, int start)
{
    var now = start;
    while (now != -1)
    {
        var cmp = _nodes[now].Key.CompareTo(key);
        if (cmp > 0)
        {
            now = _left[now];
        }
        else if (cmp < 0)
        {
            now = _right[now];
        }
        else
        {
            return now;
        }
    }

    return -1;
}

/// <summary>
/// 从表中删去键 <paramref name="key"/> 及其对应的值。
/// </summary>
/// <param name="key">要删除的键。</param>
public void Delete(TKey key)
{
    var toDelete = Get(key, _root);
    if (toDelete == -1)
    {
        throw new InvalidOperationException("No Such Key in BST");
    }

    _root = Delete(key, _root);
    RemoveNode(toDelete);
}

/// <summary>
/// 从根结点为 <paramref name="root"/> 的二叉搜索树中删除键为 <paramref name="key"/> 的结点。
/// 返回删除结点后树的根结点下标。
/// </summary>
/// <param name="key">要删除的键。</param>
/// <param name="root">根结点。</param>
/// <returns>删除结点后树的根结点下标。</returns>
private int Delete(TKey key, int root)
{
    if (root == -1 || _nodes[root] == null)
    {
        return -1;
    }

    var cmp = _nodes[root].Key.CompareTo(key);
    if (cmp > 0)
    {
        _left[root] = Delete(key, _left[root]);
    }
    else if (cmp < 0)
    {
        _right[root] = Delete(key, _right[root]);
    }
    else
    {
        if (_left[root] == -1)
        {
            return _right[root];
        }

        if (_right[root] == -1)
        {
            return _left[root];
        }

        var toReplace = Min(_right[root]);
        _right[toReplace] = DeleteMin(_right[root]);
        _left[toReplace] = _left[root];
        root = toReplace;
    }

    return root;
}

/// <summary>
/// 键 <paramref name="key"/> 在表中是否存在对应的值。
/// </summary>
/// <param name="key">键。</param>
/// <returns>如果存在则返回 <c>true</c>,否则返回 <c>false</c>。</returns>
public bool Contains(TKey key)
{
    return Get(key, _root) > -1;
}

/// <summary>
/// 符号表是否为空。
/// </summary>
/// <returns>为空则返回 <c>true</c>,否则返回 <c>false</c>。</returns>
public bool IsEmpty()
{
    return _size == 0;
}

/// <summary>
/// 获得符号表中键值对的数量。
/// </summary>
/// <returns>符号表中键值对的数量。</returns>
public int Size()
{
    return _size;
}

/// <summary>
/// [<paramref name="lo"/>, <paramref name="hi"/>] 之间键的数量。
/// </summary>
/// <param name="lo">范围起点。</param>
/// <param name="hi">范围终点。</param>
/// <returns>[<paramref name="lo"/>, <paramref name="hi"/>] 之间键的数量。</returns>
public int Size(TKey lo, TKey hi)
{
    return Keys(lo, hi).Count();
}

/// <summary>
/// 计算以 <paramref name="root"/> 为根结点的二叉树的大小。
/// </summary>
/// <param name="root">二叉树的根结点下标。</param>
/// <returns>二叉树中的结点个数。</returns>
private int Size(int root)
{
    if (root == -1)
    {
        return 0;
    }

    return 1 + Size(_left[root]) + Size(_right[root]);
}

/// <summary>
/// 获得符号表中所有键的集合。
/// </summary>
/// <returns>符号表中所有键的集合。</returns>
public IEnumerable<TKey> Keys()
{
    if (IsEmpty())
    {
        return new List<TKey>();
    }
    return Keys(Min(), Max());
}

/// <summary>
/// 获得符号表中 [<paramref name="lo"/>, <paramref name="hi"/>] 之间的键。
/// </summary>
/// <param name="lo">范围起点。</param>
/// <param name="hi">范围终点。</param>
/// <returns>符号表中 [<paramref name="lo"/>, <paramref name="hi"/>] 之间的键。</returns>
public IEnumerable<TKey> Keys(TKey lo, TKey hi)
{
    if (lo == null)
        throw new ArgumentNullException("first argument to keys() is null");
    if (hi == null)
        throw new ArgumentNullException("second argument to keys() is null");

    var queue = new Queue<TKey>();
    Keys(_root, queue, lo, hi);
    return queue;
}

/// <summary>
/// 获取二叉查找树中在 <paramref name="lo"/> 和 <paramref name="hi"/> 之间的所有键。
/// </summary>
/// <param name="x">二叉查找树的根结点。</param>
/// <param name="queue">要填充的队列。</param>
/// <param name="lo">键的下限。</param>
/// <param name="hi">键的上限。</param>
private void Keys(int x, Queue<TKey> queue, TKey lo, TKey hi)
{
    if (x == -1)
    {
        return;
    }

    var cmplo = lo.CompareTo(_nodes[x].Key);
    var cmphi = hi.CompareTo(_nodes[x].Key);
    if (cmplo < 0)
    {
        Keys(_left[x], queue, lo, hi);
    }

    if (cmplo <= 0 && cmphi >= 0)
    {
        queue.Enqueue(_nodes[x].Key);
    }

    if (cmphi > 0)
    {
        Keys(_right[x], queue, lo, hi);
    }
}

/// <summary>
/// 最小的键。
/// </summary>
/// <returns>最小的键。</returns>
public TKey Min()
{
    if (IsEmpty())
    {
        throw new InvalidOperationException("BST is Empty!");
    }

    return _nodes[Min(_root)].Key;
}

/// <summary>
/// 在二叉查找树中查找包含最小键的结点。
/// </summary>
/// <param name="x">二叉查找树的根结点。</param>
/// <returns>包含最小键的结点。</returns>
private int Min(int x)
{
    if (_left[x] == -1)
    {
        return x;
    }

    return Min(_left[x]);
}

/// <summary>
/// 最大的键。
/// </summary>
/// <returns>最大的键。</returns>
public TKey Max()
{
    if (IsEmpty())
    {
        throw new InvalidOperationException("BST is Empty!");
    }

    return _nodes[Max(_root)].Key;
}

/// <summary>
/// 在二叉查找树中查找包含最大键的结点。
/// </summary>
/// <param name="x">二叉查找树的根结点。</param>
/// <returns>包含最大键的结点。</returns>
private int Max(int x)
{
    if (_right[x] == -1)
    {
        return x;
    }

    return Max(_right[x]);
}

/// <summary>
/// 小于等于 <paramref name="key"/> 的最大值。
/// </summary>
/// <returns>小于等于 <paramref name="key"/> 的最大值。</returns>
public TKey Floor(TKey key)
{
    if (key == null)
    {
        throw new ArgumentNullException("argument to floor is null");
    }

    if (IsEmpty())
    {
        throw new InvalidOperationException("calls floor with empty symbol table");
    }
    var x = Floor(_root, key);
    if (x == -1)
    {
        return default;
    }
    else
    {
        return _nodes[x].Key;
    }
}

/// <summary>
/// 获得符号表中小于等于 <paramref name="key"/> 的最大结点。
/// </summary>
/// <param name="x">二叉查找树的根结点。</param>
/// <param name="key">键。</param>
/// <returns>小于等于 <paramref name="key"/> 的最大结点。</returns>
private int Floor(int x, TKey key)
{
    if (x == -1)
    {
        return -1;
    }
    var cmp = key.CompareTo(_nodes[x].Key);
    if (cmp == 0)
    {
        return x;
    }
    else if (cmp < 0)
    {
        return Floor(_left[x], key);
    }
    var t = Floor(_right[x], key);
    if (t != -1)
    {
        return t;
    }

    return x;
}

/// <summary>
/// 大于等于 <paramref name="key"/> 的最小值。
/// </summary>
/// <returns>大于等于 <paramref name="key"/> 的最小值。</returns>
public TKey Ceiling(TKey key)
{
    if (key == null)
    {
        throw new ArgumentNullException("argument to ceiling is null");
    }

    if (IsEmpty())
    {
        throw new InvalidOperationException("calls ceiling with empty symbol table");
    }
    var x = Ceiling(_root, key);
    if (x == -1)
    {
        return default;
    }
    return _nodes[x].Key;
}

/// <summary>
/// 获取符号表中大于等于 <paramref name="key"/> 的最小结点。
/// </summary>
/// <param name="x">二叉查找树的根结点。</param>
/// <param name="key">键。</param>
/// <returns>符号表中大于等于 <paramref name="key"/> 的最小结点。</returns>
private int Ceiling(int x, TKey key)
{
    if (x == -1)
    {
        return -1;
    }
    var cmp = key.CompareTo(_nodes[x].Key);
    if (cmp == 0)
    {
        return x;
    }
    if (cmp < 0)
    {
        var t = Ceiling(_left[x], key);
        if (t != -1)
        {
            return t;
        }
        return x;
    }
    return Ceiling(_right[x], key);
}

/// <summary>
/// 小于 <paramref name="key"/> 的键的数量。
/// </summary>
/// <returns>小于 <paramref name="key"/> 的键的数量。</returns>
public int Rank(TKey key)
{
    if (key == null)
    {
        throw new ArgumentNullException("argument to rank() is null");
    }
    return Rank(_root, key);
}

/// <summary>
/// 返回 <paramref name="key"/> 在二叉查找树中的排名。
/// </summary>
/// <param name="x">二叉查找树的根结点。</param>
/// <param name="key">要查找排名的键。</param>
/// <returns><paramref name="key"/> 的排名。</returns>
private int Rank(int x, TKey key)
{
    if (x == -1)
    {
        return 0;
    }
    var cmp = key.CompareTo(_nodes[x].Key);
    if (cmp < 0)
    {
        return Rank(_left[x], key);
    }
    else if (cmp > 0)
    {
        return 1 + Size(_left[x]) + Rank(_right[x], key);
    }
    else
    {
        return Size(_left[x]);
    }
}

/// <summary>
/// 获得排名为 k 的键。
/// </summary>
/// <param name="k">需要获得的键的排名。</param>
/// <returns>排名为 k 的键。</returns>
public TKey Select(int k)
{
    if (k < 0 || k >= Size())
    {
        throw new ArgumentException("argument to select() is invaild: " + k);
    }
    var x = Select(_root, k);
    return _nodes[x].Key;
}

/// <summary>
/// 挑拣出排名为 <paramref name="k"/> 的结点。
/// </summary>
/// <param name="x">树的根结点。</param>
/// <param name="k">要挑拣的排名。</param>
/// <returns>排名为 <paramref name="k"/> 的结点。</returns>
private int Select(int x, int k)
{
    if (x == -1)
    {
        return -1;
    }
    var t = Size(_left[x]);
    if (t > k)
    {
        return Select(_left[x], k);
    }
    else if (t < k)
    {
        return Select(_right[x], k - t - 1);
    }
    else
    {
        return x;
    }
}

/// <summary>
/// 删除最小的键。
/// </summary>
public void DeleteMin()
{
    if (IsEmpty())
    {
        throw new InvalidOperationException("Symbol table underflow");
    }

    var minIndex = Min(_root);
    _root = DeleteMin(_root);
    RemoveNode(minIndex);
}

/// <summary>
/// 在以 <paramref name="x"/> 为根结点的二叉查找树中删除最小结点。
/// </summary>
/// <param name="x">二叉查找树的根结点。</param>
/// <returns>删除后的二叉查找树。</returns>
private int DeleteMin(int x)
{
    if (_left[x] == -1)
    {
        return _right[x];
    }

    _left[x] = DeleteMin(_left[x]);
    return x;
}

/// <summary>
/// 删除最大的键。
/// </summary>
public void DeleteMax()
{
    if (IsEmpty())
    {
        throw new InvalidOperationException("Symbol table underflow");
    }

    var maxIndex = Max(_root);
    _root = DeleteMax(_root);
    RemoveNode(maxIndex);
}

/// <summary>
/// 从指定二叉查找树中删除最大结点。
/// </summary>
/// <param name="x">二叉查找树的根结点。</param>
/// <returns>删除后的二叉查找树。</returns>
private int DeleteMax(int x)
{
    if (_right[x] == -1)
        return _left[x];
    _right[x] = DeleteMax(_right[x]);

    return x;
}

/// <summary>
/// 删除下标为 <paramref name="index"/> 的结点。
/// </summary>
/// <param name="index">要删除的结点下标。</param>
private void RemoveNode(int index)
{
    _size--;
    // Remove Node
    for (var i = index; i < _size; i++)
    {
        _nodes[i] = _nodes[i + 1];
        _left[i] = _left[i + 1];
        _right[i] = _right[i + 1];
    }
    // Adjust Index
    if (_root >= index)
    {
        _root--;
    }
    for (var i = 0; i < _size; i++)
    {
        if (_left[i] >= index)
        {
            _left[i]--;
        }

        if (_right[i] >= index)
        {
            _right[i]--;
        }
    }
}

另请参阅

BinarySearchTree 库

上一题 下一题