Python 这个算法优化了吗?否则怎么用呢?
我正在处理一个项目Euler问题()。我用另一种方法解决了这个问题,但我很好奇如何优化我最初试图解决这个问题的算法,因为它增长得非常快,并且对它实际需要的时间感到惊讶。所以我真的希望学习如何分析并继续优化算法 该算法的方法是使用角点作为点-Python 这个算法优化了吗?否则怎么用呢?,python,algorithm,optimization,Python,Algorithm,Optimization,我正在处理一个项目Euler问题()。我用另一种方法解决了这个问题,但我很好奇如何优化我最初试图解决这个问题的算法,因为它增长得非常快,并且对它实际需要的时间感到惊讶。所以我真的希望学习如何分析并继续优化算法 该算法的方法是使用角点作为点-(0,0)在左上角,对于2x2网格,使用(2,2)在左下角。从顶部开始,路径将仅为x+1或y+1。因此,我通过检查网格中的点空间中是否存在下一个允许的移动来迭代形成这些路径 我最初是从左上角的(x+1,y+1)开始的,但我发现从底部向后看更有效,删除了一些冗余
(0,0)
在左上角,对于2x2网格,使用(2,2)
在左下角。从顶部开始,路径将仅为x+1
或y+1
。因此,我通过检查网格中的点空间中是否存在下一个允许的移动来迭代形成这些路径
我最初是从左上角的(x+1,y+1)
开始的,但我发现从底部向后看更有效,删除了一些冗余,并且开始只在内存中存储有价值的数据。这就是我现在的处境。可以进一步优化吗?还有什么其他类型的应用程序
givenPoints
是网格中所有点的列表,存储为字符串-ie'0202'
。该算法存储唯一路径的最新点,而不是整个路径,最后列表中的条目数等于唯一路径数
def calcPaths4(givenPoints):
paths = []
paths.append(givenPoints[-1])
dims = int(math.sqrt(len(givenPoints))) - 1
numMoves = 2*dims
numPaths = 0
for x in range(0,numMoves):
t0= time.clock()
newPaths = []
for i in paths:
origin = int(i)
dest1 = origin - 1
dest3 = origin - 100
if ('%04d' % dest1) in givenPoints:
newPaths.append(('%04d' % dest1))
numPaths +=1
if ('%04d' % dest3) in givenPoints:
newPaths.append(('%04d' % dest3))
numPaths +=1
t= time.clock() - t0
paths = newPaths
print(str(x)+": " +str(t)+": " +str(len(paths)) )
return(paths)
你走错路了。从左上角到右下角,向右移动20次,向下移动20次 因此,您可以将任何路径视为长度为20的序列,其中10个元素为
右侧
,10个元素为下方
。你只需要数一数有多少欠款
一旦你修正了,比如说,向右
移动向下
的位置就被修正了,所以整个问题就归结为:你可以用多少种方式从一组20个位置中选择10个位置
这个问题可以简单地通过以下方法解决
因此,解决办法是:
from math import factorial
def number_of_paths(k):
"""Number of paths from the top left and bottom right corner in a kxk grid."""
return factorial(2*k)//(factorial(k)**2)
注意n!可以提高效率/(k!*k!)=(n·(n-1)··(k+1))/k代码>:
import operator as op
from functools import reduce
def number_of_paths(k):
"""Number of paths from the top left and bottom right corner in a kxk grid."""
return reduce(op.mul, range(2*k, k, -1), 1)//factorial(k)
请注意,路径的数量增长很快,这意味着通过创建不同路径工作的任何算法都会很慢。认真“优化”这一点的唯一方法是改变方法,避免创建路径,而只是计算它们
我会指出一种不同的、更一般的方法:递归和记忆/动态编程
当路径位于某个位置时,它可以直接进入(x,y)
,也可以向下进入(x,y)
。因此,从该点到右下角的路径数是从(x-1,y)
到达右下角的路径数和从(x,y-1)
到达右下角的路径数之和:
基本情况是当您处于边缘时,即x==0
或y==0
def number_of_paths(x, y):
if not x or not y:
return 1
return number_of_paths(x-1, y) + number_of_paths(x, y-1)
此解决方案遵循您的推理,但它只跟踪路径的数量。你可以再次看到,这是非常低效的
问题是当我们试图计算路径(x,y)的数量时
我们最终将执行以下步骤:
- 计算路径数(x-1,y)
- 这是通过计算路径数(x-2,y)和路径数(x-1,y-1)
- 计算路径数(x,y-1)
- 这是通过计算路径数(x-1,y-1)和路径数(x,y-2)
def number_of_paths(x, y, table=None):
table = table if table is not None else {(0,0):1}
try:
# first look if we already computed this:
return table[x,y]
except KeyError:
# okay we didn't compute it, so we do it now:
if not x or not y:
result = table[x,y] = 1
else:
result = table[x,y] = number_of_paths(x-1, y, table) + number_of_paths(x, y-1, table)
return result
现在执行速度非常快:
>>> number_of_paths(20,20)
137846528820
你可以认为“执行一个调用两次,没什么大不了的”,但你必须考虑到,如果对(x-1,y-1)
的调用计算了两次,那么每次它对(x-2,y-2)
执行两次调用,从而导致计算(x-2,y-2)
四次。然后(x-3,y-3)
八次。。。然后(x-20,y-20)
1048576次
或者,我们可以构建一个kxk
矩阵,并从右下角填充它:
def number_of_paths(x, y):
table = [[0]*(x+1) for _ in range(y+1)]
table[-1][-1] = 1
for i in reversed(range(x+1)):
for j in reversed(range(y+1)):
if i == x or j == y:
table[i][j] = 1
else:
table[i][j] = table[i+1][j] + table[i][j+1]
return table[0][0]
请注意,这里的表格表示交叉点,因此我们以尺寸中的+1
结束
这种记忆前一次调用以在以后重用它们的技术称为记忆。一个更普遍的原则是动态规划,在这里,您基本上可以将问题简化为填写一个表格数据结构,就像我们在这里所做的那样,使用递归和记忆,然后使用前面填写的指针在单元格上“回溯”,以获得原始问题的解决方案。听起来您可以更好地询问您的意思是(2,2)在右下角?我已经编辑了我的答案,包括对这个算法的改进。与创建每个唯一路径然后对它们进行计数不同,您只能跟踪每个坐标的路径数,并使用
(x+1,y)
和(x,y+1)
的计数计算通过坐标(x,y)
的路径数。这叫做动态规划。是的,右下角。我的错。谢谢你的回答!非常感谢您是否有链接解释kxk矩阵为何/如何工作?谢谢@jf2qm基本上是相同的推理。将20x20网格视为19x19网格(即右下部分)加上新行和新列。假设您知道19x19矩阵中零件数量的结果:如何填充新行和新列?您可以对非对称矩阵执行相同的操作:将3x20矩阵扩展为4x20矩阵,并添加顶行(代码就是这样做的)。无论如何,如果你搜索“动态规划”,你会发现大量的材料和应用。