我想先解决一些关于数学的困惑,然后讨论两个解决方案并为其中的一个提供代码。
有一个叫做#P的计数类,它很像是 - 否类NP。从质量上讲,它比NP更难。没有什么特别的理由可以相信这个计数问题比#P-hard更好,尽管证明这一点很困难或容易。
但是,许多#P-hard问题和NP-hard问题在实践中需要多长时间才会有很大差异,甚至一个特定的难题可能更难或更容易取决于输入的属性。 NP-hard或#P-hard意味着有困难的情况。一些NP-hard和#P-hard问题也有较少的困难情况或甚至是简单的情况。 (其他人的案例似乎比最困难的案例要容易得多)。
所以实际问题可能依赖于很多感兴趣的输入。假设阈值偏高或偏低,或者您拥有足够的内存以获得相当数量的缓存结果。然后有一个有用的递归算法,它使用了两个想法,其中一个已经提到:(1)在部分分配一些值之后,剩余的阈值对于列表分段可以排除所有的置换,或者它可能允许所有这些。 (2)内存允许时,应缓存一些剩余阈值和一些列表片段的小计。为了改善缓存,你也可以从列表中选择一个元素。
这里是实现这一算法的Python代码:
list1 = [1,2,3,4,5,6,7,8,9,10,11]
list2 = [1,2,3,4,5,6,7,8,9,10,11]
size = len(list1)
threshold = 396 # This is smack in the middle, a hard value
cachecutoff = 6 # Cache results when up to this many are assigned
def dotproduct(v,w):
return sum([a*b for a,b in zip(v,w)])
factorial = [1]
for n in xrange(1,len(list1)+1):
factorial.append(factorial[-1]*n)
cache = {}
# Assumes two sorted lists of the same length
def countprods(list1,list2,threshold):
if dotproduct(list1,list2) <= threshold: # They all work
return factorial[len(list1)]
if dotproduct(list1,reversed(list2)) > threshold: # None work
return 0
if (tuple(list2),threshold) in cache: # Already been here
return cache[(tuple(list2),threshold)]
total = 0
# Match the first element of list1 to each item in list2
for n in xrange(len(list2)):
total += countprods(list1[1:],list2[:n] + list2[n+1:],
threshold-list1[0]*list2[n])
if len(list1) >= size-cachecutoff:
cache[(tuple(list2),threshold)] = total
return total
print 'Total permutations below threshold:',
print countprods(list1,list2,threshold)
print 'Cache size:',len(cache)
由于注释行说,我测试此代码与阈值的硬值。这比在所有排列中进行天真的搜索要快得多。
如果满足三个条件,还有另一种算法比这个算法更好:(1)你没有足够的内存来存放好的缓存,(2)列表条目是小的非负整数,( 3)你对最难的门槛感兴趣。使用第二种算法的第二种情况是,如果您希望统计所有阈值是否平坦,是否满足其他条件。要将这个算法用于两个长度为n的列表,首先选择一个基本x,它是10或2的幂乘以n的因子。现在让矩阵
M[i][j] = x**(list1[i]*list2[j])
如果计算永久使用Ryser formula这个矩阵M,那么永久的底数x的第k个数字告诉你排列为其积正是数k。此外,Ryser公式比直接对所有排列进行求和要快得多。 (但它仍然是指数型的,所以它不会与计算永久物是#P-hard的事实相矛盾。)
此外,是的,确实是排列组是对称组。如果你能以某种方式使用群论来加速这个计数问题,那将是非常好的。但据我所知,这个问题的描述并没有深刻。最后,如果不是准确计算低于某个阈值的置换数量,则只需要大约该数字,那么可能游戏完全改变。 (您可以近似多项式时间的永久值,但在这里没有帮助。)我不得不考虑要做什么;无论如何,这不是提出的问题。
我意识到还有另一种缓存/动态编程,从上面的讨论和上面的代码中缺少。在代码中实现的缓存是早期缓存:如果仅将list1的前几个值分配给list2,并且如果剩余阈值多次出现,则缓存允许代码重用结果。如果list1和list2的条目是不是太大的整数,这很好用。但是如果条目是典型的浮点数,它将是一个失败的缓存。
但是,当list1的大部分值已被分配时,您也可以在另一端预先计算。在这种情况下,您可以为所有剩余值创建小计的排序列表。请记住,您可以按顺序使用list1,并在list2端执行所有排列。例如,假设list1的最后三个条目是[4,5,6],并且假设list2中的三个值(位于中间某处)是[2.1,3.5,3.7]。然后你会缓存六点产品的排序列表:
endcache[ [2.1, 3.5, 3.7] ] = [44.9, 45.1, 46.3, 46.7, 47.9, 48.1]
这对你有什么影响?如果你查看我发布的代码,函数countprods(list1,list2,threshold)递归地使用一个子阈值进行工作。第一个参数list1作为全局变量可能比参数更好。如果list2足够短,则通过在列表endcache [list2]中执行二进制搜索,countprods可以更快地完成工作。 (我刚刚从stackoverflow得知,这是在Python中的bisect模块中实现的,尽管性能代码不会用Python编写)。与头缓存不同,最终缓存可以加快代码速度,即使存在在list1和list2的条目中没有数字符合。 Ryser的算法对于这个问题也没有数值上的一致性,所以对于这种类型的输入,我只能看到两种加速度:使用“全部”测试和“无”测试以及结束缓存锯掉搜索树的一个分支。
你要求和?或者你要求prodSum(L1,L2)
DarthVader
2009-11-29 03:01:34
不是功课。这实际上是我需要解决的计算统计函数的问题。我把它放在这种背景下,因为它会使非统计人员的问题变得不必要地复杂化。 – dsimcha 2009-11-29 03:12:16
你的意思是我们可以假设L1 = range(1,n)和L2 = range(1,m)?即每个都是一些到所有点的_all_整数列表? – Managu 2009-11-29 03:17:34