考虑一个按角落定义区域的班级Rectangle
:
public class Rectangle {
public int X1 { get; set; }
public int Y1 { get; set; }
public int X2 { get; set; }
public int Y2 { get; set; }
}
可以说两个Rectangle
个对象Overlap
如果它们有任何共同的区域。这是一个实现Overlap
逻辑的简单方法:
public bool Overlaps(Rectangle other) {
return (this.X1 < other.X2 && this.X2 > other.X1 &&
this.Y1 < other.Y2 && this.Y2 > other.Y1);
}
现在我想将一组Rectangle
个对象划分为重叠矩形组。问题在于,组中的某些矩形可能不一定与同一组中的其他矩形重叠,只要它们共享其他重叠矩形即可。结果始终定义明确,但没有从矩形到最终重叠组的直接映射。
似乎直觉上应该可以使用GroupBy
来构建重叠矩形组。但是,没有“关键”来定义矩形是否属于同一组;重要的是它们是否重叠。使用GroupBy
可以解决这个问题,即使它意味着递归分组直到所有正确的组合起来吗?
答案 0 :(得分:2)
不,GroupBy
期望一个属性可以通过查看一个实例和一个实例来确定。
但是,有一个相对简单的解决方案:您可以使用Disjoint-Set Data Structure(这不仅仅是一个荣耀的链表)及其相关的union
算法。整个算法可以在几十行中编码,并且相对易于理解和调试。
给你的矩形序号,并在每对矩形上运行你的交集算法。检测到重叠时,请在相应的不相交集结构上执行set union。完成后,每个成员将指向其集合的“根”号。您可以使用这些根编号按LINQ中的列表进行分组。
答案 1 :(得分:0)
以防有人寻找实际的实现。它不是用于矩形,而是用于时间范围。 DisjointSet实现取自 http://www.mathblog.dk/disjoint-set-data-structure/
public class Range {
public DateTime Start { get; set; }
public DateTime Stop { get; set; }
public int Parent { get; set; }
public Range(DateTime start, DateTime stop)
{
Start = start;
Stop = stop;
}
}
void Main()
{
List<Range> ranges = new List<Range>();
ranges.Add(new Range(new DateTime(2019, 10, 1), new DateTime(2019, 10, 2)));
ranges.Add(new Range(new DateTime(2019, 10, 2), new DateTime(2019, 10, 3)));
ranges.Add(new Range(new DateTime(2019, 10, 1), new DateTime(2019, 10, 3)));
ranges.Add(new Range(new DateTime(2019, 10, 4), new DateTime(2019, 10, 5)));
ranges.Add(new Range(new DateTime(2019, 10, 6), new DateTime(2019, 10, 8)));
ranges.Add(new Range(new DateTime(2019, 10, 5), new DateTime(2019, 10, 7)));
ranges.Add(new Range(new DateTime(2019, 10, 9), new DateTime(2019, 10, 9)));
var set = new DisjointSet(ranges.Count());
var IsOverlaping = new Func<Range, Range, bool>((a, b) => a.Start < b.Stop && b.Start < a.Stop );
for (var i = 0; i < ranges.Count; i++)
{
for (var j = 0; j < ranges.Count; j++)
{
if (IsOverlaping(ranges[i], ranges[j]))
{
set.Union(i,j);
}
}
}
for (int i = 0; i < set.Count; i++)
{
ranges[i].Parent = set.Parent[i];
}
ranges.GroupBy(x=> x.Parent);
}
/// <summary>
/// A Union-Find/Disjoint-Set data structure.
/// </summary>
public class DisjointSet {
/// <summary>
/// The number of elements in the universe.
/// </summary>
public int Count { get; private set; }
/// <summary>
/// The parent of each element in the universe.
/// </summary>
public int[] Parent;
/// <summary>
/// The rank of each element in the universe.
/// </summary>
private int[] Rank;
/// <summary>
/// The size of each set.
/// </summary>
private int[] SizeOfSet;
/// <summary>
/// The number of disjoint sets.
/// </summary>
public int SetCount { get; private set; }
/// <summary>
/// Initializes a new Disjoint-Set data structure, with the specified amount of elements in the universe.
/// </summary>
/// <param name='count'>
/// The number of elements in the universe.
/// </param>
public DisjointSet(int count) {
this.Count = count;
this.SetCount = count;
this.Parent = new int[this.Count];
this.Rank = new int[this.Count];
this.SizeOfSet = new int[this.Count];
for (int i = 0; i < this.Count; i++) {
this.Parent[i] = i;
this.Rank[i] = 0;
this.SizeOfSet[i] = 1;
}
}
/// <summary>
/// Find the parent of the specified element.
/// </summary>
/// <param name='i'>
/// The specified element.
/// </param>
/// <remarks>
/// All elements with the same parent are in the same set.
/// </remarks>
public int Find(int i) {
if (this.Parent[i] == i) {
return i;
} else {
// Recursively find the real parent of i, and then cache it for later lookups.
this.Parent[i] = this.Find(this.Parent[i]);
return this.Parent[i];
}
}
/// <summary>
/// Unite the sets that the specified elements belong to.
/// </summary>
/// <param name='i'>
/// The first element.
/// </param>
/// <param name='j'>
/// The second element.
/// </param>
public void Union(int i, int j)
{
// Find the representatives (or the root nodes) for the set that includes i
int irep = this.Find(i),
// And do the same for the set that includes j
jrep = this.Find(j),
// Get the rank of i's tree
irank = this.Rank[irep],
// Get the rank of j's tree
jrank = this.Rank[jrep];
// Elements are in the same set, no need to unite anything.
if (irep == jrep)
return;
this.SetCount--;
// If i's rank is less than j's rank
if (irank < jrank)
{
// Then move i under j
this.Parent[irep] = jrep;
this.SizeOfSet[jrep] += this.SizeOfSet[irep];
} // Else if j's rank is less than i's rank
else if (jrank < irank)
{
// Then move j under i
this.Parent[jrep] = irep;
this.SizeOfSet[irep] += this.SizeOfSet[jrep];
} // Else if their ranks are the same
else
{
// Then move i under j (doesn't matter which one goes where)
this.Parent[irep] = jrep;
this.SizeOfSet[jrep] += this.SizeOfSet[irep];
// And increment the the result tree's rank by 1
this.Rank[jrep]++;
}
}
/// <summary>
/// Return the element count of the set that the specified elements belong to.
/// </summary>
/// <param name='i'>
/// The element.
/// </param>
public int SetSize(int i)
{
return this.SizeOfSet[this.Find(i)];
}
}
结果: