一些观察:
- 按最小线的大小划分屏幕会使算法非常不可预测,甚至没有必要。您可以使用任何大小的存储桶,并且该算法可以正常工作。
- 检查
bucket[i-1]到bucket[j+1]是没有必要的,bucket[i]到bucket[j]就足够了
- 设 A 和 B 为矩形,B 不比 A 宽。则 B 的左边缘或右边缘的一部分位于 A 上或这些矩形不重叠(稍后会用到)。
所以我做的算法:
- 为每个矩形计算它所属的桶的范围
(
bucketXFrom, bucketXTo, bucketYFrom, bucketYTo)。这是课
RectangleInBucket。
- 按 (
bucketXTo - bucketXFrom) 对它们进行排序。由于没有那么多桶,它基本上是一步基数排序。
- 对于每个矩形,从宽度最小的开始,扫描它所属的所有桶。如果有矩形,比较它们并保存存在的关系。将矩形保存到左右边缘下方的存储桶中。
我使桶的总数等于矩形的数量,它似乎效果最好。
它通常比朴素算法快,但没有人们想象的那么快。由于矩形可以(并且确实)属于许多桶,因此需要多次重新检查一个关系。这增加了步数。此外,它使用不那么便宜的结构来进行重复数据删除。但是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;
}