在2D数组中创建类似元素的集合

时间:2013-07-22 19:23:01

标签: arrays algorithm multidimensional-array set

我正在尝试解决基于2D数组的问题。该数组包含不同类型的元素(总共3种可能的类型)。让我们假设这种类型为X,Y,Z。

阵列似乎是这样的。请注意,它将始终完全填充。该图表仅供参考。

7 | | | | | | |
6 | | | | | | |
5 | | | | | | |
4 | |X|Z|Y|X| |
3 | |Y|X|Y|Y|X|
2 |Y|Y|X|Z|Z|X|
1 |X|X|Y| |X|X|
0 | | | |Z| | |
   0 1 2 3 4 5

我正在尝试创建彼此相邻放置的元素集。例如,set1可以包括位于以下的类型X的元素:(0,1),(1,1),(2,2),(2,3),(1,4)。类似地,set2可以包括Y型元素,位于:(3,4),(3,3),4,3)。

问题:鉴于数组中的任何一点,它必须能够将所有元素添加到适当的集合,并确保没有两个集合包含相同的元素。请注意,只有在遇到两个以上相同类型的相邻元素时才会创建一个集合。

此外,如果删除元素的某个子集,则会添加更多元素来替换删除的元素。然后必须重新迭代该数组以创建新集或修改现有数组。

解决方案:我实现了一个递归解决方案,以便迭代所有相邻元素,例如元素X(0,1)。然后,在迭代8个可能的相邻元素时,只要发生类型X,它就会递归调用自身。

这种解决方案过于暴力和低效,特别是在某些元素被可能不同类型的新元素替换的情况下。在这种情况下,几乎整个数组都必须重新进行迭代以制作/修改集合,并确保在多个集合中不存在相同的元素。

是否有任何算法可以有效地处理这类问题?我需要一些想法/建议或伪代码的帮助。

4 个答案:

答案 0 :(得分:7)

[编辑2013年5月8日:固定时间复杂度。 (O(a(n))基本上是恒定的时间!)]

在下文中,“连通分量”是指通过仅允许具有相同种类元素的相邻位置之间的水平,垂直或对角线移动的路径彼此可到达的所有位置的集合。例如。您的示例{(0,1), (1,1), (2,2), (2,3), (1,4)}是示例输入中的连接组件。每个位置都属于一个连接的组件。

我们将构建一个union/find data structure,用于给每个位置(x,y)一个数字“标签”,该标签具有以下属性:当且仅当任意两个位置(x,y)和(x' ,y')属于同一个组件,然后它们具有相同的标签。特别是这种数据结构支持三种操作:

  • set(x, y, i)会将位置(x,y)的标签设置为i。
  • find(x, y)将返回分配给位置(x,y)的标签。
  • 对于某些标签Z,
  • union(Z)会将Z中的所有标签合并为单个标签k,这意味着将来在任何位置(x,y)上调用find(x, y)在Z中有一个标签现在将返回k。 (通常k将是Z中已有的标签之一,但这实际上并不重要。)union(Z)也会返回新的“主”标签,k。

如果总共有n = width *高度位置,则可以在O(n * a(n))时间内完成,其中a()是极慢增长的逆Ackermann函数。 对于所有实际输入大小,这与O(n)相同。

请注意,只要两个顶点彼此相邻,就有四种可能的情况:

  1. 一个在另一个之上(由垂直边缘连接)
  2. 一个在另一个的左边(通过水平边缘连接)
  3. 一个在另一个的上方和左侧(由\对角线边缘连接)
  4. 一个在另一个的上方和右侧(由/对角线边缘连接)
  5. 我们可以使用以下过程来确定每个位置(x,y)的标签:

    • 将nextLabel设为0。
    • 对于每行y递增顺序:
      • 对于每列x按升序排列:
        • 检查(x,y)的W,NW,N和NE邻居。设Z是这4个邻居的子集,与(x,y)相同。
        • 如果Z是空集,那么我们暂时假设(x,y)启动一个全新的组件,所以调用set(x,y,nextLabel)并递增nextLabel。
        • 否则,在Z的每个元素上调用find(Z [i])来查找它们的标签,并在这组标签上调用union()将它们组合在一起。将新标签(此union()调用的结果)分配给k,然后调用set(x,y,k)将(x,y)添加到此组件。

    在此之后,在任何位置(x,y)上调用find(x, y)可以有效地告诉您它属于哪个组件。如果您希望能够快速回答“哪些位置属于包含位置(x,y)的连通组件?”形式的查询?然后创建列表posInComp的哈希表,并在输入数组上进行第二次传递,将每个(x,y)追加到列表posInComp[find(x, y)]。这可以在线性时间和空间中完成。现在回答某个给定位置(x,y)的查询,只需调用lab = find(x, y)找到该位置的标签,然后在posInComp[lab]中列出位置。

    要处理“太小”的组件,只需查看posInComp[lab]的大小即可。如果它是1或2,则(x,y)不属于任何“足够大”的组件。

    最后,所有这些工作都需要线性时间,因此除非你的输入数组很大,否则它会很快。因此,在修改输入数组后从头开始重新计算它是完全合理的。

答案 1 :(得分:1)

在你的情况下,我至少会依赖于两个不同的阵列:

Array1 (sets) -> all the sets and the associated list of points. Main indices: set names.
Array2 (setsDef) -> type of each set ("X", "Y" or "Z"). Main indices: type names.

有可能创建更多的支持数组,例如,包括每组的最小/最大X / Y值,以加快分析速度(尽管如此,如下所示,它会非常快)。 / p>

