【问题标题】:Topological Sorting, finding dependencies faster拓扑排序,更快地找到依赖关系
【发布时间】:2015-10-24 20:37:12
【问题描述】:

我几个月来一直在研究一个图形问题,现在我正在寻找其他人的新意见。

我在图书馆里呆了几个小时,看书。我非常有信心找到了解决方案,但也许这里有人可以给我一个新的视角。

问题:

我有一个二维平面和矩形。矩形可以相互重叠,我必须找到这些矩形的顺序。要在屏幕上可视化想象窗口,您必须找到一个订单以使其看起来正确

为了给你一张图片,这可能是一个输出:

给定:

  • 判断两个矩形是否相互重叠的函数

    public bool overlap(Rect a, Rect b) {...}
    
  • 给定两个矩形重叠的函数,决定先绘制哪个矩形

    //returns [1 => a before b, -1 => b before a, 0 => a & b have no "before (<)" relation]
    public int compare(Rect a, Rect b) {...}
    
  • 带有

    的矩形实体
    int x,y,width,height
    
  • 屏幕宽度和高度

    int screen.width, int screen.height
    

为了解决这个问题,可以忽略这两个函数的运行时复杂度。

可以将问题抽象为我想在其中找到正确的评估顺序的依赖关系图。矩形是 nodes 并且 isBefore 关系指定节点之间的 arcs。如图所示,该图可以有多个连接的组件。所以仅仅在所有节点上抛出Sort 是行不通的。 注意compare 避免了循环依赖,因此图将保持非循环。所以好消息是:秩序确实存在,耶!

现在困难的部分来了

如何尽快找到依赖关系,以便构建图形并在其上运行拓扑排序算法。

最幼稚和最糟糕的方法是只为每个对象上的每个对象执行compare,从而导致 O(n²) 复杂度。但这在这里是不可接受的,因为屏幕上可能有数千个这样的矩形。

那么,如何最大限度地减少节点数量,我必须将一个节点与 in oder 进行比较以找到所有依赖项?


现在这是我的解决方案。也许你应该在自己找到一些东西后阅读这篇文章,以避免产生偏见。

首先问题可以通过去掉一维来简化。这些问题仍然是同构的,但更容易理解,至少对我来说是这样。 所以让我们在一条大线(屏幕)上取线(矩形)。一条线有一个位置和一个长度。重叠的线构成一个连通分量。

  1. 由于我们的行数有限,我们可以找到最小的行 我们在 O(n) 中的一组线。

  2. 为了让 2 条线重叠,它们的最大距离就是我们最小线的长度。以上内容不能与我们重叠。

  3. 我们将屏幕除以最小行的大小,最后得到离散的块。我们为每个块创建一个 HashMap 和一个桶。我们现在可以将一行分类到这些桶中。

  4. 我们再次运行该集合 O(n) 并且可以非常容易地决定我们必须将我们的线放在哪个桶中。 position % smallest.length = i(position + length) % smallest.length = j 将给出我们 HashMap 的索引。我们将行从bucket[i]bucket[j] 排序到我们的HashMap 中。

  5. 我们现在已经最小化了我们必须与一条线进行比较的线集,以便找到它的所有依赖项。在对所有行执行此操作后,我们只需将一行与bucket[i-1]bucket[j+1] 中的所有其他行进行比较。无论如何,任何其他线都将远离我们重叠。模运算是有效的。存储桶的额外内存不应该太多。

这是我想出的最好的。也许这里有人有更好的解决方案。

【问题讨论】:

  • 为什么不使用简单的List&lt;Rectangle&gt;,其中索引是 z 顺序?然后将所有图形从下到上绘制到缓冲区中,然后blit到屏幕。 5 个矩形应该不是问题(讽刺)。
  • 找到正确的顺序是问题所在。我不能只接受任何订单并将其绘制在屏幕上。

标签: c# graph-theory topological-sort


【解决方案1】:

一些观察:

  • 按最小线的大小划分屏幕会使算法非常不可预测,甚至没有必要。您可以使用任何大小的存储桶,并且该算法可以正常工作。
  • 检查bucket[i-1]bucket[j+1]是没有必要的,bucket[i]bucket[j]就足够了
  • 设 A 和 B 为矩形,B 不比 A 宽。则 B 的左边缘或右边缘的一部分位于 A 上或这些矩形不重叠(稍后会用到)。

所以我做的算法:

  1. 为每个矩形计算它所属的桶的范围 (bucketXFrom, bucketXTo, bucketYFrom, bucketYTo)。这是课 RectangleInBucket
  2. 按 (bucketXTo - bucketXFrom) 对它们进行排序。由于没有那么多桶,它基本上是一步基数排序。
  3. 对于每个矩形,从宽度最小的开始,扫描它所属的所有桶。如果有矩形,比较它们并保存存在的关系。将矩形保存到左右边缘下方的存储桶中。

我使桶的总数等于矩形的数量,它似乎效果最好。

