以递增的顺序迭代成对的数字

时间:2013-12-08 22:02:47

标签: algorithm sorting numbers iteration

我将在底部解释问题的来源,但这是声明。假设我有两个非负整数列表,我会写(A[0] ... A[n])(B[0] ... B[m])。它们严格增加,所有A[i+1] > A[i] iB类似n * m。我希望按其总和的递增顺序收集所有A = (0 1 2)对元素。

所以,例如,如果B = (1 4)((0 1) (1 1) (2 1) (0 4) (1 4) (2 4)),那么我想最终收集A = (0 1)。如果存在平局,我不关心我收集这两个元素的顺序。例如,如果B = (0 1)(0 1),那么我不介意哪个混合术语,{{ 1}}或(1 0),我先拿起。

显然,我希望这是合理有效的。我希望及时渐渐可以m * n。具体来说,如果我对输入一无所知,我希望有序输入能使这个问题比同等问题更容易。当我第一次提出问题时,我在思考的是我们必须存储的状态量。我希望这可能是一个恒定的数额,但也许这是不现实的。 (我以后尝试过的都失败了!)

代码实际上是用Lisp编写的,但我认为问题陈述几乎与此无关。输入最自然地会作为单链接列表,但无论如何我都必须提前撤消它们,所以如果随机访问是相关的,我可以将它们作为数组。如果它是相关的,我希望这主要是在非常小的列表上调用,因此运行时的大量常量项/常数因子可能会排除解决方案。 (虽然我很想知道算法的想法!)

背景:我一直在查看Maxima的源代​​码,这是一个计算机代数系统,特别是它的代码,用于两个多项式的乘法。多项式以“稀疏格式”表示,因此x^5 + x^2 + 2可能显示为(5 1 2 1 0 2),下降指数后跟各自的系数。为了有效地计算产品,我真正想做的是收集度零度项,然后是1度项等等。当前的代码通过对其进行半心半意的刺激以提高效率来避免解决这个问题,然后做一个一类通用多项式加法,以按照它不期望的顺序处理系数。我觉得我们应该能做得更好!

7 个答案:

答案 0 :(得分:9)

这个问题只是表面上与排序 X + Y 不同,这是多年来对计算几何学的主要刺激因素。 (参见Joseph O’Rourke’s (open) Problem 41。)为了总结实施者的链接,当只能添加和比较指数时,最快的已知算法是O(m n log(m n)),这是显而易见的算法。如果指数是有界整数,那么彼得的傅立叶方法适用。许多聪明的人已经考虑过这个问题很长一段时间了,所以我不希望很快就会有更好的算法。

答案 1 :(得分:6)

我想知道你的多项式有多稀疏?

对于密集多项式的乘法而言,值得考虑的一个选项是计算多项式的傅立叶变换并将它们的傅里叶系数相乘。

这允许你在O(nlogn)中乘以多项式,其中n是多项式的次数。

这不适用于稀疏多项式,例如(1 + x ^ 1000000)*(1 + x ^ 1000000),因为n大约为1000000,而当前算法只需要几个周期。

这种傅立叶方法的一个很好的解释可以在these lecture notes中找到。

答案 2 :(得分:5)

就我的理解而言,由于以下逻辑,您甚至无法希望找到复杂度为O(N*M)的解决方案。

说数组是(a1, a2, a3, a4)(b1, b2, b3, b4, b5)

可能的对应如下:

a1b1 a1b2 a1b3 a1b4 a1b5
     a2b1 a2b2 a2b3 a2b4 a2b5
          a3b1 a3b2 a3b3 a3b4 a3b5
               a4b1 a4b2 a4b3 a4b4 a4b5

现在对于每一行,左边的对总是在右边的对之前被拾取。

  1. 所以第一位候选人是a1b1
  2. 然后选择a1b1后,下一位候选人为a1b2a2b1
  3. a1b2被选中。然后候选人是a1b3a2b1
  4. 现在说a2b1被选中了。然后候选人是a1b3a2b2a3b1
  5. 因此,我们看到随着我们前进,该职位的候选人数量呈线性增长。

    因此,在最坏的情况下,您必须对N*M*M的顺序进行比较。

    O(N*Mlog(M*N))方法。 (where N = a.size() and M = b.size())

    for ( int i = 0; i < a.size(); i++ )
      for ( int j = 0; j < b.size(); j++ )
        sumVector.push_back( a[i] + b[j] );
    
    sort( sumVector ); // using merge sort or quicksort.
    

答案 3 :(得分:4)

  

我希望它只能存储一定量的状态,并且只能遍历每个列表一次。

