二叉堆(BinaryHeap)的一种C#实现

二叉堆(BinaryHeap)的一种C#实现

最近项目里用到了寻路,可以用两种算法实现,迪杰斯特拉和AStar。其中AStar是迪杰斯特拉算法的一种改进,所以使用更广泛一点。这篇文章不讨论这两种算法,而聚焦于这两种算法中都用到的一种数据结构——二叉堆(BinaryHeap)。

在这两种算法中的性能瓶颈最可能发生在从”开表”中取出路径最短的节点,也就是从一个集合中取出最小元素。最简单的方法当然就是对集合进行排序,然后取出最小值,而排序操作往往需要耗费大量的性能,尤其是元素较多时。其实我们只需要得到最小的元素,而不太关心其他元素。二叉堆就是这样一种数据结构。具体的概念和原理在这里:

浅析基础数据结构——二叉堆

总结来说,这种数据结构使用一个数组实现,它总是可以保证第0个元素是集合中最小(最小堆)或者最大(最大堆)的,而不保证之后的元素顺序。在寻路中需要的是最小堆。在二叉堆中插入,删除操作复杂度都为O(log2n),要优于大部分的排序算法。下面给出一种二叉堆在C#中的实现:

using System;

/* 二叉堆用于寻路
 * 注意T为值类型时默认值对结果的影响
 * 最小堆
 */
public class BinaryHeap where T : IComparable
{
    private readonly T[] items;
    private readonly bool IsClass;
    public int Count { get; private set; }
    public int MaxSize
    {
        get { return items == null ? -1 : items.Length; }
    }

    public BinaryHeap(int maxSize)
    {
        items = new T[maxSize];
        IsClass = typeof(T).IsClass;
        Count = 0;
    }

    public void Clear()
    {
        for (int i = 0; i < items.Length; ++i)
        {
            items[i] = default(T);
        }
        Count = 0;
    }

    /// 
    /// 压入元素
    /// 
    /// 
    public void Push(T node)
    {
        if (IsClass && node == null)
            return;

        if (Count >= items.Length)
            throw new IndexOutOfRangeException("Cant add node");

        //先放在尾部
        items[Count] = node;
        ++Count;

        //再找到该元素适当位置
        int current = Count - 1;
        int parent = (current - 1) / 2;

        while (items[parent].CompareTo(items[current]) > 0)
        {
            Exchange(parent, current);

            current = parent;
            parent = (current - 1) / 2;
        }
    }

    /// 
    /// 移除并返回堆中最小的元素
    /// 
    /// 
    public T Pop()
    {
        if (Count <= 0)
            throw new IndexOutOfRangeException("No element in BinaryHeap");

        var node = items[0];
        Remove(0);
        return node;
    }

    /// 
    /// 查找堆中一个指定条件的元素(不移除),返回结果
    /// 
    /// 
    /// 
    /// 
    public bool TryGet(Func func, out T item)
    {
        foreach (var node in items)
        {
            if (IsClass && node == null)
                break;

            if (func(node))
            {
                item = node;
                return true;
            }
        }
        item = default(T);
        return false;
    }

    /// 
    /// 移除堆中一个指定条件的元素,返回结果
    /// 
    /// 
    /// 
    public bool TryRemove(Func func)
    {
        int idx = 0;
        foreach (var node in items)
        {
            if (IsClass && node == null)
                break;

            if (func(node))
            {
                Remove(idx);
                return true;
            }
            ++idx;
        }
        return false;
    }

    /// 
    /// 移除一个指定索引的元素,并重新排布
    /// 
    /// 
    private void Remove(int idx)
    {
        if (Count <= 0)
            throw new IndexOutOfRangeException("No element in BinaryHeap");

        items[idx] = items[Count - 1];
        items[Count - 1] = default(T);
        Count--;

        int current = idx;

        while (true)
        {
            int child1 = current * 2 + 1;
            int child2 = current * 2 + 2;

            if (child1 >= Count)
                break;

            if (child2 >= Count)
            {
                if (items[child1].CompareTo(items[current]) < 0)
                    Exchange(child1, current);
                break;
            }

            int min;
            int max;
            if (items[child1].CompareTo(items[child2]) > 0)
            {
                min = child2;
                max = child1;
            }
            else
            {
                min = child1;
                max = child2;
            }

            if (items[current].CompareTo(items[min]) <= 0)
                break;

            Exchange(min, current);
            current = min;
            continue;
        }
    }

    private void Exchange(int idx1, int idx2)
    {
        var temp = items[idx1];
        items[idx1] = items[idx2];
        items[idx2] = temp;
    }
}

需要注意的是:

  1. 因为是用于寻路,所以只实现了最小堆,最大堆原理相同。
  2. 用泛型实现的,但二叉堆元素必须实现IComparable接口,毕竟元素要可以比较大小。
  3. 当元素为引用类型时做了特殊的处理,因为可以判断引用类型为null时的错误操作。

目前项目中使用似乎未发现问题,如果有任何bug欢迎指正。

你可能感兴趣的:(二叉堆(BinaryHeap)的一种C#实现)