Algorithm 渐进连接元件标记

Algorithm 渐进连接元件标记,algorithm,language-agnostic,path-finding,Algorithm,Language Agnostic,Path Finding,我正在使用一个正方形网格,它有两种状态,“开”和“关”。我有一个相当简单的算法,可以找到所有的“开”组件。通常,但并非总是,只有一个“开”组件 我希望构造一个算法,将开/关单元矩阵、组件标签(可能格式化为单元哈希集列表)和自标签形成以来已更改的单元列表作为输入,并输出新标签。显而易见的解决方案是从头开始重新计算,尽管这并不高效。通常,已更改的单元格列表将很小 如果更改列表只是已打开的单元格,则很容易做到: Groups G; Foreach changed cell C: Group U =

我正在使用一个正方形网格,它有两种状态,“开”和“关”。我有一个相当简单的算法,可以找到所有的“开”组件。通常,但并非总是,只有一个“开”组件

我希望构造一个算法,将开/关单元矩阵、组件标签(可能格式化为单元哈希集列表)和自标签形成以来已更改的单元列表作为输入,并输出新标签。显而易见的解决方案是从头开始重新计算,尽管这并不高效。通常,已更改的单元格列表将很小

如果更改列表只是已打开的单元格,则很容易做到:

Groups G;
Foreach changed cell C:
  Group U = emptygroup;
  U.add(C);
  Foreach Group S in G:
    if (S contains a cell which is adjacent to C)
      G.Remove(S);
      U.UnionWith(S);
  G.add(C);
但是,如果更改包含任何已关闭的单元格,我不确定该怎么办。请记住,所有ON单元必须是一个组的成员。因此,一种解决方案是将与新脱离单元相邻的每个单元取下来,看看它们是否彼此连接(例如,使用*寻路)。这将产生1-4个连续组(除非该单元是其组中唯一的单元,因此有0个相邻单元要检查,在这种情况下,它产生0个组)。然而,这只比从头开始要好一点,因为通常(但并非总是)将这些相邻的正方形连接在一起就像找到一个相邻的组一样困难(除非有人建议一种聪明的方法)。此外,如果有很多细胞发生了变化,这也有点可怕……尽管我承认通常没有

对于那些坚持知道我为什么要这样做的人: 谜题中的一条规则是,你只能有一组连续的墙。上面是我试图解决的一个问题的简化,以提高速度(并进行寻路)。基本上,我希望检查连续墙,而不浪费从以前的此类测试中获得的信息。我试图看看我的解算器中有多少地方可以利用以前的信息来提高速度,因为当一个O(f(Δ))算法足够时,使用O(f(N))算法似乎有点痛苦(N是拼图的大小,Δ是自上次运行算法以来所做的更改)

分析确实表明,改进此算法将对执行时间产生影响,但这是一个有趣的项目,而不是为了盈利,因此它实际上并不重要,只是能够测量更改是否有任何影响

注: 我省略了解释我当前的算法,但它基本上是根据它找到的第一个on平方做一个基于堆栈的算法,然后检查是否有更多on平方(这意味着有多个组,它不需要检查)

编辑:增强想法:Yairchu和John Kugelman的建议在我的脑海中结晶为这一改进,这实际上并不是这个问题本身的解决方案,但可能会使这部分代码和其他几段代码运行得更快:

电流回路:

foreach (Square s in m.Neighbors[tmp.X][tmp.Y])    
{
    if (0 != ((byte)(s.RoomType) & match) && Retval.Add(s)) curStack.Push(s);
}
改进思路:

foreach (Square s in m.NeighborsXX[tmp.X][tmp.Y])    
{
    if (Retval.Add(s)) curStack.Push(s);
}

这将需要维护几个m.NeighborsXX实例(每种需要增强的匹配类型一个实例),并在正方形更改时更新它们。我需要对此进行基准测试,看看它是否真的有帮助,但它看起来像是用一些内存换取一些速度的标准案例。

有趣的问题!这是我最初的想法。希望我会有更多,并会更新这个答案,因为他们来了

[Update 2]因为您只关心一个组,所以A*搜索似乎很理想。你有没有分析过A*搜索和重新标记?我不得不认为一个写得很好的搜索会比洪水泛滥更快。如果没有,也许您可以发布您的实际代码以获得优化帮助

[Update 1]如果您知道新脱离的单元格
C
在组
G
中,则可以重新运行CCL算法,但只需重新标记组
G
中的单元格即可。单元格上的另一个可以保留其现有标签。您不必检查电网的其余部分,与整个电网的初始CCL相比,这可能会大大节省成本。(作为一个狂热的Nurikabe解算者,这应该是一个已经解决的谜题至少节省33%,而对于正在进行的谜题,这应该是一个非常显著的节省,不是吗?“33%”来自我的猜测,已经解决的谜题大约有2/3黑色和1/3白色。)


要做到这一点,您必须存储每个组中包含的单元格列表,以便您可以快速迭代组
G
中的单元格,并仅重新标记这些单元格。

这不是一个完整的解决方案,但如下所示:

  • 对于每个连接的组件,在内存中保留一个生成树
    • 树属性A:我们的生成树有一个概念,即哪个节点在哪个节点之上(就像在搜索树中一样)。选择哪个在哪个之上是任意的
  • 让我们讨论删除和添加边
  • 添加边时:
    • 通过检查两个节点的树的根是否相同,检查它们是否位于同一组件中
      • 树属性B:树应该是稠密的,因此此检查将为O(日志n)
    • 如果在同一组,则什么也不做
    • 如果它们在不同的组中,则使用新边连接树。
      • 这将需要转换其中一棵树的“形状”(谁在谁之上的定义),以便我们的新边可以“在”它之上
  • 删除边时:
    • 如果此边不参与组的生成树,则不执行任何操作
    • 如果有,我们需要检查该组是否仍然连接
      • 来自一个组的DFS尝试联系另一个组
      • 最好从两者中较小的一个开始
        • 树属性C:我们为树中的每个节点维护其子树的大小
        • 使用属性C,我们可以判断两组的si