我认为这是不可能的。我无法提供严格的数学证明,但请考虑一下:您的问题有两个部分:

1)生成A和B中的所有对。 2)确保它们按增加的顺序排列。

让我们放下第二部分,让它变得更容易。最简单的实现方法是

foreach a in A:
  foreach b in B:
    emit (a, b)

我希望你会同意没有更有效的方法来做到这一点。但是在这里我们已经迭代了B长度(A)次。

所以最小的时间复杂度是O(A * B),但你想要O(A + B)。

答案 4 :(得分:3)

this question中所述,使用算法在排序数组中找到一对与特定常量K相加的数字非常简单。

摘要

最终的解决方案是时间复杂度O((M+N)*(M+N)),最多比M*N的下限差4倍(只输出所有对)。所以常数因子只有4。

修改:哎呀,我认为它不是O((M+N)*(M+N)),显然,它是O(K*(M+N)),其中K是两个数组中的最高总和。我不确定这个算法是否可以改进,但似乎该解决方案类似于Peter de Rivaz描述的快速傅里叶变换方法。

排序数组算法中的和

在该算法中,我们将较低的指针设置为0,将较高的指针设置为数组的末尾。然后,如果这两个位置的总和大于K,我们减少更高的指针。如果它更低,我们增加下指针。这是有效的,因为在任何迭代中,arr[low]是迄今为止可以派生答案的最低元素。同样,arr[high]是最高的。因此,如果我们采用最低和最高元素,并且总和大于K,我们就会知道arr[high]的任何其他组合都会大于K。所以arr[high]不能成为任何解决方案的一部分。因此,我们可以从数组中删除它(这可以通过减少high指针来实现。)

将其应用于您的问题

将此问题应用于您的问题,我们的想法是,我们迭代可能的总和,即从0到A[len(A)-1]+B[len(B)-1]。对于每个可能的总和,我们运行上述算法。对于您的问题,我们将较低的指针设置为数组A,将较高的指针指向数组B

对于原始算法,一旦找到与常量相加的对,它就会中断。对于您的问题,您将增加ptr_A并将每个ptr_B减少1.这是因为您的数组严格增加。因此,如果我们找到A[ptr_A]+B[ptr_B]==K,则所有A[ptr_A]+B[low_B]<K的所有low_B<ptr_BA[high_A]+B[ptr_B]>K都会high_A>ptr_A

这会找到所有对,因为对于每个可能的总和K,它会找到总和为K的所有对,并且我们迭代所有可能的总和K

作为奖励,此算法将根据列表A 中的增加值对输出进行排序(您可以通过交换指针,根据列表B中的值增加进行排序),我们不需要随机访问到数组。

Python中的代码

在Python中实现:

def pair_sum(A,B):
    result = []
    max_sum = A[-1]+B[-1]
    for cur_sum in range(max_sum+1): # Iterate over all possible sum
        ptr_A = 0        # Lower pointer
        ptr_B = len(B)-1 # Higher pointer
        while True:
            if A[ptr_A]+B[ptr_B]>cur_sum:
                ptr_B -= 1
            elif A[ptr_A]+B[ptr_B]<cur_sum:
                ptr_A += 1
            else:
                result.append((A[ptr_A],B[ptr_B]))
                ptr_A += 1
                ptr_B -= 1
            if ptr_A==len(A):
                break
            if ptr_B==-1:
                break
    return result

def main():
    print pair_sum([0,1,2],[1,4])
    print pair_sum([0,1],[0,1])
    print pair_sum([0,1,3],[1,2])

if __name__=='__main__':
    main()

将打印:

[(0, 1), (1, 1), (2, 1), (0, 4), (1, 4), (2, 4)]
[(0, 0), (0, 1), (1, 0), (1, 1)]
[(0, 1), (0, 2), (1, 1), (1, 2), (3, 1), (3, 2)]

根据需要。

答案 5 :(得分:3)

所以让我们拿两个'scares数组's和sB,它只包含原始帖子中描述的非零度/系数数字。
例如:

A   =  x^5 + 3*x^2 + 4
sA  = [ 5, 1, 2, 3, 0, 4 ]

B = 2*x^6 + 5*x^3 + 8*x
sB = [ 6, 2, 3, 5, 1, 8]

我建议的是按照我们手工操作的方式进行操作,因此它们需要m * n的时间,其中m,n是非空系数的数量,而不是p * q,其中p和q是A,B的度数。
既然你说m和n都很小,那么m * n就没什么大不了的了 要在计算时存储系数,请使用稀疏数组(可能很昂贵)或哈希表。索引或键是度,而值是相应的系数 这里是一个使用哈希表在javascript中实现的例子:

