在网格中查找最佳长路径

时间:2015-02-08 18:01:33

标签: c++ algorithm recursion dynamic-programming

给定是一个N * N网格。现在我们需要找到一条最大长度的好路径,其中好的路径定义如下:

  1. 好路径始终从标记为0
  2. 的单元格开始
  3. 我们只允许向左,向右,向上或向下移动
  4. 如果第i个单元格的值为A,则路径中下一个单元格的值必须为A + 1.
  5. 现在给出这几个条件,我需要找出可以进行的最大路径的长度。此外,我需要计算最大长度的路径。

    示例:设N = 3,我们有3 * 3矩阵如下:

    0 3 2               
    3 0 1               
    2 1 0            
    

    此处最大良好路径长度为3,此类良好路径的数量为4。

    0 3 2
    3 0 1
    2 1 0

    0 3 2
    3 0 1
    2 1 0

    0 3 2
    3 0 1
    2 1 0

    0 3 2
    3 0 1
    2 1 0

4 个答案:

答案 0 :(得分:4)

此问题是Longest Path Problem的变体,但是您的限制会使此问题变得更加容易,因为图表实际上是Directed Acyclic Graph (DAG),因此问题可以有效解决。

定义有向图G=(V,E)如下:

  • V = { all cells in the matrix}(完整性检查:| V | = N ^ 2)
  • E = { (u,v) | u is adjacent to v AND value(u) + 1 = value(v) }

请注意,上述定义中的结果图是一个DAG,因为您不能有任何周期,因为它会产生一些e= (u,v)的边value(u) > value(v)

现在,您只需要从任何起点找到longest path in a DAG。这是通过图表上的topological sort完成的,然后使用动态编程:

init:
for every source in the DAG:
D(v) = 0            if value(v) = 0
       -infinity    otherwise
step:
for each node v from first to last (according to topological sort)
   D(v) = max{D(u) + 1 | for each edge (u,v) }

完成后,找到具有最大值v的节点D(v),这是最长"良好路径"的长度。
找到路径本身是通过重新滚动上面的步骤来完成的,从最大D(v)回溯你的步骤,直到你返回值为0的初始节点。

此方法的复杂性为O(V+E) = O(n^2)


由于您要查找最长路径的数量,因此可以稍微修改此解决方案以计算到达每个节点的路径数,如下所示:

Topological sort the nodes, let the sorted array be arr (1)
For each node v from start to end of arr:
   if value(v) = 0:
        set D(v) = 1 
   else
        sum = 0
        for each u such that (u,v) is an edge: (2)
            sum = sum + D(u) 
        D(v) = sum

以上将为每个节点v找到"良好路径的数量"到达它的D(v)。您现在要做的就是找到具有总和节点x的最大值v,以便value(v) = xD(v) > 0,并将到达任何节点的路径数相加value(v)

max = 0
numPaths = 0
for each node v:
    if value(v) == max:
         numPaths = numPaths + D(v)
    else if value(v) > max AND D(v) > 0:
         numPaths = D(v)
         max = value(v)
return numPaths

注意: (1) - a"常规" sort在这里工作,但它需要O(n ^ 2logn)时间,并且拓扑排序需要O(n ^ 2)时间
(2)提醒,(u,v)是边缘,如果:(1)uv相邻(2)值(u)+ 1 =值(v)< / p>

答案 1 :(得分:0)

您可以使用简单的广度优先搜索来完成此操作。

首先找到标记为0的所有细胞。(这是O(N 2 )。)在每个这样的细胞上放一个步行者。每个步行者都会将一个数字'p'初始化为1。

现在迭代:

所有步行者站在具有相同数字k的细胞上。每个助行器都会查找标有k + 1的相邻单元格(左,右,上或下)。

如果没有助行者看到这样的单元格,搜索结束。最长路径的长度为k,这些路径的数量是所有步行者的p的总和。

如果一些步行者看到这样的数字,那就杀死任何不这样做的步行者。

