İki dizi verildiğinde, birinin sonu ile diğerinin başlangıcı arasındaki maksimum çakışma bulun


11

Aşağıdaki sorunu çözmek için verimli (sözde) bir kod bulmak gerekir:

İki (mutlaka farklı değil) tamsayılar dizileri göz önüne alındığında (a[1], a[2], ..., a[n])ve (b[1], b[2], ..., b[n])maksimum bulmak döyle a[n-d+1] == b[1], a[n-d+2] == b[2], ..., ve a[n] == b[d].

Bu ev ödevi değil, aslında iki tensörü mümkün olduğunca çok boyut boyunca daraltmaya çalışırken ortaya çıktım. Etkili bir algoritmanın varlığından şüpheleniyorum (belki O(n)?), Ama olmayan bir şey bulamıyorum O(n^2). O(n^2)Yaklaşım üzerinde bariz döngü olacaktır dve daha sonra öğeleri bir iç döngü maksimum isabet kadar gerekli durumunu kontrol etmek d. Ama bundan daha iyi bir şey olduğundan şüpheleniyorum.


Dizinizdeki nesne grubu için bir haddeleme karma hesaplanabilirse, bunun daha verimli yapılabileceğini düşünüyorum. Elemanlar için karma değerini hesaplayın b[1] to b[d]ve sonra aeşleşirse dizi hesaplama karma değerine gidin, a[1] to a[d]o zaman bu sizin cevabınızdır, eğer hesaplanan karma değerini a[2] to a[d+1]yeniden kullanarak hesaplama karma değeri değilse a[1] to a[d]. Ama dizideki nesnelerin, üzerinde bir haddeleme karma hesaplamak için uygun olup olmadığını bilmiyorum.
SomeDude

2
@becko Üzgünüm, sonunda neyi başarmaya çalıştığınızı anlıyorum. Hangi sonu arasında maksimum çakışmasını bulmaktır abaşlangıcı ile b. Bunun gibi .
user3386109

1
Bana göre problem, Knuth-Morris-Pratt algoritmasındaki bir varyasyon ile çözülebilen dize eşleşmesinde bir varyasyon . İşletim süresi olacaktır O (m + n), burada melemanların sayısı ave nelemanların sayısı b. Ne yazık ki, KMP ile nasıl adapte edeceğinizi söylemek için yeterli deneyimim yok.
user3386109

1
@ user3386109 Benim çözümüm de , Horner yöntemini hash işlevi olarak kullanan Rabin-Karp adlı dize eşleme algoritmasının bir varyasyonudur .
Daniel

1
@Daniel Ah, bir yerde bir haddeleme hash gördüğümü biliyordum, ama nerede hatırlayamıyorum :)
user3386109

Yanıtlar:


5

Aşağıdakileri yapan doğrusal bir zaman ( O (n) ) algoritması olan z algoritmasını kullanabilirsiniz :

Bir dizi verilen S uzunluğu n, Z algoritması bir dizi üreten Z , Z [i] uzun başlayarak substring uzunluğudur [i] S , aynı zamanda, bir önek olan S

(Lütfen dizilerini birleştirmek için gereken B + bir ) ve birinci kadar elde edilen inşa dizi algoritma çalıştırmak I öyle ki Z [i] + i == m + n .

Örneğin, a = [1, 2, 3, 6, 2, 3] & b = [2, 3, 6, 2, 1, 0] için birleştirme [2, 3, 6, 2, 1 olacaktır. Z, [1] = 2, Z [i] + i = 12 = m + n'yi sağlayan Z [10] = 2 verecek, 0, 1, 2, 3, 6, 2, 3] .


Güzel! Teşekkürler.
becko

3

O (n) zaman / uzay karmaşıklığı için, püf noktası her bir sonraki işlem için karmaları değerlendirmektir. Diziyi düşünün b:

[b1 b2 b3 ... bn]

Horner yöntemini kullanarak , her bir takip için olası tüm karmaları değerlendirebilirsiniz. Bir taban değeri seçin B(her iki dizinizdeki herhangi bir değerden daha büyük):

from b1 to b1 = b1 * B^1
from b1 to b2 = b1 * B^1 + b2 * B^2
from b1 to b3 = b1 * B^1 + b2 * B^2 + b3 * B^3
...
from b1 to bn = b1 * B^1 + b2 * B^2 + b3 * B^3 + ... + bn * B^n

Her sekansı, önceki sekansın sonucunu kullanarak O (1) zamanında değerlendirebildiğinizi, dolayısıyla tüm iş masraflarını O (n) değerlendirebileceğinizi unutmayın.

Şimdi bir diziniz var Hb = [h(b1), h(b2), ... , h(bn)], burası Hb[i]hash b1olana kadar bi.

Dizi için aynı şeyi yapın a, ancak küçük bir hile ile:

from an to an   =  (an   * B^1)
from an-1 to an =  (an-1 * B^1) + (an * B^2)
from an-2 to an =  (an-2 * B^1) + (an-1 * B^2) + (an * B^3)
...
from a1 to an   =  (a1   * B^1) + (a2 * B^2)   + (a3 * B^3) + ... + (an * B^n)

Bir diziden diğerine adım attığınızda, önceki dizinin tamamını B ile çarptığınızı ve yeni değeri B ile çarptığınızı not etmelisiniz. Örneğin:

from an to an =    (an   * B^1)

for the next sequence, multiply the previous by B: (an * B^1) * B = (an * B^2)
now sum with the new value multiplied by B: (an-1 * B^1) + (an * B^2) 
hence:

from an-1 to an =  (an-1 * B^1) + (an * B^2)

Şimdi bir diziniz var Ha = [h(an), h(an-1), ... , h(a1)], burası Ha[i]hash aiolana kadar an.

Şimdi, n'den 1'e kadar Ha[d] == Hb[d]tüm ddeğerleri karşılaştırabilirsiniz , eğer eşleşirse, cevabınız vardır.


DİKKAT : Bu bir karma yöntemdir, değerler büyük olabilir ve hızlı bir üs alma yöntemi ve modüler aritmetik kullanmanız gerekebilir, bu da size (neredeyse) çarpışmalar verebilir , bu yöntemi tamamen güvenli hale getirmez. İyi bir uygulama, Bgerçekten büyük bir asal sayı olarak bir taban seçmektir (en azından dizilerinizdeki en büyük değerden daha büyük). Ayrıca, her adımda sayıların sınırları taşabileceğinden de dikkatli olmalısınız, bu nedenle Kher işlemde ( modulo ) kullanmanız gerekir (burada Kasal daha büyük olabilir B).

Bu, iki farklı sekansın aynı hash'a sahip olabileceği , ancak iki eşit sekansın her zaman aynı hash'a sahip olacağı anlamına gelir .


Bu cevaba lütfen kaynak gereksinimlerinin bir değerlendirmesiyle başlayabilir misiniz?
greybeard

2

Bu gerçekten doğrusal zaman, O (n) ve O (n) fazladan uzayda yapılabilir. Giriş dizilerinin karakter dizeleri olduğunu varsayacağım, ama bu gerekli değil.

Saf bir yöntem olurdu - eşleşen sonra k eşit karakterleri - eşleşmeyen bir karakter bulmak ve geri dönmek k-1 birimleri bir , içinde dizini sıfırlamak b ve sonra oradan eşleştirme işlemini başlatın. Bu açıkça O (n²) en kötü durumu temsil eder .

Bu geri izleme işleminden kaçınmak için, son k-1 karakterlerini tararken b [0] karakteriyle karşılaşmazsak geri dönmenin yararlı olmadığını gözlemleyebiliriz . Biz ise yaptığımız o karakteri olması bakımından özellikle eğer, o zaman bu pozisyona geriye sadece yararlı olacağını k alt dize büyüklüğünde bir periyodik tekrarını vardı.

Örneğin, içinde yerde "abcabc'ait" alt dize bakarsak bir ve b "abcabd", ve biz son karakteri bulmak b uymuyor, biz başarılı bir maç ikinci "a" de başlayabileceğini de göz önüne almalıyız ve karşılaştırmaya devam etmeden önce mevcut dizinimizi b olarak geri taşımalıyız .

Buradaki fikir, uyumsuzluk olduğunda kontrol etmek için yararlı olan b'deki referansları günlüğe kaydetmek için b dizesini temel alan bazı ön işlemler yapmaktır . Örneğin, eğer b "acaacaacd" ise, bu 0 tabanlı geri başvuruları belirleyebiliriz (her karakterin altına konur):

index: 0 1 2 3 4 5 6 7 8
b:     a c a a c a a c d
ref:   0 0 0 1 0 0 1 0 5

Elimizdeki Örneğin, bir eşit "acaacaaca" ilk uyuşmazlığı nihai karakteri olur. Yukarıdaki bilgiler daha sonra geri dönmek algoritma söyler b "acaac" beri, endeks 5 yaygındır. Ve sonra sadece mevcut endeksi değişen b biz mevcut endeksinde eşleştirme devam edebilir a . Bu örnekte, son karakterin eşleşmesi başarılı olur.

Bununla, arama optimize etmek ve dizin emin olabilirsiniz bir zaman öne ilerleyebilir.

JavaScript'te bu fikrin yalnızca o dilin en temel sözdizimini kullanan bir uygulaması:

function overlapCount(a, b) {
    // Deal with cases where the strings differ in length
    let startA = 0;
    if (a.length > b.length) startA = a.length - b.length;
    let endB = b.length;
    if (a.length < b.length) endB = a.length;
    // Create a back-reference for each index
    //   that should be followed in case of a mismatch.
    //   We only need B to make these references:
    let map = Array(endB);
    let k = 0; // Index that lags behind j
    map[0] = 0;
    for (let j = 1; j < endB; j++) {
        if (b[j] == b[k]) {
            map[j] = map[k]; // skip over the same character (optional optimisation)
        } else {
            map[j] = k;
        }
        while (k > 0 && b[j] != b[k]) k = map[k]; 
        if (b[j] == b[k]) k++;
    }
    // Phase 2: use these references while iterating over A
    k = 0;
    for (let i = startA; i < a.length; i++) {
        while (k > 0 && a[i] != b[k]) k = map[k];
        if (a[i] == b[k]) k++;
    }
    return k;
}

console.log(overlapCount("ababaaaabaabab", "abaababaaz")); // 7

İç içe whiledöngüler olmasına rağmen , bunların toplamda n'den daha fazla yineleme yoktur . Çünkü k değeri whilevücutta kesinlikle azalır ve negatif olamaz. Bu, ancak k++bu tür azalmalar için yeterli alan vermek için birçok kez idam edildiğinde gerçekleşebilir . Sonuçta, whilevücudun gerçekleştirilenden daha fazla infazı olamazk++ ve ikincisi açıkça O (n) 'dir.

Tamamlamak için, burada yukarıdaki ile aynı kodu bulabilirsiniz, ancak etkileşimli bir snippet'te: kendi dizelerinizi girebilir ve sonucu etkileşimli olarak görebilirsiniz:

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.