【问题标题】:Efficient algorithm to find connected components查找连通分量的有效算法
【发布时间】:2019-01-21 07:56:01
【问题描述】:

现在我有一个 4 列和无限行的网格。每个单元格都可能被一个 Square 占据,而 Square 存储在 ArrayList<Square> squares 中。

我希望能够找到所有(通过边/角)连接到选定 Square 的 Square,例如:

我使用了一个递归函数来检查选定 Square 周围的正方形,然后对这些正方形执行相同的操作,但这会导致某些正方形被检查两次并且似乎效率低下。

现在我正在使用类而不是函数来执行此操作,并跟踪到目前为止已在 Set 中检查过哪些内容,但为了简单起见,我希望将其保留在函数中。

我可以采取哪些步骤来实现高效的算法?

更新:Square 存储在 ArrayList 中,而不是 2D 数据结构,因为我需要它们可以在程序的其他地方轻松访问。当我需要找到相邻的方块时,我会测试方块之间的碰撞。

【问题讨论】:

  • 请参阅meta.stackoverflow.com/questions/284236/… ...我们会帮助解决具体问题。在某种程度上,你的要求就像:“有人和我坐下来解释一下如何解决这个问题”。这不是这个社区的目的。
  • 你需要一个良好的 8 连接洪水填充算法实现
  • 请描述方块是如何存储的。坐标列表或二维数组?
  • 它们存储在坐标的 ArrayList 中。当我想找到相邻的方块时,我会执行碰撞检测以查看附近是否有任何方块。
  • squares是按Y坐标排序的吗?

标签: java algorithm graph-algorithm


【解决方案1】:

短版

我认为深度优先搜索算法可能会对您有所帮助。

在您的情况下,每个图块都可以视为图形的一个节点,如果两个节点共享一个边或一个角,则它们之间存在一条边。

我在这里找到了一个很好的视频来解释该算法的工作原理:Depth First Search on youtube

DFS 算法可能与您尝试使用递归方法的算法非常相似,但主要区别在于您在算法的进展过程中为您访问的节点/图块“着色”。与其将探索的节点保存在单独的数据结构中,我建议您将其作为每个图块的属性。

然后,如果您偶然发现了已访问过的图块,则不会对其进行探索。如果您已经探索了当前节点的所有邻居,您将返回到您之前正在探索的节点并(递归地)探索其邻居,直到您回溯到您开始算法的节点。

与您的具体问题相关的更多细节

检测邻居

您提到您的正方形存储在 ArrayList 中。这可以。但是,如果没有正方形或包含对位于该位置的正方形实例的引用,它不会阻止您构建其单元格为 null 的二维正方形数组。以我的拙见,这将使寻找邻居比寻找每对正方形之间的碰撞要容易得多(我认为这就是您现在正在做的事情)。

您不必为程序中的任何其他内容使用这样的二维数组。我很有信心它会使大量方块的速度更快。

当然,还有其他数据结构可以很容易地查找图节点之间的交集。例如,您可以构建一个 adjacency matrix 一次并将其用于任何后续计算,但您不必这样做。

使用您的示例运行 DFS

我将使用堆栈来跟踪我在瓷砖探索中的位置。我将通过它们的坐标来引用类型。我们开始算法的单元格在您的图中以红色着色,并具有坐标 (1,2)。

算法如下:

while (!stack.isEmpty()) {
  currentTyle = stack.top();
  boolean currentHasNeighborsToExplore = false;
  for (n in neighbors of currentTyle) {
    if (n is not explored) {
      n is explored;
      stack.add(n);
      currentHasNeighborsToExplore = true;
      break;
    }
  }
  if (!currentHasNeighborsToExplore) {
    stack.pop();
  }
}

我们从您的初始类型 (1,2) 开始算法。

第 1 步

堆栈:[(1,2)

栈顶是(1,2)

(1,2) 的邻居 n: (2,2) 尚未探索

(2,2) 现在已经探索过了,我们将它添加到堆栈中并进行下一步

第 2 步

堆栈:[(1,2) (2,2)

栈顶是(2,2)

(2,2) 有邻居 n: (1,2) 被探索

(2,2) 的邻居 n: (3,1) 尚未探索

(3,1) 现已探索,我们将其添加到堆栈并进行下一步

第 3 步

堆栈:[(1,2) (2,2) (3,1)

栈顶是(3,1)

(3,1) 有邻居 n: (2,2) 被探索

(3,1) 的邻居 n: (4,2) 尚未探索

(4,2) 现在已经探索过了,我们将它添加到堆栈中并进行下一步

第 4 步

堆栈:[(1,2) (2,2) (3,1) (4,2)

栈顶是 (4,2)

(4,2) 的邻居 n: (4,3) 尚未探索

(4,3) 现已探索,我们将其添加到堆栈并进行下一步

第 5 步

堆栈:[(1,2) (2,2) (3,1) (4,2) (4,3)

栈顶是 (4,3)

(4,3) 有邻居 n: (4,2) 被探索

(4,3) 没有未探索的邻居,我们将其从堆栈中弹出并进行下一步

第 6 步

堆栈:[(1,2) (2,2) (3,1) (4,2)

栈顶是(2,2)

(4,2) 有邻居 n: (4,3) 正在探索

(4,2) 的邻居 n: (5,1) 尚未探索

(5,1) 现在已经探索过了,我们将它添加到堆栈中并进行下一步

后续步骤

在下一步中,(5,1) 没有未探索的邻居,它将像所有后续类型一样从堆栈中弹出,因为没有未探索的邻居。

【讨论】:

  • Breath-First-Search 也可以
  • 在查看了 DFS 算法之后,似乎大多数示例都使用了树。只是为了确保我走在正确的道路上,您将如何在基于网格/平铺的系统中最好地实现它?
  • 我编辑了我的答案,试图更好地回答你的问题。
【解决方案2】:

我同意 cmets 的观点,即您应该在问题中具体化。但是,既然您问“某些方格被检查了两次”,我可以回答那部分。您可以维护矩阵来跟踪已经访问过的单元格。最初,所有单元格都可以设置为 0。处理给定的单元格后,您可以将其设置为 1。每次处理任何单元格时,只需使用访问矩阵检查它是否已被访问过。

 int visited[][] = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } };
 // you logic to process cell
if (visited[x][y] ==0) // check is not visited
 visited[x][y] = 1; // mark cell as visited
else 
 //skip

【讨论】:

    【解决方案3】:

    基本上我认为没有理由实施任何复杂的算法,因为网格中的邻居很容易计算

     +-------+-------+-------+
     |x-1,y+1|  x,y+1|x+1,y+1|
     +-------+-------+-------+
     |x-1,  y|  x,  y|x+1,  y|
     +-------+-------+-------+
     |x-1,y-1|  x,y-1|x+1,y-1|
     +-------+-------+-------+
    

    您可以将squares 保存在List<List<Square>> 之类的位置并通过索引访问它们

    但是,如果您想将它们保持在简单的 List<> 中,您仍然可以通过计算 n-th 元素的 n 索引为

    +----->
    | [(0,0) - 0  ]   [(1,0) - 1st]   [(2,0) - 2nd]   [(3,0) - 3th]
    | [(0,1) - 4th]   [(1,1) - 5th]   [(2,1) - 6th]   [(3,1) - 7th]
    v [(0,2) - 8th]   [(1,2) - 9th]   [(2,2) -10th]   [(3,2) -11th]
    
    // index for (2,1)
    index_of_2_1 = (y * rows_count) + x = (1*4) + 2 = 6
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-06
      相关资源
      最近更新 更多