每个步行者都会进入一个好的相邻小区。如果一个步行者看到一个以上的好细胞,它就会分成尽可能多的步行者,就像有好的细胞一样,一个进入每个细胞。 (每个“孩子”具有与其“父母”相同的p值。)如果两个或更多个步行者在同一个单元格中相遇(即,如果多个路径通向该单元格),则它们组合成一个步行者,'p'值是'p'值的总和。

该算法为O(N 2 ),因为不能多次访问一个单元格,并且步行者的数量不能超过单元格数。

答案 2 :(得分:0)

我是使用ActionScript完成的,希望它具有可读性。我认为它工作正常,但我可能错过了一些东西。

const N:int = 9; // field size
const MIN_VALUE:int = 0; // start value

var field:Array = [];

// create field - not relevant to the task
var probabilities:Array = [0,1,2,3,4,5];
for (var i:int = 0; i < N * N; i++) field.push(probabilities[int(Math.random() * probabilities.length)]);//RANGE));
print_field();

// initial chain fill. We will find any chains of adjacent 0-1 elements.
var chain_list:Array = [];
for (var offset:int = 0; offset < N * N - 1; offset++) {
    if (offset < N * N - N) { // y coordinate is not the lowest
        var chain:Array = find_chain(offset, offset + N, MIN_VALUE);
        if (chain) chain_list.push(chain);
    }
    if ((offset % N) < N - 1) { // x coordinate is not the rightmost
        chain = find_chain(offset, offset + 1, MIN_VALUE);
        if (chain) chain_list.push(chain);
    }
}

var merged_chain_list:Array = chain_list;
var current_value:int = MIN_VALUE + 1;

// for each found chain, scan its higher end for more attached chains
// and merge them into new chain if found
while(chain_list.length) {
    chain_list = [];
    for (i = 0; i < merged_chain_list.length; i++) {
        chain = merged_chain_list[i];
        offset = chain[chain.length - 1];

        if (offset < N * N - N) {
            var tmp:Array = find_chain(offset, offset + N, current_value);
            if (tmp) chain_list.push(merge_chains(chain, tmp));
        }
        if (offset > N) {
            tmp = find_chain(offset, offset - N, current_value);
            if (tmp) chain_list.push(merge_chains(chain, tmp));
        }
        if ((offset % N) < N - 1) {
            tmp = find_chain(offset, offset + 1, current_value);
            if (tmp) chain_list.push(merge_chains(chain, tmp));
        }
        if (offset % N) {
            tmp = find_chain(offset, offset - 1, current_value);
            if (tmp) chain_list.push(merge_chains(chain, tmp));
        }
    }

    //save the last merged result if any and try the next value
    if (chain_list.length) {
        merged_chain_list = chain_list;
        current_value++;
    }
}

// final merged list is a list of chains of a same maximum length
print_chains(merged_chain_list);

function find_chain(offset1, offset2, current_value):Array {
    // returns always sorted sorted from min to max
    var v1:int = field[offset1];
    var v2:int = field[offset2];
    if (v1 == current_value && v2 == current_value + 1) return [offset1, offset2];
    if (v2 == current_value && v1 == current_value + 1) return [offset2, offset1];
    return null;
}

function merge_chains(chain1:Array, chain2:Array):Array {
    var tmp:Array = [];
    for (var i:int = 0; i < chain1.length; i++) tmp.push(chain1[i]);
    tmp.push(chain2[1]);
    return tmp;
}

function print_field():void {
    for (var pos_y:int = 0; pos_y < N; pos_y++) {
        var offset:int = pos_y * N;
        var s:String = "";
        for (var pos_x:int = 0; pos_x < N; pos_x++) {
            var v:int = field[offset++];
            if (v == 0) s += "[0]"; else s += " " + v + " ";
        }
        trace(s);
    }
}