http://jsbin.com/ITIgokiJ/2/edit?js,console

一个例子:

A     =   "x^5+3x^2+4"
B     =   "2x^6+5x^3+8x^1"
A * B =   "2x^11+11x^8+16x^6+15x^5+44x^3+32x^1"

代码:

function productEncodedPolynoms( sA, sB) {
   var aIndex = 0 ;
   var bIndex = 0 ;
   var resIndex = 0 ;
   var resHash = {} ;
   // for loop within sA, moving 2 items at a time
   for (aIndex = 0; aIndex < sA.length ; aIndex+=2) {
       // for loop within sB, moving 2 items at a time
       for (bIndex = 0; bIndex < sB.length ; bIndex+=2 ) {
           resIndex = sA[aIndex]+sB[bIndex] ;
           // create key/value pair if none created
           if (resHash[resIndex]===undefined)  resHash[resIndex]=0;
           // add this product to right coefficient
           resHash[resIndex] += sA[aIndex+1]*sB[bIndex+1];
       }
   } 
   // now unpack the hash into an encoded sparse array
   // get hash keys
   var coeff = Object.keys(resHash);
   // sort keys in reverse order
   coeff.sort(reverseSort);
   encodedResult = [];
   for (var i=0; i<coeff.length; i++ ) {
     if (resHash[coeff[i]]) {
       encodedResult.push(+coeff[i]);          // (+ converts to int)
       encodedResult.push(+resHash[coeff[i]]);
     }
   }
   return encodedResult;
}

示例:

sA = [ 5, 1, 2, 3, 0, 4 ] ;

sB = [ 6, 2, 3, 5, 1, 8] ;

sAB = productEncodedPolynoms ( sA, sB );

打印结果的实用程序:

function printEncodedArray(sA) {
  res='';
  for (var i=0; i<sA.length; i+=2) {
    if (sA[i+1]) {
        if (sA[i+1] != 1 || sA[i]==0) res+=sA[i+1];
        if (sA[i]!=0) res+='x^'+sA[i];
        if (i!=sA.length-2) res+='+';
    }
  }
  return res;
}

// utilities
function reverseSort(a,b) { return b-a ; }

答案 6 :(得分:2)

为什么不生成未排序的二维数组然后使用快速排序?

ED:看起来如果您在边缘智能地生成最终数组(在初始迭代和生成数组期间使用比较算法),您可以稍后使用更具体的排序算法(例如smoothsort)和最终性能接近O(2(m * n)),最坏情况为O(m * n +(m * n)log(m * n))

ED2:我绝对相信你可以在O(m * n)中编写一个算法来做到这一点。 你想要做的是干净地生成阵列。我没有时间编写伪代码,但如果你这样做的话 1.为每个数组设置minimum-iterator,maximum-iterator和current-iterator,以及它们的current-max-sum。 2.生成第一对 3.迭代一个变量,将其与另一个可能的第三对进行比较,(将其保存在临时变量中)并将其保存到输出中或将另一个数组保存到输出中并重新开始。

我想象它的方式是两排具有三种颜色的块:红色表示完全完成,蓝色表示“迄今为止最高”和未完成。您一次处理其中一行块,生成结果数组的下一个值,始终比较当前一侧的基线组合低于当前处理的总和。每次总和较低时,将新的最低结果设置为刚刚计算的结果,将先前最低的结果添加到(已排序的)输出数组,并使用前一个低(及其行)作为基线开始相加。永远不要回到红色区块,并且每当你找到一个新的对侧低点时,哪一侧是蓝色的。

你永远不应该计算两次单个结果,并提出一个排序数组。

它本质上是一种浮顶式冒泡。您知道计算出的值高于当前总和,直到不处理数组中的低位寄存器为止。如果不再高,则将顶部值向上切换,然后返回到迭代成员当前较小的数组的位置。 EX:

A:(1 5 10)

B:(1 6 9)

(1,1) - &gt; (1,6)高于(1,5)所以加(1,5)使(1,6)最高

(5,6)高于(1,6)所以加(1,6)使(5,6)最高移动到(1,9)

(1,9)低于(5,6)所以[添加(1,9)并在A上完成增加]

当前数组:(1,1),(1,5),(1,6),(1,9)

(1,10)等于(5,6)所以加两者,增加完成B,以(5,9)开始

(5,9)高于(10,1)所以加(10,1)使(5,9)最高移动到(10,6)

(10,6)高于(5,9)所以......

反正。如果你设置正确,你只需要对最终数组进行一次比较,然后在不分支的情况下即可构建它。如果你不需要在任何地方的内存中的最终数组,FFT可能会更快,但这应该工作。