簡體   English   中英

以遞增的順序迭代成對的數字

[英]Iterating over pairs of numbers in an increasing order

我將在底部解釋問題的來源,但這是聲明。 假設我有兩個非負整數列表,我將寫入(A[0] ... A[n])(B[0] ... B[m]) 它們嚴格增加,因此A[i+1] > A[i]適用於所有i ,類似地適用於B 我想按照它們總和的遞增順序收集所有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 0) ,我先拿起。

顯然,我希望這是合理有效的。 我希望有時候m * n可以漸近漸近。 具體來說,如果我對輸入一無所知,我希望有序輸入能使這個問題比同等問題更容易。 當我第一次提出問題時,我在思考的是我們必須存儲的狀態量。 我希望這可能是一個恆定的數額,但也許這是不現實的。 (自那以后我嘗試過的都失敗了!)

代碼實際上是用Lisp編寫的,但我認為問題陳述幾乎與它無關。 輸入最自然地會作為單鏈接列表,但無論如何我都必須提前撤消它們,所以如果隨機訪問是相關的,我可以將它們作為數組。 如果它是相關的,我希望這主要是在非常小的列表上調用,因此運行時的大量常量項/常數因子可能會排除解決方案。 (雖然我很想知道算法的想法!)

背景:我一直在查看Maxima的源代​​碼,這是一個計算機代數系統,特別是它的代碼,用於兩個多項式的乘法。 多項式以“稀疏格式”表示,因此x^5 + x^2 + 2可能表現為(5 1 2 1 0 2) ,下降指數后跟其各自的系數。 為了有效地計算產品,我真正想做的是收集度零度項,然后是1度項等等。當前的代碼通過對其進行半心半意的刺激以提高效率來避免解決這個問題,然后做一個一類通用多項式加法,以按照它不期望的順序處理系數。 我覺得我們應該做得更好!

這個問題只是表面上與排序X + Y不同 ,這是多年來對計算幾何學的主要刺激因素。 (參見Joseph O'Rourke的(開放)問題41。 )總結實施者的鏈接,只能添加和比較指數的最快的已知算法是O(mn log(mn)),這是顯而易見的算法。 如果指數是有界整數,那么彼得的傅立葉方法適用。 很多聰明的人已經考慮過這個問題很長一段時間了,所以我不希望很快就會有更好的算法。

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

對於密集多項式的乘法而言可能值得考慮的一個選項是計算多項式的傅立葉變換並將它們的傅里葉系數相乘。

這允許您在O(nlogn)中乘以多項式,其中n是多項式的次數。

這對於諸如(1 + x ^ 1000000)*(1 + x ^ 1000000)的稀疏多項式是不合適的,因為n將是大約1000000,而當前算法將僅需要幾個周期。

這些講義中可以找到這種傅立葉方法的一個很好的解釋。

據我所知,由於以下邏輯,你甚至不能指望復雜度為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

因此,我們看到隨着我們前進,該職位的候選人數量呈線性增長。

因此,在最壞的情況下,您將不得不對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.

我希望它可以只存儲一定量的狀態,並且只迭代每個列表一次。

我認為這是不可能的。 我無法提供嚴格的數學證明,但請考慮一下:您的問題有兩個部分:

1)生成A和B中的所有對.2)確保它們按增加的總和順序排列。

讓我們放下第二部分,使其更容易。 實現這一目標的最簡單方法是

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

我希望你會同意沒有更有效的方法來做到這一點。 但是在這里我們已經迭代了B長度(A)次。

所以最小的時間復雜度是O(A * B),但你想要O(A + B)。

本問題所述,使用算法在排序數組中找到一對數和一個特定常數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)]

如預期的。

因此,讓我們采用兩個'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 ; }

你為什么不生成未排序的二維數組然后使用快速排序呢?

\n

ED:看起來如果您稍微智能地生成最終數組(在初始迭代和數組生成期間使用比較算法),您可以稍后使用更具體的排序算法(例如smoothsort)並最終獲得接近O(2(m)的性能* n))最壞情況下的O(m * n +(m * n)log(m * n))

ED2:我絕對肯定你可以在O(m * n)中編寫一個算法來做到這一點。 你想要做的是干凈地生成陣列。 我沒有時間編寫偽代碼,但是如果你要1.為每個數組設置最小迭代器,最大迭代器和當前迭代器,以及它們的當前最大值和。 2.生成第一對3.迭代一個變量,將其與另一個可能的第三對進行比較(將其保存在臨時變量中)並將其保存到輸出或將另一個數組保存到輸出並重新開始。

我想象它的方式是兩排具有三種顏色的塊:紅色表示完全完成,藍色表示“迄今為止最高”和未完成。 您一次處理其中一行塊,生成結果數組的下一個值,始終比較當前一側的基線組合低於當前處理的總和。 每次總和較低時,將新的最低結果設置為剛剛計算的結果,將先前最低的結果添加到(已排序的)輸出數組,並使用前一個低(及其行)作為基線開始相加。 永遠不要回到紅色區塊,並且每當你發現一個新的對側低點時,哪一側是藍色的。

你永遠不應該計算兩次單個結果,並提出一個排序數組。

它本質上是一種浮頂式冒泡。 您知道計算出的值高於當前總和,直到不處理數組中的低位寄存器為止。 如果不再高,則將頂部值向上切換,然后返回到迭代成員當前較小的數組的位置。 EX:

答:(1 5 10)

B:(1 6 9)

(1,1) - >(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可能會更快,但這應該工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM