【问题标题】:Algorithm to find the total number of connected sets in a matrix查找矩阵中连接集总数的算法
【发布时间】:2012-06-28 21:16:02
【问题描述】:

我想知道我应该在这里应用哪种算法。 DFS 会这样做吗?

给定一个二维矩阵。求该矩阵中连接集的总数。

连接集可以定义为一组单元格,上面提到了 1 个单元格,并且在该集合中至少有一个其他单元格与之共享邻居关系。一个包含 1 的单元格并且没有包含 1 的周围邻居可以被认为是一个包含一个单元格的集合。邻居可以定义为在 8 个可能方向(即 N、W、E、S、NE、NW、SE、SW 方向)上与给定单元相邻的所有单元。一个细胞不是它自己的邻居。

例如:

1 0 0 1

0 0 1 0

0 0 1 0

1 0 0 1

连接集数为 3

0 0 1 0 0 1 0 0

1 0 0 0 0 0 0 1

0 0 1 0 0 1 0 1

0 1 0 0 0 1 0 0

1 0 0 0 0 0 0 0

0 0 1 1 0 1 1 0

1 0 1 1 0 1 1 0

0 0 0 0 0 0 0 0

连接集数为 9。

【问题讨论】:

    标签: algorithm graph graph-theory


    【解决方案1】:

    我认为您不需要将其视为一般图问题并应用任何算法,例如 BFSDFS

    您需要对矩阵进行三次扫描。

    扫描 1:

    从顶部开始

    1. 给每个数字每个 1 和 1..n,在您的示例中,该步骤之后的第一行看起来像

      1 0 0 2

    2. 转到下一行,对于行中的每个 1,检查您左边的邻居是否为非 0
      • 如果非 0 取左边的值
      • if 0 检查上一行中的非 0 邻居并取最左边的值
      • 如果所有这些都是 0,则只需将 1 加到目前给出的最大数字上
    3. 重复 2 直到处理完最后一行

    您的示例应如下所示

    1 0 0 2
    0 0 2 0
    0 0 2 0
    3 0 0 2
    

    扫描 2:

    从底部开始 检查每个邻居是否与最左边的邻居具有相同的编号以及与其下一行中的邻居相同的编号

    基本上如果你有这样的矩阵

    1 0 2

    1 0 2

    0 1 0

    检查确保一个集合具有相同的数字

    扫描 3:

    计算矩阵中唯一非 0 条目的数量

    【讨论】:

    • 谁能解释一下Scan 1的第二步
    【解决方案2】:

    Connected-component labeling 算法旨在标记连接的元素组(用于 4 连接和 8 连接)

    【讨论】:

    • 与其他答案相比,我发现这很容易阅读和实施。谢谢!
    【解决方案3】:

    Pythonic 实现,更易理解的代码:

    # sea is 2 D array of 0 and 1s we have to find 1's group surrounded by 0's
    def dfs(sea, i, j, b, h, visited):
        surround = ((-1, -1), (0, -1), (1, -1),
                    (-1, 0), (1, 0),
                    (-1, 1), (0, 1), (1, 1)
                    )
        if can_visit(sea, i, j, b, h, visited):
            for s in surround:
                visited[(i, j)] = 1
                dfs(sea, i + s[0], j + s[1], b, h, visited)
    
    
    def can_visit(sea, i, j, b, h, visited):
        if i >= 0 and j >= 0 and i < b and j < h:
            if (i, j) not in visited and sea[i][j] == 1:
                return True
    
    
    def find_island(sea):
        visited = {}
        h = len(sea)
        count = 0
        for i, row in enumerate(sea):
            b = len(row)
            for j, item in enumerate(row):
                if can_visit(sea, i, j, b, h, visited):
                    count += 1
                    dfs(sea, i, j, b, h, visited)
        return count
    
    
    sea = [[1, 1, 0, 0, 0],
           [0, 1, 0, 0, 1],
           [1, 0, 0, 1, 1],
           [0, 0, 0, 0, 0],
           [1, 0, 1, 0, 1]
           ]
    
    print find_island(sea)
    

    【讨论】:

    • 我认为这是最好的答案!
    【解决方案4】:

    您想使用disjoint set 数据结构和算法。这将为每个连接的组件选择一个唯一的代表,您可以在最后计算。

    为了有效地评估哪些元素是邻居,您可以逐行扫描矩阵,维护前一行的段列表(连续的1),同时确定当前行上的哪些段与他们。

    【讨论】:

    • 我看不出您将如何将代表元素分配给特定集合,这样集合中的所有其他元素都将成为该元素的后代。
    • 经典不相交集操作的简要描述:每个不相交集都表示为一棵倒置树,只有parent 链接。要查找集合的代表元素,请按照parent 链接查找树的根。要连接两棵树,请找到每棵树的根,并将其中一棵树作为另一棵树的父级。
    • 我知道什么是不相交集合,不相交集合中最重要的方法之一是Find 方法(见wiki),其实我问你将如何实现Find 方法? (通过这个问题陈述。)。
    • 树的根是代表元素。维基百科链接描述了这一点,以及实现摊销 O(inverseAckermann(N)) 性能所需的优化。
    【解决方案5】:

    有 3 个连接的集合。彼此相邻的所有 1 都被视为一个集合。 a[1,4]a[2,3]a[3,3]a[4,4] 的所有 1 个构成一组,a[1,1] 的一个构成一组,a[4,1] 的一个构成一组。

    【讨论】:

      【解决方案6】:

      扫描矩阵1s。当你找到一个时,调用一个递归函数来标记它的连接组件,如果它还没有被识别为在一个中。使用递归来查找连接的组件。在某处快速查找,告诉您给定节点是否已被识别为连接组件,以避免识别连接组件 2x,并避免在遍历连接组件时出现无限循环。

      【讨论】:

        【解决方案7】:

        如果您只想通过矩阵进行操作(没有额外的内存),请按以下方式进行:

        将扫描仪位置设置为 [0,0]

        1. 将计数器设置为零。
        2. 从当前扫描仪位置逐行(和逐个单元格)扫描矩阵并找到一个1,并将扫描仪位置设置到此1之后的下一个元素,如果没有1,则转到步骤6 .
        3. 将相关的一个设置为counter+2 并递归查找其所有1 邻居并将它们设置为count + 2
        4. count = count + 1
        5. 转到第 2 步。
        6. 输出count

        PS:如果扫描仪位置大于您的算法将完成的矩阵大小,这很清楚(我没有写这个以防止混淆)。

        【讨论】:

          【解决方案8】:

          这并不像看起来那么难。事实上,这感觉非常像教授在第一年计算机科学中分配的任务。所以如果这是家庭作业,你应该这样标记它。

          但是,解决方案相当简单。

          for (int y = 0; y < arr.height(); y++)
          {
             for (int x = 0; x < arr.width(); x++)
             {
                if (arr[x][y] == 1)
                {
                   if (CheckIfConnected(x, y, arr))
                   {
                      connectedPositionsX.Add(x);
                      connectedPositionsY.Add(y);
                   }
                }
             }
          }
          

          connectedPositions 是一个链表或任何你想用来存储集合的地方。

          arr 是一个二维数组,其中包含您在上面指定的类型的矩阵。

          CheckIfConnected 也可以相当简单地实现。

          bool CheckIfConnected(int x, int y, int[][]arr)
              {
                 if (arr.width() >= 2) || (arr.height() >= 2)
                 {
                    if ((x < arr.width()) && (x >= 0) && (y < arr.height()) && (y >= 0))
                    {
                      if ((x-1) >= 0) //West
                      {
                          if (arr[x-1][y] == 1)
                          {
                              adjCount[x-1][y] += 1;
                              return true;
                          }
                      }
                      if (((x-1) >= 0) && ((y-1) >= 0)) //Northwest
                      {
                          if (arr[x-1][y-1] == 1)
                          {
                              adjCount[x-1][y-1] += 1;
                              return true;
                          }
                      }
                      if ((y-1) >= 0) //North
                      {
                          if (arr[x][y-1] == 1)
                          {
                              adjCount[x][y-1] += 1;
                              return true;
                          }
                      }
                      if (((x+1) < arr.width()) && ((y-1) >= 0)) //Northeast
                      {
                          if (arr[x+1][y-1] == 1)
                          {
                              adjCount[x+1][y-1] += 1;
                              return true;
                          }
                      }
                      if ((x+1) < arr.width()) //East
                      {
                          if (arr[x+1][y] == 1)
                          {
                              adjCount[x+1][y] += 1;
                              return true;
                          }
                      }
          
                      //I'll let you implement Southeast to Southwest on your own,
                      //the pattern is clear now.
                    }
                 }
                 return false;
              }
          

          从那里,您知道您在网格中的每个位置上找到了多少次配对。这有助于您跟踪您的连接。

          二维数组adjCount 中的计数会为您记录这一点。

          您还可以查看并修改 Dijkstra 算法,以递归方式为您执行此操作。既然您提到了 DFS(深度优先搜索),我假设您的教授或老师希望您以这种方式解决它。

          在这种情况下:

          这是伪代码中的 Dijkstra 算法: http://en.wikipedia.org/wiki/Dijkstra's_algorithm

          希望对您有所帮助!干杯!

          【讨论】:

            【解决方案9】:

            对于每个值为 1 的节点,只需递归地在东、东南、南和西南方向一次搜索。 如果访问函数的调用是新调用而不是来自递归,则增加连接的组件。

            import java.util.Scanner;
            
            public class Solution {
            
                public static void visit(int[][] ar, boolean[][] v,int i, int j){
                    int size = ar.length;
                    if(ar[i][j] == 1){
                        v[i][j] = true;
                        if(j>0 && i<size-1){
                            visit(ar,v,i+1,j-1); // SouthWest
                        }
                        if(i<size-1){
                            visit(ar,v,i+1,j); // South
                            if(j < size-1)
                                visit(ar,v,i+1,j+1); // SouthEast
                        }
                        if(j<size-1)
                            visit(ar,v,i,j+1); // East
                    }
                }
            
                public static void main(String[] args) {
                    int[][] ar;
                    int count = 0;
                    Scanner sc = new Scanner(System.in);
                    int n = sc.nextInt();
                    ar = new int[n][n];
                    boolean[][] v = new boolean[n][n];
                    for(int i=0; i<n ; i++) {
                        for(int j=0; j<n; j++){
                            ar[i][j] = sc.nextInt();
                            v[i][j] = false;
                        }
                    }
            
                    for(int i=0; i<n ; i++) {
                        for(int j=0; j<n; j++){                
                            if(ar[i][j] == 1 && !v[i][j]){
                                count++;
                                visit(ar,v,i,j);
                            }
                        }
                    }
                    System.out.println(count);
                }
            }
            

            【讨论】:

              【解决方案10】:

              我有一个类可以帮助您找到二维数组中连接组件的总数。我的课不仅给你总数,还给你集群并为你可视化它们。您可以注释掉不需要的部分。请在 (java) 中查看此类:https://github.com/m-vahidalizadeh/foundations/blob/master/src/algorithms/ConnectedComponetns.java

              【讨论】:

                【解决方案11】:

                如果你使用python,scipy中有一个函数可以找到这个数字。

                from scipy.ndimage import label
                
                data = [[0, 0, 1, 0, 0, 1, 0, 0],
                        [1, 0, 0, 0, 0, 0, 0, 1],
                        [0, 0, 1, 0, 0, 1, 0, 1],
                        [0, 1, 0, 0, 0, 1, 0, 0],
                        [1, 0, 0, 0, 0, 0, 0, 0],
                        [0, 0, 1, 1, 0, 1, 1, 0],
                        [1, 0, 1, 1, 0, 1, 1, 0],
                        [0, 0, 0, 0, 0, 0, 0, 0]]
                
                connection_structure = [[1,1,1],
                                        [1,0,1],
                                        [1,1,1]]
                
                _, N = label(data, connection_structure)
                    
                print(N) 
                >>> 9
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-10-01
                  • 2011-12-28
                  • 2021-06-05
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-01-20
                  相关资源
                  最近更新 更多