【问题标题】:Using dictionaries to improve algorithm efficiency使用字典提高算法效率
【发布时间】:2020-04-21 16:44:53
【问题描述】:

第n个三角形数定义为和1+2+...+n。我正在研究Project Euler problem 12,它要求找到具有超过 500 个除数的最小三角形数,因此(在 Python 中)我编写了两个函数,mytri(n)mydiv(n),来计算第 n 个三角形数和数字n 的除数,分别。然后,我使用了一个循环,直到mydiv(mytri(n)) 大于或等于 500:

import math

def mytri(n):
    return n*(n+1)/2

def mydivs(n):
    num = 0
    max = math.floor(n/2)
    for k in range(1,max+1):
        if n%k == 0: 
            num += 1
    return num+1

n = 1
while (mydivs(mytri(n)) <= 500): n += 1

print(mytri(n))

我认为我写mytri()mydiv() 的效率很高,但根据一些测试,这个程序似乎很快就会变得笨拙。计算第一个有超过 100 个除数的数需要不到一秒,但计算第一个有超过 150 个除数的数大约需要 8-9 秒,这表明它可能是指数时间?我在计算复杂性或编写高效算法方面没有太多经验,但我曾经看到一个使用字典的示例(我认为是记忆化?)来极大地改进递归算法来计算斐波那契数,我想知道类似的想法是否可以在这里使用。

例如,第 n 个三角形数可以表示为 n(n+1)/2,因此不失一般性,它是奇数和偶数的乘积,分别为 n 和 (n+1)/2。如果您可以将每个数字的除数最多存储在字典中,那么您就不必在mydiv() 中重做计算,而只需引用字典即可​​。唯一的问题是找出 n 和 (n+1)/2 之间的哪些除数重叠以获得正确的数量。这是合理的进攻路线吗?还是我在这里遗漏了什么?

另外,我的算法的时间复杂度是多少,我将如何计算它?

【问题讨论】:

  • 你不能真正使用任何内存,因为你总是使用其他 n。 (据我所知)。斐波那契之所以有效,是因为F(n) = F(n-1) + F(n-2) 它有一个递归,如果你计算F(50),你将计算F(30) 多次,因此你可以使用内存。您可能应该从考虑另一种更有效的算法开始。
  • @goodatthis 检查我发布的答案。我可以帮你。
  • @goodatthis 如果对您有帮助,请接受答案。它将帮助整个社区确定正确的答案。

标签: python algorithm dynamic-programming


【解决方案1】:

mytri(n) 的时间复杂度是O(1)mydivs(n) 的时间复杂度是 O(n/2) 也就是 O(n).while (mydivs(mytri(n)) &lt;= 500) 的时间复杂度是 O(n^3) 因为它是一个循环内的循环,一个循环运行 N 次,其他运行 N^2 次。您可以将 mydivs(n) 的时间复杂度降低到 O(sqrt(n)

def new_mydivs(n):
    res=set()
    for i in range(1,int(n**0.5)+1):
        #print(i)
        if n%i==0:
            res.update([i,n//i])
        #print(res)
    return len(res)    #returns the number of divisors.

new_mydivs(n)的时间复杂度是O(sqrt(n))

查找具有 250 个除数的数的代码执行时间。

import time
import timeit
import math
def mytri(n):
    return n*(n+1)/2
def mydivs(n):
    num = 0
    max = math.floor(n/2)
    for k in range(1,max+1):
        if n%k == 0: 
            num += 1
    return num+1

def main():
    n = 1
    while (mydivs(mytri(n)) <= 250): n += 1

    print(mytri(n))

startTime=time.time()
main()
print(time.time()-startTime)

输出:

2162160.0
100.24735450744629

我的 250 除数的代码执行时间:

import time
import timeit
import math
def mytri(n):
    return n*(n+1)/2
def mydivs(n):
    res=set()
    for i in range(1,int(n**0.5)+1):
        #print(i)
        if n%i==0:
            res.update([i,n//i])
        #print(res)
    return len(res)    #returns the number of divisors.

def main():
    n = 1
    while (mydivs(mytri(n)) <= 250): n += 1

    print(mytri(n))

startTime=time.time()
main()
print(time.time()-startTime)

输出:

2162160.0
0.22459840774536133

对于 500 个除数:

76576500.0
5.7917985916137695

对于 750 除数:

236215980.0
17.126375198364258

看到性能大幅提升。

【讨论】:

  • 原来的复杂度是O(N^3),你把它降低到O(N^2)。
【解决方案2】:

RE:时间复杂度。你有两个循环,一个循环,一个循环最多 N,另一个循环最多 N^2。这给了我们 O(N^3) 的时间复杂度。

您可以使用字典来保存部分结果,但总体复杂度仍将是 O(N^3),但常数因子较小,因为您仍然需要遍历其余值。

【讨论】:

  • 哪些部分结果?据我所知,他没有计算两次的值。
猜你喜欢
  • 1970-01-01
  • 2011-06-08
  • 2018-02-22
  • 2015-05-26
  • 2021-04-05
  • 1970-01-01
  • 2015-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多