联盟查找数据结构

时间:2011-11-28 17:55:30

标签: c++ data-structures union-find

对于许多问题,我看到推荐的解决方案是使用union-find数据结构。我试着阅读它并思考它是如何实现的(使用C ++)。我目前的理解是它只是一组集合。因此,要查找元素所属的集合,我们需要n*log n个操作。当我们必须执行union时,我们必须找到需要合并的两个集合并对它们执行set_union。这对我来说看起来并不十分有效。我对这个数据结构的理解是正确的还是我错过了什么?

4 个答案:

答案 0 :(得分:17)

这是很晚的回复,但这可能在stackoverflow上没有得到解答,因为这是搜索union-find的人的最顶层页面,这里是详细的解决方案。

Find-Union是一种非常快速的操作,在接近恒定的时间内执行。 它遵循Jeremie对路径压缩和跟踪集合大小的见解。对每个查找操作本身执行路径压缩,从而采用摊销的lg *(n)时间。 lg *类似于逆Ackerman函数,增长非常慢,很少超过5(至少直到n <2 ^ 65535)。 Union / Merge集合是懒惰的,只需将1个根指向另一个,特别是较小的set的根到较大的set的根,这是在恒定时间内完成的。

请参阅https://github.com/kartikkukreja/blog-codes/blob/master/src/Union%20Find%20%28Disjoint%20Set%29%20Data%20Structure.cpp

中的以下代码
public partial class Form1 : Form
{
    Random rnd = new Random { };
    double xEnd = 0;
    double yEnd = 0;
    double xOrigin = 30;
    double yOrigin = 450;
    double xFire;
    double yFire;
    double xTarget = 500;
    double yTarget;
    double yTargetEnd;
    double xHor = 30;
    double yHor = 350;
    double xVert = 130;
    double yVert = 450;
    double fireLine = 750;
    double lineLength = 50;
    double targetLine = 50;

    public Form1()
    {
        xEnd = xOrigin + lineLength;
        yEnd = yOrigin;
        xFire = xOrigin + fireLine;
        yFire = yOrigin;

        InitializeComponent(); 
    }

    private void LineDrawEvent(object sender, PaintEventArgs paint)
    {
        Graphics drawSurface = paint.Graphics;
        Pen turretLine = new Pen(Color.Blue);
        Pen graphHorizontal = new Pen(Color.Red);
        Pen graphVertical = new Pen(Color.Red);
        Pen firedLine = new Pen(Color.Blue);
        Pen targetLine = new Pen(Color.Black);
        float[] dashValues = { 5, 2 };
        firedLine.DashPattern = dashValues;

        drawSurface.DrawLine(graphVertical, (int)xOrigin, (int)yOrigin, (int)xHor, (int)yHor);
        drawSurface.DrawLine(graphHorizontal, (int)xOrigin, (int)yOrigin, (int)xVert, (int)yVert);
        drawSurface.DrawLine(firedLine, (int)xOrigin, (int)yOrigin, (int)xFire, (int)yFire);
        drawSurface.DrawLine(targetLine, (int)xTarget, (int)yTarget, (int)xTarget, (int)yTargetEnd);

        double angleInRadians = ConvertDegsToRads((double)trckBarAngle.Value);
        xEnd = xOrigin + lineLength * Math.Cos(angleInRadians / 2.0);
        yEnd = yOrigin - lineLength * Math.Sin(angleInRadians / 2.0);

        drawSurface.DrawLine(turretLine, (int)xOrigin, (int)yOrigin, (int)xEnd, (int)yEnd);
        this.Refresh();
    }

    private void trckBarAngle_Scroll(object sender, EventArgs e)
    {
        lblAngle.Text = "Angle is:" + Convert.ToString((double)trckBarAngle.Value / 2.0);  
    }

    private double ConvertDegsToRads(double degrees)
    {
        return degrees * (Math.PI / 180.0);
    }

    private void btnFire_Click(object sender, EventArgs e)
    {
        double angleInDegrees = trckBarAngle.Value;

        double angleInRadians = ConvertDegsToRads(angleInDegrees);
        xFire = xOrigin + fireLine * Math.Cos(angleInRadians / 2.0);
        yFire = yOrigin - fireLine * Math.Sin(angleInRadians / 2.0);
        this.Refresh();
    }

    private void btnRedraw_Click(object sender, EventArgs e)
    {
        yTargetEnd = yTarget - targetLine;
        yTarget = rnd.Next(100, 500);
    }
}

答案 1 :(得分:5)

数据结构可以表示为树,其中分支被反转(而不是向下指向,分支向上指向父级 - 并将子项与其父级链接)。

如果我没记错的话,可以(很容易)显示:

  • 该路径压缩(每当您查找集合A的“父”时,您“压缩”该路径,以便以后对这些路径的每次调用将在时间O(1)中提供父将)导致每次呼叫的O(log n)复杂度;

  • 平衡(你大致跟踪每组儿童的数量,以及当你必须“联合”两套时,你制作一个儿童少一个孩子的那个)也每次通话都会导致O(log n)复杂度。

更复杂的证明可以表明,当你结合两种优化时,你得到的平均复杂度是逆Ackermann函数,写成α(n),这是Tarjan在这种结构中的主要发明。

我相信,后来证明,对于某些特定的使用模式,这种复杂性实际上是不变的(尽管对于所有实际目的,ackermann的逆约为4)。根据Union-Find的维基百科页面,在1989年,任何等效数据结构的每次操作的摊余成本显示为Ω(α(n)),证明当前的实现是渐近最优的。

答案 2 :(得分:2)

正确的union-find数据结构在每次查找期间都使用路径压缩。这会使成本摊销,然后每个操作都与ackermann函数的倒数成比例,这基本上使它保持不变(但不完全)。

如果您从头开始实施,那么我建议使用基于树的方法。

答案 3 :(得分:0)

一个简单的联合集结构保持一个数组(元素 - >设置),使得查找设置恒定时间;更新它们是按时间分摊,并且连接列表是不变的。不像上面的一些方法那么快,但是编程很简单,而且足以改善Kruskal的最小生成树算法的Big-O运行时间。