function print_chains(chain_list):void {
    var cl:int = chain_list.length;
    trace("\nchains found: " + cl);
    if (cl) trace("chain length: " + chain_list[0].length);
    for (var i:int = 0; i < cl; i++) {
        var chain:Array = chain_list[i];
        var s:String = "";
        for (var j:int = 0; j < chain.length; j++) s += chain[j] + ":" + field[chain[j]] + " ";
        trace(s);
    }
}

示例输出:

 1  2  1  3  2  2  3  2  4 
 4  3  1  2  2  2 [0][0] 1 
[0][0] 1  2  4 [0] 3  3  1 
[0][0] 5  4  1  1 [0][0] 1 
 2  2  3  4  3  2 [0] 1  5 
 4 [0] 3 [0] 3  1  4  3  1 
 1  2  2  3  5  3  3  3  2 
 3  4  2  1  2  4  4  4  5 
 4  2  1  2  2  3  4  5 [0]

chains found: 2
chain length: 5
23:0 32:1 41:2 40:3 39:4 
33:0 32:1 41:2 40:3 39:4 

答案 3 :(得分:0)

我用自己的Lisp方言实现了它,所以源代码不会对你有多大帮助:-) ...

编辑:也添加了Python版本。

无论如何,这个想法是:

  • 编写一个函数paths(i, j) --> (maxlen, number),它返回从(i, j)开始的最大路径长度以及它们中有多少...
  • 此函数是递归的,查看值为(i, j)的{​​{1}}的邻居将调用M[i][j]+1以获取有效邻居的结果
  • 如果邻居的最大长度大于当前最大长度,则设置新的当前最大长度并重置计数器
  • 如果最大长度与当前相同,则将计数器添加到总计
  • 如果最大长度较小,则忽略该邻居结果
  • 缓存单元格的计算结果(这非常重要!)。在我的版本中,代码分为两个相互递归的函数:paths(ni, nj),首先检查缓存,否则调用paths;处理邻居时compute-paths调用compute-paths。递归调用的缓存大致相当于一种显式的动态编程方法,但有时更容易实现。

要计算最终结果,您基本上会执行相同的计算,但会为所有paths单元格添加结果,而不是考虑邻居。

请注意,不同路径的数量可能会变得很大,这就是为什么枚举所有这些路径不是一个可行的选项,并且缓存/ DP是必须的:例如对于具有值的0矩阵N=20有35,345,263,800条长度为38的最大路径。

该算法及时为O(N ^ 2)(每个单元最多访问一次)并且需要O(N ^ 2)空间用于高速缓存和递归。当然,鉴于输入本身由N ^ 2个数字组成,您至少需要读取它们才能计算出答案,所以你不能指望得到更好的东西。

M[i][j] = i+j

修改

以下是Python中的上述音译,如果您之前从未见过Lisp,那么应该更容易理解......

(defun good-paths (matrix)
  (let** ((N (length matrix))
          (cache (make-array (list N N)))
          (#'compute-paths (i j)
            (let ((res (list 0 1))
                  (count (1+ (aref matrix i j))))
              (dolist ((ii jj) (list (list (1+ i) j) (list (1- i) j)
                                     (list i (1+ j)) (list i (1- j))))
                (when (and (< -1 ii N) (< -1 jj N)
                           (= (aref matrix ii jj) count))
                  (let (((maxlen num) (paths ii jj)))
                    (incf maxlen)
                    (cond
                      ((< (first res) maxlen)
                       (setf res (list maxlen num)))
                      ((= (first res) maxlen)
                       (incf (second res) num))))))
              res))
          (#'paths (i j)
            (first (or (aref cache i j)
                       (setf (aref cache i j)
                             (list (compute-paths i j))))))
          (res (list 0 0)))
    (dotimes (i N)
      (dotimes (j N)
        (when (= (aref matrix i j) 0)
          (let (((maxlen num) (paths i j)))
            (cond
              ((< (first res) maxlen)
               (setf res (list maxlen num)))
              ((= (first res) maxlen)
               (incf (second res) num)))))))
    res))