有一个2d项目数组(在我的例子中,它们被称为Intersections
)。
某个项目作为开始。任务是找到直接或间接连接到此项目以满足特定功能的所有项目。
所以基本算法是这样的:
将开头添加到结果列表中。 重复直到没有修改:添加满足该功能的数组中的每个项目,并将结果列表中的任何项目触摸到结果列表。
我目前的实现如下:
private IList<Intersection> SelectGroup (
Intersection start,
Func<Intersection, Intersection, bool> select)
{
List<Intersection> result = new List<Intersection> ();
Queue<Intersection> source = new Queue<Intersection> ();
source.Enqueue (start);
while (source.Any ()) {
var s = source.Dequeue ();
result.Add (s);
foreach (var neighbour in Neighbours (s)) {
if (select (start, neighbour)
&& !result.Contains (neighbour)
&& !source.Contains (neighbour)) {
source.Enqueue (neighbour);
}
}
}
Debug.Assert (result.Distinct ().Count () == result.Count ());
Debug.Assert (result.All (x => select (x, result.First ())));
return result;
}
private List<Intersection> Neighbours (IIntersection intersection)
{
int x = intersection.X;
int y = intersection.Y;
List<Intersection> list = new List<Intersection> ();
if (x > 1) {
list.Add (GetIntersection (x - 1, y));
}
if (y > 1) {
list.Add (GetIntersection (x, y - 1));
}
if (x < Size) {
list.Add (GetIntersection (x + 1, y));
}
if (y < Size) {
list.Add (GetIntersection (x, y + 1));
}
return list;
}
(select
函数接受一个开始项,如果第二个项满足则返回true。)
这完成了它的工作并且对于通常的阵列大小(大约20 * 20)来说是合理的。但是,我对进一步改进感兴趣。有什么想法吗?
示例(X
满足其他X
s,.
永远不会满足:
.... XX.. .XX. X...
在这种情况下,有2组:4个项目的中央组和左下角的单个项目组。选择组(例如通过开始项[3,3])返回前者,而后者可以使用起始项和唯一返回值[1,4]来选择。
示例2:
.A.. ..BB A.AA
这次有4组。 3 A组未连接,因此它们作为单独的组返回。较大的A和B组是连接的,但A与B无关,因此它们作为单独的组返回。
答案 0 :(得分:1)
第1步:为获取巨额利益而进行微不足道的改变
简单,即时的改进:您的result.Contains
和source.Contains
成员资格都在列表类型上,因此它们将是这些列表大小的O(n),效率不高。由于您真的不关心任何特定的排序,我会将这两个排序更改为HashSet
以进行恒定时间查找。
请注意,在最坏的情况下,您当前的实现将是O(n ^ 2),这在整个数组有效时发生(当您插入最后几个元素时,您将检查每个元素与其余元素的网格)。
第2步:进一步优化
更好的结构更改:保留与Intersection数组大小相同的布尔visited
数组,每次查看Intersection
时,将其标记为已访问。这样,您不必每次都检查result
或source
中的某些内容,更好的是,您不必重新评估select
谓词。否则,给出这样的东西:
XXX
X.X
XXX
你要四次评估中心点上的select
,如果价格昂贵,这可能会很糟糕。这样,你的
if (select (start, neighbour)
&& !result.Contains (neighbour)
&& !source.Contains (neighbour))
条件变为:if (!visited(neighbour) && select(start, neighbour)
,在任何给定的交叉点上最多只评估一次select
。
此外,如果你这样做,你甚至不需要再进行result
和contains
哈希,因为你不会对它们进行包容性检查。
答案 1 :(得分:0)
我不是很擅长C#而是阅读你的算法描述,我可以给你一些建议:
1 /你知道动态编程还是贝尔曼算法? 我们的想法是,对于每次迭代,您只能获得最新结果并继续搜索。 Dynamic Programming
例如:
第1次迭代:x1,x2
第二次迭代:x3,x4
第三次迭代,您只搜索并与x3和x4进行比较。它将为您节省大量的CPU计算和迭代次数
2 /使用HashSet更好地搜索并获取包含
的数据答案 2 :(得分:0)
根据tzaman的回答,我使用了HashSet。由于队列不支持散列,我发现了一项改进,只需要在结果列表中查找而不是结果和源列表:
HashSet<Intersection> result = new HashSet<Intersection> ();
result.Add (start); // need to add first item in advance
Queue<Intersection> source = new Queue<Intersection> ();
source.Enqueue (start);
while (source.Any ()) {
var s = source.Dequeue ();
foreach (var neighbour in Neighbours (s)) {
if (select (start, neighbour) && !result.Contains (neighbour)) {
result.Add (neighbour); // add immediately to hashset
source.Enqueue (neighbour);
}
}
}