如何在二维数组中查找和存储值组?

时间:2018-10-19 02:52:52

标签: arrays algorithm grouping

我正在使用一个二维数组,该数组在每个点处都包含值,我需要找到一种方法来计算和返回由与该值相邻的值组成的组数。例如:

+ - -
+ - -
+ + +

在3x3数组的ASCII版本中,将有两个值“ +”和“-”的组。我将基于邻接关系将此数组定义为具有两个不同的组。如果两个正方形不通过相邻路径连接,则它们将不在同一组中。

3组:

+ - -
- - -
+ - -

5组:

+ - +
- - -
+ - +

如何编写一种算法,可以找到组的数量,其值和位置?

感谢您的帮助!

2 个答案:

答案 0 :(得分:4)

您要寻找的算法是Flood Fill。要查找数组中从[0,0]开始的组,然后填充以查找具有相同值的所有连接的单元格,并随即将它们标记为“完成”。洪水填充终止后,它访问的单元格列表就是您的第一组。然后扫描整个阵列,直到找到未标记为已完成的单元,然后从那里开始另一个洪水填充以找到下一个组。重复直到所有单元格都标记为完成。

答案 1 :(得分:2)

我们可以通过将其建模为graph来解决此问题,如下所示:

  • 为数组中的每个单元格创建一个顶点
  • 如果两个顶点在数组中相邻且具有相同的“类型”,请在两个顶点之间添加一条边

例如,对于此数组:

+ - -     
+ - -     
+ + +

我们图的adjacency list表示将是(这里,顶点被标记为0,0之类的坐标,它对应于array[0][0]处的像元):

0,0 -> 1,0
0,1 -> 0,2  1,1
0,2 -> 0,1  1,2
1,0 -> 0,0  2,0
1,1 -> 0,1  1,2
1,2 -> 1,1  0,2
2,0 -> 1,0  2,1
2,1 -> 2,0  2,2
2,2 -> 2,1

在外观上看起来像这样(水平边缘表示为...):

+   -...-     
|   |   |
+   -...-     
|
+...+...+

请注意,我们的图形构造在较高层次上符合我们的要求(即,连接类似的“区域”)。现在我们需要计算这些区域。为此,我们可以使用depth first search

我们的策略是在任何顶点启动DFS并完成它。这将完全覆盖初始顶点所属的区域。我们将继续对尚未被任何DFS触及的所有顶点执行此操作,并计算我们需要执行这些DFS的次数。

这通常称为connect components问题。用于查找特定情况下组件数量的算法将花费 nxn 数组花费 O(n ^ 2)时间,因为我们将拥有 n ^ 2 个顶点且小于 n ^ 2 个边(DFS的时间复杂度为 O(V + E))。

这是基于上述算法的javascript工作解决方案(示例的测试在底部):

function getGroups(array) {
  const graph = buildGraph(array);
  const components = getConnectedComponents(graph);
  return components;
}

function getConnectedComponents(graph) {
  let componentId = 0,
    vertexComponent = {},
    marked = new Set();

  let dfs = function(source) {
    marked.add(source);
    vertexComponent[source] = componentId;
    for (let u of graph[source]) {
      if (!marked.has(u)) dfs(u);
    }
  }

  for (let v in graph) {
    if (!marked.has(v)) {
      dfs(v); // start dfs from an unmarked vertex
      componentId++; // dfs must have "touched" every vertex in that component, so change componentId for the next dfs
    }
  }
  return vertexComponent;
}

function buildGraph(grid) {
  // builds an adjacency list (set) graph representation from a 2d grid
  let g = {};
  grid.forEach((row, i) => {
    row.forEach((cell, j) => {
      let v = i + ',' + j; // vertex label ( e.g 0,1 )
      if (!(v in g)) g[v] = new Set();
      getNeighbors(grid, i, j).forEach(n => {
        if (!(n in g)) g[n] = new Set();
        g[v].add(n);
        g[n].add(v);
      });
    });
  });
  return g;
}

function getNeighbors(grid, i, j) {
  // returns neighboring cells of the same type as grid[i][j]
  let n = [];
  let color = grid[i][j];
  if (i - 1 >= 0 && grid[i - 1][j] === color) n.push([i - 1, j].join());
  if (j - 1 >= 0 && grid[i][j - 1] === color) n.push([i, j - 1].join());
  if (i + 1 < grid.length && grid[i + 1][j] === color) n.push([i + 1, j].join());
  if (j + 1 < grid[0].length && grid[i][j + 1] === color) n.push([i, j + 1].join());
  return n;
}

// Let's do some testing

let array = [
  [0, 1, 1],
  [0, 1, 1],
  [0, 0, 0]
];
let arrayGroups = getGroups(array);
console.log(arrayGroups);
console.log('Total groups: ', new Set(Object.values(arrayGroups)).size);

array = [
  [0, 1, 1],
  [1, 1, 1],
  [0, 1, 1]
];
arrayGroups = getGroups(array);
console.log(arrayGroups);
console.log('Total groups: ', new Set(Object.values(arrayGroups)).size);

array = [
  [0, 1, 0],
  [1, 1, 1],
  [0, 1, 0]
];
arrayGroups = getGroups(array);
console.log(arrayGroups);
console.log('Total groups: ', new Set(Object.values(arrayGroups)).size);