【问题标题】:Algorithm: Distance transform - any faster algorithm?算法:距离变换 - 任何更快的算法?
【发布时间】:2013-05-07 17:28:23
【问题描述】:

我正在尝试解决距离变换问题(使用曼哈顿距离)。基本上,给矩阵 0 和 1,程序必须将每个位置的距离分配到最近的 1。例如,对于这个

0000
0100
0000
0000

距离变换矩阵是

2123
1012
2123
3234

我认为可能的解决方案是:

最慢​​的(最慢的,因为我试图实现它们 - 它们在非常大的矩阵上滞后):

  1. 蛮力 - 对于程序读取的每 1,从开始到结束相应地更改距离。

  2. 从 0 开始的广度优先搜索 - 对于每一个 0,程序从内到外查找最近的 1。

  3. 与 2 相同,但从 1 的标记开始,每个距离由内向外。

  4. 更快(从其他人的代码中读取)

    从 1 开始的广度优先搜索

    1. Assign all values in the distance matrix to -1 or very big value.
    2. While reading matrix, put all positions of 1's into queue.
    3. While queue is not empty
        a. Dequeue position - let it be x
        b. For each position around x (that has distance 1 from it)
            if position is valid (does not exceed matrix dimensions) then
                if distance is not initialized or is greater than (distance of x) + 1 then
                    I. distance = (distance of x) + 1
                    II. enqueue position into queue
    

我想问是否有更快的解决方案来解决这个问题。我试图搜索距离变换的算法,但它们中的大多数都在处理欧几里得距离。

提前致谢。

【问题讨论】:

  • 4) 是广度优先搜索,以 1s 开头

标签: algorithm optimization


【解决方案1】:

广度优先搜索将执行Θ(n*m) 操作,其中nm 是矩阵的宽度和高度。

您需要输出Θ(n*m) 数字,因此从理论上讲,您不能比这更快。

我假设您对讨论缓存和此类优化不感兴趣。


请注意,此解决方案适用于更有趣的情况。例如,想象同样的问题,但可能有不同的“来源”:

00000
01000
00000
00000
00010

使用 BFS,您将在相同的时间复杂度下获得以下到最近源的距离:

21234
10123
21223
32212
32101

但是,对于单一来源,还有另一种解决方案可能在实践中具有稍好的性能(即使复杂性仍然相同)。

在此之前,让我们观察以下属性。

属性:如果源在 (a, b),则点 (x, y) 的曼哈顿距离如下:

d(x, y) = abs(x - a) + abs(y - b)

这应该很容易证明。所以另一种算法是:

for r in rows
  for c in cols
    d(r, c) = abc(r - a) + abs(c - b)

这很简短。


除非您编写和测试它,否则没有简单的方法来比较这两种算法。假设一个高效的有界队列实现(使用数组),每个单元格有以下主要操作:

  • BFS:队列插入/删除,每个节点访问5次(邻居4次,出队1次)
  • 直接公式:两个减法和两个ifs

这实际上取决于编译器及其优化以及特定的 CPU 和内存架构来判断哪个性能更好。

也就是说,我建议你选择对你来说更简单的那个。但是请注意,对于多个源,在第二种解决方案中,您需要对数组进行多次传递(或一次传递多次距离计算),对于足够多的源,这肯定会比 BFS 性能更差。

【讨论】:

  • 不会是O(n^2*m^2)吧,因为会有O(nm)个广度优先搜索,每一个都是O(nm)?
  • @mbeckish- 我不这么认为。一旦你找到了一个网格点到任何 1 的距离,你就不需要再重新处理那个正方形了。因此,您可以同时从每个 1 开始一个 BFS,然后再不做任何重复的工作。
  • @templatetypedef - 诀窍不是重新处理正方形,它可能取决于实现的细节。至少,区域之间的边界节点会被检查多次(因为你不知道什么时候停止扩展,直到你检查一个已经处理过的节点并确定它更接近另一个节点)。除非你能证明一个节点可以被检查多少次的恒定上限,否则你不能确定它是 O(n*m)。
  • @mbeckish- 由于每个方格有四个邻居,每个方格最多可以访问四次。还是我误解了您的担忧?
  • @mbeckish- 我认为,如果您将 BFS 队列中的所有方块都设置为 1,然后从那里继续,您将永远不会遇到这种情况。第一次到达队列中的节点时,必须给出从 any 的 1 到特定 0 的最短路径。因此,您永远不会一次填充网格,然后覆盖它在第二遍时,然后在第三遍时覆盖它,等等。但你是对的 - 只是迭代运行 BFS 并不能提供这种保证。
【解决方案2】:

您根本不需要队列或类似的东西。请注意,如果 (i,j) 与 (k,l) 的距离为 d,实现该距离的一种方法是向左或向右 |i-k|次,然后向上或向下 |j-l|次。

所以,用大数字初始化矩阵,并在输入中有1 的任何地方粘贴零。现在做这样的事情:

for (i = 0; i < sx-1; i++) {
  for (j = 0; j < sy-1; j++) {
    dist[i+1][j] = min(dist[i+1][j], dist[i][j]+1);
    dist[i][j+1] = min(dist[i][j+1], dist[i][j]+1);
  }
  dist[i][sy-1] = min(dist[i][sy-1], dist[i][sy-2]+1);
}
for (j = 0; j < sy-1; j++) {
  dist[sx-1][j] = min(dist[sx-1][j], dist[sx-2][j]+1);
}

此时,您已找到所有仅涉及向下或向右的最短路径。如果您对向上和向左执行类似的操作,dist[i][j] 将为您提供从 (i, j) 到输入矩阵中最近的 1 的距离。

【讨论】:

    猜你喜欢
    • 2011-11-17
    • 2016-04-13
    • 2014-11-29
    • 1970-01-01
    • 1970-01-01
    • 2020-04-26
    • 1970-01-01
    相关资源
    最近更新 更多