它通常比朴素算法快,但没有人们想象的那么快。由于矩形可以(并且确实)属于许多桶,因此需要多次重新检查一个关系。这增加了步数。此外,它使用不那么便宜的结构来进行重复数据删除。但是compare 调用的数量很容易减少了几倍。即使这个调用非常便宜并且当compare 函数不是微不足道时差异会增加,它也会得到回报。最后是代码:

   public class Rectangle
    {
        public int x;
        public int y;
        public int width;
        public int height;
    }

    /// <summary>
    /// Creates array of objects
    /// </summary>
    protected T[] InitializeArray<T>(int length) where T : new()
    {
        T[] array = new T[length];
        for (int i = 0; i < length; ++i)
        {
            array[i] = new T();
        }

        return array;
    }

    /// <summary>
    /// Creates array of objects
    /// </summary>
    protected T[,] InitializeArray<T>(int length, int width) where T : new()
    {
        T[,] array = new T[length, width];
        for (int i = 0; i < length; ++i)
        {
            for (int j = 0; j < width; ++j)
            {
                array[i, j] = new T();
            }
        }

        return array;
    }

    protected class RectangleInBucket
    {
        public readonly Rectangle Rect;
        public readonly int RecNo;
        public readonly int bucketXFrom;
        public readonly int bucketXTo;
        public readonly int bucketYFrom;
        public readonly int bucketYTo;
        public RectangleInBucket(Rectangle rectangle, int recNo, int bucketSizeX, int bucketSizeY)
        {
            Rect = rectangle;
            RecNo = recNo;// arbitrary number unique for this rectangle
            bucketXFrom = Rect.x / bucketSizeX;
            bucketXTo = (Rect.x + Rect.width) / bucketSizeX;
            bucketYFrom = Rect.y / bucketSizeY;
            bucketYTo = (Rect.y + Rect.height) / bucketSizeY;
        }
    }

    /// <summary>
    /// Evaluates rectagle wrapped in RectangleInBucket object against all rectangles in bucket.
    /// Saves result into tmpResult.
    /// </summary>
    protected void processBucket(Dictionary<long, int> tmpResult, List<RectangleInBucket> bucket, RectangleInBucket rib)
    {
        foreach (RectangleInBucket bucketRect in bucket)
        {
            if (bucketRect.RecNo < rib.RecNo)
            {
                long actualCouple = bucketRect.RecNo + (((long)rib.RecNo) << 32);
                if (tmpResult.ContainsKey(actualCouple)) { continue; }
                tmpResult[actualCouple] = overlap(bucketRect.Rect, rib.Rect) ? compare(bucketRect.Rect, rib.Rect) : 0;
            }
            else
            {
                long actualCouple = rib.RecNo + (((long)bucketRect.RecNo) << 32);
                if (tmpResult.ContainsKey(actualCouple)) { continue; }
                tmpResult[actualCouple] = overlap(rib.Rect, bucketRect.Rect) ? compare(rib.Rect, bucketRect.Rect) : 0;
            }
        }
    }

    /// <summary>
    /// Calculates all couples of rectangles where result of "compare" function is not zero
    /// </summary>
    /// <param name="ra">Array of all rectangles</param>
    /// <param name="screenWidth"></param>
    /// <param name="screenHeight"></param>
    /// <returns>Couple of rectangles and value of "compare" function</returns>
    public List<Tuple<Rectangle, Rectangle, int>> GetRelations(Rectangle[] ra, int screenWidth, int screenHeight)
    {
        Dictionary<long, int> tmpResult = new Dictionary<long, int>();
        // the key represents couple of rectangles. As index of one rectangle is int,
        // two indexes can be stored in long. First index must be smaller than second,
        // this ensures couple can be inserted only once. Value of dictionary is result 
        // of "compare" function for this couple.

        int bucketSizeX = Math.Max(1, (int)Math.Sqrt(screenWidth * screenHeight / ra.Length));
        int bucketSizeY = bucketSizeX;

        int bucketsNoX = (screenWidth + bucketSizeX - 1) / bucketSizeX;
        int bucketsNoY = (screenHeight + bucketSizeY - 1) / bucketSizeY;

        List<RectangleInBucket>[,] buckets = InitializeArray<List<RectangleInBucket>>(bucketsNoX, bucketsNoY);
        List<RectangleInBucket>[] sortedRects = InitializeArray<List<RectangleInBucket>>(bucketsNoX);

        for (int i = 0; i < ra.Length; ++i)
        {
            RectangleInBucket rib = new RectangleInBucket(ra[i], i, bucketSizeX, bucketSizeY);
            sortedRects[rib.bucketXTo - rib.bucketXFrom].Add(rib);// basically radix sort
        }

        foreach (List<RectangleInBucket> sorted in sortedRects) // start with most narrow rectangles
        {
            foreach (RectangleInBucket rib in sorted) // all of one width (measured in buckets)
            {
                for (int x = rib.bucketXFrom; x <= rib.bucketXTo; ++x)
                {
                    for (int y = rib.bucketYFrom; y <= rib.bucketYTo; ++y)
                    {
                        processBucket(tmpResult, buckets[x, y], rib);
                    }
                }

                for (int y = rib.bucketYFrom; y <= rib.bucketYTo; ++y)
                {
                    buckets[rib.bucketXFrom, y].Add(rib); // left edge of rectangle
                    if (rib.bucketXFrom != rib.bucketXTo)
                    {
                        buckets[rib.bucketXTo, y].Add(rib); // right edge of rectangle
                    }
                }
            }
        }

        List<Tuple<Rectangle, Rectangle, int>> result = new List<Tuple<Rectangle, Rectangle, int>>(tmpResult.Count);
        foreach (var t in tmpResult) // transform dictionary into final list
        {
            if (t.Value != 0)
            {
                result.Add(Tuple.Create(ra[(int)t.Key], ra[(int)(t.Key >> 32)], t.Value));
            }
        }

        return result;
    }

【讨论】:

  • 哇,这太酷了。 compare实际上并不是微不足道的,所以我一直在寻找最小化comparecalls 的数量。
猜你喜欢
  • 1970-01-01
  • 2013-01-05
  • 1970-01-01
  • 2014-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多