从电路板的一个角落到另一个角落寻找一些独特的路径而不需要回溯

时间:2013-03-16 16:00:37

标签: python algorithm optimization

我坚持使用算法优化。

目标是找出在国际象棋棋盘中从A点到B点的不同方式:

  • A是左下角的方格。
  • B是右上角。
  • 每回合只能上一个或一个。

这是一个虚拟解决方案:

# -*- conding: utf-8 -*-
import time

def solution(n, m, x, y):
   ret = 0
   if x < n-1:
      ret += solution(n, m, x+1, y)
   if y < m-1:
      ret += solution(n, m, x, y+1)
   if x == n-1 and  y == m-1:
     ret = 1
   return ret

def wrapper(n, m):
    start = time.time()
    reponse = solution(n, m, 0, 0)
    stop = time.time()
    print "Response: %dx%d = %d\nTime : %f\n" % (n, m, reponse, stop-start)



if __name__ == "__main__":
    for i in range(10):
        wrapper(i+1,i+1)
    #wrapper(7,7)
    #wrapper(10,10)
    #wrapper(100,100)
    #wrapper(1000,1000)
    #wrapper(10000,10000) <- way too slow

当我留在小棋盘时,它工作正常,结果是相关的。但我的目标是为10000x10000电路板找到解决方案。

是否有任何想法?

4 个答案:

答案 0 :(得分:4)

这样想:由于你的A点和B点在同一个地方,你必须移动相同数量的UP和RIGHT,但顺序会有所不同。所以你需要找到不同组合的数量。

答案 1 :(得分:2)

动态编程:O(N)

已经提到使用Triangle of Pascal及其与Binomial coefficient的关系来解决问题。此外,Catalan number的条目对 n × n 案例有很好的说明。

n × n 案例

通过使用上述资源,您可以得出结论,对于大小 n × n 的网格,您需要计算C(2n - 2,n - 1) 。您可以通过将网格旋转45度并映射Pascal的三角形来仔细检查它。

实际上,直接计算这个数字需要以一种天真的方式计算最多3个不同的因子,这是一项非常昂贵的任务。如果你可以预先计算它们,那么这里就没有讨论,你可以说这个问题有复杂性O(1)。如果您对预先计算的方式不感兴趣,那么您可以继续阅读。

您可以使用动态编程(DP)计算这种不祥的数字。这里的诀窍是以较小的步骤执行操作,根本不需要你计算一个大的阶乘数。

也就是说,要计算C(n,k),你可以先将自己置于C(n,1)并走到C(n,k)。让我们从C(n,k - 1)来定义C(n,k)。

C(n, k) = n! / k! * ( n - k )!                            ; k! = (k - 1)! * k
        = n! / (k - 1)! * k * (n - k)!                    ; (n - k)! = (n - k + 1)! / (n - k + 1)
        = n! * (n - k + 1) / (k - 1)! * k * (n - k + 1)!  ; C(n, k - 1) = n! / (k - 1)! * ( n - k + 1 )!
        = C(n, k - 1) * (n - k + 1) / k

基于此,您可以在Python中定义一个函数来计算C(n,k),如下所示:

def C(n, k):
    """
    Calculate C(n, k) using Dynamic Programming.
    C(n, k) = C(n, k - 1) * (n - k + 1) / k
    """
    C = 1
    for ki in range(1, k + 1):
        C = C * (n - ki + 1) / ki
    return C

它以线性时间运行,O(N)。

对于 n × n 情况,您需要计算C(2n - 2,n - 1)。

>> print "Response: %dx%d = %d" % (n, n, C(2 * n - 2, n - 1),)
Response: 10000x10000 = 5...

n × m 案例

对于一般的 n × m 情况,你只需要计算C(n + m - 2,m - 1)。

>> print "Response: %dx%d = %d" % (n, m, C(n + m - 2, m - 1),)
Response: 10000x10000 = 5...    

最后但并非最不重要的是,you can see a live example at Ideone here.

计时

我运行了以下网格尺寸的算法。

      N x N      | Response's Length |   Time
-----------------+-------------------+-----------
      1 x 1      |           1 chars |  0.000001
     10 x 10     |           5 chars |  0.000004
    100 x 100    |          59 chars |  0.000068
   1000 x 1000   |         600 chars |  0.002207
  10000 x 10000  |        6018 chars |  0.163647
 100000 x 100000 |       60203 chars | 40.853971

由于所涉及的数量非常大,看起来网格尺寸为100 000 x 100 000的操作变得非常昂贵。没什么可惊讶的。

答案 2 :(得分:1)

您不需要算法。只是数学。这里有一些需要考虑的事项:当你在右上方时,你没有任何不同的选择。让我们把它算作零。如果您就在右上角的右侧,您唯一的选择是向右(一个选项),因为您不允许回溯。当你在右上角的下方时,你唯一的选择是上升。让我们把它映射出来

... 1 0
..... 1

从目标角落向左/向下的角落怎么样?从那里,有两条通往拐角的路径(到达邻居的选项总和):你可以向右走:

... 1 0
....2 1

扩展边缘我们总是使用1扩展:一旦你位于顶部,只有一种方法可以到达右上角:

...1 1 1 0
...... 2 1
........ 1
........=1

但是每个非边缘选择是北方和东方邻居中数字的总和:

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

等等。希望这可以帮助您开始解决方案。

对此也有不同的思考方式。给定NxN板,您必须进行2N移动才能从一个角落到另一个角落。这些移动中的N个是北移,N是东移。问题是:N个东移多少个不同的组合,N个北移可以有一个2N长的字符串。

答案 3 :(得分:0)

您正在寻找Pascal的三角形。 wikipedia link甚至提到了您的确切问题