您没有提及任何编程语言,但我提供了一个示例(C#)代码,因为它是解释这一点的最佳方式。请不要将其理解为最佳方式的建议(个人而言,我不太喜欢Dictionaries / Lists;尽管认为这确实提供了一个很好的图形化方式来展示算法,即使是没有经验的C#用户)。此代码仅旨在显示数据存储/检索方法;实现最佳性能的最佳方式取决于目标语言和其他问题(例如,数据集大小),这是您必须要处理的事情。

Dictionary<string, List<Point>> sets = new Dictionary<string, List<Point>>(); //All sets and the associated list of points
Dictionary<string, List<string>> setsDef = new Dictionary<string, List<string>>(); //Array indicating the type of information stored in each set (X or Y)

List<Point> temp0 = new List<Point>();
temp0.Add(new Point(0, 0));
temp0.Add(new Point(0, 1));
sets.Add("Set1", temp0);
List<String> tempX = new List<string>();
tempX.Add("Set1");

temp0 = new List<Point>();
temp0.Add(new Point(0, 2));
temp0.Add(new Point(1, 2));
sets.Add("Set2", temp0);
List<String> tempY = new List<string>();
tempY.Add("Set2");

setsDef.Add("X", tempX);
setsDef.Add("Y", tempY);


//-------- TEST
//I have a new Y value which is 2,2
Point targetPoint = new Point(2, 2);
string targetSet = "Y";

//I go through all the Y sets
List<string> targetSets = setsDef[targetSet];

bool alreadyThere = false;
Point candidatePoint;
string foundSet = "";
foreach (string set in targetSets) //Going through all the set names stored in setsDef for targetSet
{
    List<Point> curPoints = sets[set];
    foreach (Point point in curPoints) //Going through all the points in the given set
    {
        if (point == targetPoint)
        {
            //Already-stored point and thus the analysis will be stopped
            alreadyThere = true;
            break;
        }
        else if (isSurroundingPoint(point, targetPoint))
        {
            //A close point was found and thus the set where the targetPoint has to be stored
            candidatePoint = point;
            foundSet = set;
            break;
        }
    }
    if (alreadyThere || foundSet != "")
    {
        break;
    }
}

if (!alreadyThere)
{
    if (foundSet != "")
    {
        //Point added to an existing set
        List<Point> curPoints = sets[foundSet];
        curPoints.Add(targetPoint);
        sets[foundSet] = curPoints;
    }
    else
    {
        //A new set has to be created
        string newName = "New Set";
        temp0 = new List<Point>();
        temp0.Add(targetPoint);
        sets.Add(newName, temp0);

        targetSets.Add(newName);
        setsDef[targetSet] = targetSets;
    }
}

其中isSurroundingPoint是检查两个点是否彼此接近的函数:

private bool isSurroundingPoint(Point point1, Point point2)
{
    bool isSurrounding = false;
    if (point1.X == point2.X || point1.X == point2.X + 1 || point1.X == point2.X - 1)
    {
        if (point1.Y == point2.Y || point1.Y == point2.Y + 1 || point1.Y == point2.Y - 1)
        {
            isSurrounding = true;
        }
    }
    return isSurrounding;
}

答案 2 :(得分:1)

您可能需要查看region growing算法,这些算法用于图像分割。这些算法从种子像素开始,并生长一个连续区域,该区域中的所有像素都具有某些属性。

在您的情况下,如果相邻的“像素”具有相同的标签(即,元素X,Y或Z的种类),则它们位于同一图像片段中

答案 3 :(得分:0)

我写了something来为另一个SO问题找到一种类型的对象。下面的示例添加了两种类型。任何重新迭代都会再次检查整个列表。我们的想法是分别处理每种类型的点列表。函数solve对任何连接点进行分组,并在枚举下一组之前将其从列表中删除。 areConnected检查点坐标之间的关系,因为我们只测试一种类型的点。在这个通用版本中,类型(a b c)可以是任何东西(字符串,数字,元组等),只要它们匹配。

btw - 这是j_random_hacker极好算法的JavaScript示例的链接:http://jsfiddle.net/groovy/fP5kP/

Haskell代码:

import Data.List (elemIndices, delete) 

example = ["xxyyyz"
          ,"xyyzzz"
          ,"yxxzzy"
          ,"yyxzxy"
          ,"xyzxyy"
          ,"xzxxzz"
          ,"xyzyyz"
          ,"xyzxyy"]

objects a b c ws = [("X",solve xs []),("Y",solve ys []),("Z",solve zs [])] where
  mapIndexes s = 
    concatMap (\(y,xs)-> map (\x->(y,x)) xs) $ zip [0..] (map (elemIndices s) ws)
  [xs,ys,zs] = map mapIndexes [a,b,c]
  areConnected (y,x) (y',x') = abs (x-x') < 2 && abs (y-y') < 2
  solve []     r = r
  solve (x:xs) r =
    let r' = solve' xs [x]
    in solve (foldr delete xs r') (if null (drop 2 r') then r else r':r)
  solve' vs r =
    let ys = filter (\y -> any (areConnected y) r) vs
    in if null ys then r else solve' (foldr delete vs ys) (ys ++ r)

示例输出:

*Main> objects 'x' 'y' 'z' example
[("X",[[(7,0),(6,0),(5,0),(4,0)]
      ,[(3,4),(5,2),(5,3),(4,3),(2,2),(3,2),(2,1),(0,1),(1,0),(0,0)]])
,("Y",[[(7,5),(6,4),(7,4),(6,3)],[(4,4),(4,5),(3,5),(2,5)]
      ,[(4,1),(3,0),(3,1),(0,4),(2,0),(0,3),(1,1),(1,2),(0,2)]])
,("Z",[[(5,5),(6,5),(5,4)]
      ,[(7,2),(6,2),(5,1),(4,2),(3,3),(1,3),(2,3),(2,4),(1,4),(1,5),(0,5)]])]
(0.02 secs, 1560072 bytes)