1.5.11

1.5.11 #

解答 #

类似于加权 quick-union 的做法,新增一个 size[] 数组以记录各个根节点的大小。

每次合并时先比较一下两棵树的大小,再进行合并。

这样会略微减少赋值语句的执行次数,提升性能。

代码 #

WeightedQuickFindUF.cs

/// <summary>
/// 用加权 QuickFind 算法实现的并查集。
/// </summary>
public class WeightedQuickFindUf
{
    private readonly int[] _size; // 记录每个连通分量的大小。
    private readonly int[] _id; // 记录每个结点的连通分量。
    private int _count;// 连通分量总数。

    public int ArrayVisitCount { get; private set; } //记录数组访问的次数。

    /// <summary>
    /// 新建一个使用加权 quick-find 实现的并查集。
    /// </summary>
    /// <param name="n">并查集的大小。</param>
    public WeightedQuickFindUf(int n)
    {
        _count = n;
        _id = new int[n];
        _size = new int[n];
        for (var i = 0; i < n; i++)
        {
            _id[i] = i;
            _size[i] = 1;
        }
    }

    /// <summary>
    /// 重置数组访问计数。
    /// </summary>
    public void ResetArrayCount()
    {
        ArrayVisitCount = 0;
    }

    /// <summary>
    /// 表示并查集中连通分量的数量。
    /// </summary>
    /// <returns>返回并查集中连通分量的数量。</returns>
    public int Count()
    {
        return _count;
    }
        
    /// <summary>
    /// 寻找 p 所在的连通分量。
    /// </summary>
    /// <param name="p">需要寻找的结点。</param>
    /// <returns>返回 p 所在的连通分量。</returns>
    public int Find(int p)
    {
        Validate(p);
        ArrayVisitCount++;
        return _id[p];
    }

    /// <summary>
    /// 判断两个结点是否属于同一个连通分量。
    /// </summary>
    /// <param name="p">需要判断的结点。</param>
    /// <param name="q">需要判断的另一个结点。</param>
    /// <returns>如果属于同一个连通分量则返回 true,否则返回 false。</returns>
    public bool IsConnected(int p, int q)
    {
        Validate(p);
        Validate(q);
        ArrayVisitCount += 2;
        return _id[p] == _id[q];
    }

    /// <summary>
    /// 将两个结点所在的连通分量合并。
    /// </summary>
    /// <param name="p">需要合并的结点。</param>
    /// <param name="q">需要合并的另一个结点。</param>
    public void Union(int p, int q)
    {
        Validate(p);
        Validate(q);
        var pId = _id[p];
        var qId = _id[q];
        ArrayVisitCount += 2;

        // 如果两个结点同属于一个连通分量,那么什么也不做。
        if (pId == qId)
        {
            return;
        }

        // 判断较大的连通分量和较小的连通分量。
        int larger;
        int smaller;
        if (_size[pId] > _size[qId])
        {
            larger = pId;
            smaller = qId;
            _size[pId] += _size[qId];
        }
        else
        {
            larger = qId;
            smaller = pId;
            _size[qId] += _size[pId];
        }

        // 将较小的连通分量连接到较大的连通分量上,
        // 这会减少赋值语句的执行次数,略微减少数组访问。
        for (var i = 0; i < _id.Length; i++)
        {
            if (_id[i] == smaller)
            {
                _id[i] = larger;
                ArrayVisitCount++;
            }
        }

        ArrayVisitCount += _id.Length;
        _count--;
    }

    /// <summary>
    /// 获得 id 数组。
    /// </summary>
    /// <returns>id 数组。</returns>
    public int[] GetId()
    {
        return _id;
    }

    /// <summary>
    /// 验证输入的结点是否有效。
    /// </summary>
    /// <param name="p">需要验证的结点。</param>
    /// <exception cref="ArgumentException">输入的 p 值无效。</exception>
    private void Validate(int p)
    {
        var n = _id.Length;
        if (p < 0 || p > n)
        {
            throw new ArgumentException("index " + p + " is not between 0 and " + (n - 1));
        }
    }
}

Main 方法

char[] split = { '\n', '\r' };
var input = File.ReadAllText(DataFiles.MediumUf).Split(split, StringSplitOptions.RemoveEmptyEntries);
var size = int.Parse(input[0]);

var quickFind = new QuickFindUf(size);
var weightedQuickFind = new WeightedQuickFindUf(size);
for (var i = 1; i < size; i++)
{
    var pair = input[i].Split(' ');
    var p = int.Parse(pair[0]);
    var q = int.Parse(pair[1]);
    quickFind.Union(p, q);
    weightedQuickFind.Union(p, q);
}

Console.WriteLine("quick-find: " + quickFind.ArrayVisitCount);
Console.WriteLine("weighted quick-find: " + weightedQuickFind.ArrayVisitCount);

另请参阅 #

UnionFind 库