Dizeler arasındaki farkları hızla bulmak için veri yapısı veya algoritma


19

Tüm uzunluğu 100.000 dizeleri bir dizi var . İki dize 1 karakter farklı olup olmadığını görmek için her dize diğer dize karşılaştırmak istiyorum. Şu anda, dizeye her dizeyi eklerken, dizinin zaten içinde bulunan ve zaman karmaşıklığına sahip her dizeye karşı kontrol ediyorum .kn(n1)2k

Halihazırda yaptığımdan daha hızlı bir şekilde dizeleri karşılaştırabilen bir veri yapısı veya algoritma var mı?

Bazı ek bilgiler:

  • Sipariş hususlar: abcdeve xbcdeise 1 karaktere farklılık abcdeve edcba4 karakterden tarafından farklıdır.

  • Bir karakterle farklılık gösteren dizelerin her çifti için, dizelerden birini diziden kaldıracağım.

  • Şu anda, sadece 1 karakterle farklılık gösteren dizeler arıyorum, ancak bu 1 karakter farkı, örneğin 2, 3 veya 4 karaktere yükseltilebilseydi iyi olurdu. Ancak, bu durumda, verimliliğin karakter farkı sınırını arttırma yeteneğinden daha önemli olduğunu düşünüyorum.

  • k genellikle 20-40 aralığındadır.


4
1 hatayla dize sözlüğü aramak oldukça iyi bilinen bir sorundur, örneğin cs.nyu.edu/~adi/CGL04.pdf
KWillets

1
20-40 metre oldukça az yer kaplayabilir. Dejenere dizelerin - bir test mercindeki bir, iki veya daha fazla değişiklikten oluşan tüm bir dizi - kesinlikle "belki" veya "olup olmadığını test etmek için bir Bloom filtresine ( en.wikipedia.org/wiki/Bloom_filter ) bakabilirsiniz. -not-in "bir dizi kmers. Bir "belki" alırsanız, yanlış bir pozitif olup olmadığını belirlemek için iki dizeyi de karşılaştırın. "Kesinlikle olmayan" durumlar, karşılaştırmaları yalnızca potansiyel "belki" isabetlerle sınırlandırarak, yapmanız gereken toplam harf sayısı karşılaştırmasını azaltacak gerçek negatiflerdir.
Alex Reynolds

Daha küçük bir k aralığıyla çalışıyorsanız, tüm dejenere dizeler için karma booleans tablosunu saklamak için bir bit seti kullanabilirsiniz (örneğin , oyuncak örneği için github.com/alexpreynolds/kmer-boolean ). Bununla birlikte, k = 20-40 için, bir bit kümesi için alan gereksinimleri çok fazladır.
Alex Reynolds

Yanıtlar:


12

en kötü çalışma süresini elde etmek mümkündür .O(nklogk)

Basit başlayalım. Birçok girdide verimli olacak, ancak hepsi değil, uygulanması kolay bir çözüm umursanız, burada birçok durumda uygulamada yeterli olan basit, pragmatik, uygulaması kolay bir çözüm var. Yine de, en kötü durumda ikinci dereceden çalışma süresine geri dönüyor.

Her dizeyi alın ve dizenin ilk yarısında anahtarlanmış bir hashtable içinde saklayın. Sonra, hashtable kovaları tekrarlayın. Aynı gruptaki her bir dizgi çifti için 1 karakterde farklılık gösterip göstermediklerini kontrol edin (yani, ikinci yarılarının 1 karakterde farklılık gösterip göstermediğini kontrol edin).

Daha sonra, her bir dizeyi alın ve bir hashtable içinde saklayın, bu kez dizenin ikinci yarısında anahtarlanır . Yine aynı kovadaki her tel dizesini kontrol edin.

Dizelerin iyi dağıtıldığı varsayılarak, çalışma süresi muhtemelen . Ayrıca, 1 karakterden farklı bir çift dize varsa, iki geçişten birinde bulunur (yalnızca 1 karakterle farklılık gösterdiğinden, bu farklı karakter dizenin ilk veya ikinci yarısında olmalıdır, böylece dizenin ikinci veya ilk yarısı aynı olmalıdır). Bununla birlikte, en kötü durumda (örneğin, tüm dizeler aynı k / 2 karakterle başlıyor veya bitiyorsa), bu O ( n 2 k ) çalışma süresine düşer, bu nedenle en kötü çalışma süresi kaba kuvvet üzerinde bir iyileştirme değildir .O(nk)k/2O(n2k)

Bir performans optimizasyonu olarak, herhangi bir kovada çok fazla dize varsa, bir karakterden farklı bir çift aramak için aynı işlemi yinelemeli olarak tekrarlayabilirsiniz. Özyinelemeli çağırma, uzunluktaki dizelerde olacaktır .k/2

En kötü çalışma süresine önem veriyorsanız:

Yukarıdaki performans optimizasyonu ile en kötü çalışma süresinin olduğuna inanıyorum .O(nklogk)


3
Eğer dizgileri aynı ilk yarıyı paylaşıyorsa, bu gerçek hayatta çok iyi olabilir, o zaman karmaşıklığı geliştirmediniz. Ω(n)
einpoklum

@einpoklum, tabi! Bu yüzden, ikinci cümlemde en kötü durumda ikinci dereceden çalışma süresine geri döndüğünü ve son cümlemde en kötü durum karmaşıklığına nasıl ulaşacağınızı açıklayan ifadeyi yazdım. en kötü durum hakkında. Ama sanırım belki bunu çok açık bir şekilde ifade etmedim - bu yüzden cevabımı buna göre düzenledim. Şimdi daha iyi? O(nklogk)
DW

15

Benim çözüm j_random_hacker benzer ama sadece tek bir karma küme kullanır.

Bir karma dizeleri oluştururdum. Girişteki her dize için, ayarlanan dizelerine ekleyin . Bu dizelerin her birinde, harflerden birini, dizelerden hiçbirinde bulunmayan özel bir karakterle değiştirin. Bunları eklerken, kümede bulunmadığından emin olun. Eğer öyleyse, sadece bir karakterle (en fazla) farklı olan iki dizeniz vardır.k

'Abc', 'adc' dizeli bir örnek

Abc için '* bc', 'a * c' ve 'ab *' ekliyoruz

Adc için '* dc', 'a * c' ve 'ad *' ekliyoruz

"A * c" yi ikinci kez eklediğimizde, kümede olduğunu fark ettiğimizde, yalnızca bir harfe göre farklılık gösteren iki dize olduğunu biliyoruz.

Bu algoritma, toplam çalışma süresi olan . Bunun nedeni , girişteki tüm n dizeler için k yeni dizeler oluşturmamızdır . Bu dizelerin her biri için, genellikle O ( k ) zaman alan karma değerini hesaplamamız gerekir .O(nk2)knO(k)

Tüm dizelerin saklanması alanı kaplar.O(nk2)

Diğer iyileştirmeler

Değiştirilmiş dizeleri doğrudan saklamak yerine, orijinal nesneyi ve maskelenen karakterin dizinini referans alan bir nesneyi depolayarak algoritmayı daha da geliştirebiliriz. Bu şekilde tüm dizeleri yaratmamıza ve tüm nesneleri saklamak için sadece boşluğuna ihtiyacımız var.O(nk)

Nesneler için özel bir karma işlevi uygulamanız gerekir. Java uygulamasını örnek olarak alabiliriz , java belgelerine bakın . Java hashCode, her karakterin unicode değerini ( k dize uzunluğu ve i karakterin tek tabanlı dizini ile) çarpar . bu karakterin karma koduna katkısıdır. Bunu çıkarabilir ve maskeleme karakterimizi ekleyebiliriz.Bu hesaplamak için O ( 1 ) alır.Bu toplam çalışma süresini O ( n) ' a getirmemizi sağlar.31kikiO(1)O(nk)


4
@JollyJoker Evet, uzay bu yöntemle ilgili bir şey. Değiştirilmiş dizeleri saklamak yerine, dizeye ve maskelenmiş dizine referansla bir nesneyi depolayarak alanı azaltabilirsiniz. Bu sizi O (nk) boşluğuyla bırakmalıdır.
Simon Prins

Hesaplamak için her dize için karmaları Ç ( k ) zamanında, sana özel bir ev yapımı karma işlevi (örneğin, orijinal dize karma hesaplamak gerekir düşünüyorum Ey ( k ) XOR onu silmiş her biri ile daha sonra, zaman karakterler O ( 1 ) zaman her (bu muhtemelen başka yollarla oldukça kötü bir hash fonksiyonu olsa)). BTW, bu benim çözümüme oldukça benziyor, ancak k ayrı olanlar yerine tek bir hashtable ve bir karakteri silmek yerine "*" ile değiştiriyor. kO(k)O(k)O(1)k
j_random_hacker

@SimonPrins Çalışabilecek özel equalsve hashCodeyöntemlerle. Bu yöntemlerde sadece a * b tarzı bir dize oluşturmak onu kurşun geçirmez yapmalıdır; Buradaki diğer cevapların bazılarının çarpışma problemleri olduğundan şüpheleniyorum.
JollyJoker

1
@DW Mesajımı, karmaları hesaplamanın zaman alacağı gerçeğini yansıtacak şekilde değiştirdim ve toplam çalışma süresini O ( n k ) değerine geri getirmek için bir çözüm ekledim . O(k)O(nk)
Simon Prins

1
@SimonPrins Hashset.'de String eşitliği denetimi nedeniyle en kötü durum nk ^ 2 olabilir . Hashh çarpıştığında içerir. Her dize için aynı karma almak için özellikle, dizeleri bir hoş çok el işi seti gerektirecektir aynı kesin karma, sahip olduğunda Tabii ki, en kötü durum olduğunu *bc, a*c, ab*. Merak ediyorum imkansız gösterilebilecek mi?
JollyJoker

7

Ben yapacak karışık tablolar H 1 , ... , H k bir sahip, her biri, ( k - 1 ) anahtar olarak uzunlukta dize ve değer olarak numaralar (dize kimlikleri) bir listesini. Hashtable'a H ı tüm dizeleri içerecektir Şu ana kadar işleme ancak pozisyonda karakteriyle i silindi . Örneğin, k = 6 ise , H 3 [ A B D E F ] şu ana kadar A desenine sahip olan tüm dizelerin bir listesini içerecektirkH1,,Hk(k1)'Hbenbenk=6H3[ABDEF] , burada "herhangi bir karakter" anlamına gelir. Sonra j- girdi girdisini s j işlemek için:ABDEFjsj

  1. 1 ila k aralığındaki her için : ik
    • İ- th karakterini s j'den silerek dizesi oluşturun .sjisj
    • Bak . Buradaki her dize kimliği, s'ye eşit veya yalnızca i konumunda farklı olan orijinal bir dize tanımlar . Bunları, s j dizesi için eşleşme olarak çıktılar . (Tam kopyaları hariç tutmak istiyorsanız, hashtables değer türünü bir (dize kimliği, silinmiş karakter) çifti yapın, böylece s j'den sildiğimiz gibi aynı karakteri silmiş olanları test edebilirsiniz .)Hi[sj]sisjsj
    • Takın içine H i kullanımına gelecek sorguları için.jHi

Her karma anahtarını açık bir şekilde saklarsak, boşluğunu kullanmalı ve böylece en azından bu zaman karmaşıklığına sahip olmalıyız. Ancak Simon Prins tarafından tarif edildiği gibi, bir dizede bir dizi modifikasyonu (onun durumunda tek karakterleri , benimkilerde silme olarak değiştirmek olarak tanımlanır ) dolaylı olarak belirli bir dize için tüm k hash anahtarlarının ihtiyaç duyduğu şekilde temsil etmek mümkündür. O ( k ) alanı, giden , O ( n, k ) genel alan ve olasılığını açma O ( n, k )O(nk2)*kO(k)O(nk)O(nk)zaman da. Bu zaman karmaşıklığını elde etmek için , O ( k ) zamanında bir uzunluk k dizesinin tüm varyasyonları için hashları hesaplamak için bir yola ihtiyacımız var : örneğin, DW tarafından önerildiği gibi polinom karmaları kullanılarak yapılabilir (ve bu silinmiş karakteri orijinal dizenin karmasıyla XORing'den daha iyi olabilir).kkO(k)

Simon Prins'ın örtülü sunum hilesi aynı zamanda her karakterin "silinmesi" nin gerçekte gerçekleştirilmediği anlamına gelir, bu nedenle bir performans dizisi olmadan (aslında önerdiğim gibi bağlantılı listeler yerine) bir dizenin olağan dizi tabanlı temsilini kullanabiliriz.


2
Güzel çözüm. Ismarlama hash fonksiyonunun bir örneği, polinom hashidir.
DW

Thanks @DW "Polinom karma" ile ne demek istediğinizi biraz açıklığa kavuşturabilir misiniz? Terimi araştırmak bana kesin görünen hiçbir şey alamadı. (İsterseniz doğrudan benim yazı düzenlemek için çekinmeyin.)
j_random_hacker

1
Dizeyi temel numarası modulo p olarak okuyun , burada p hashmap boyutunuzdan biraz daha küçüktür ve q , p'nin ilkel köküdür ve q , alfabe boyutundan daha fazladır. Kimin katsayıları de dize tarafından verilen polinomu değerlendiren gibidir çünkü "polinom karma" denir q . O ( k ) zamanında istenen tüm karmaların nasıl hesaplanacağını bulmak için bir egzersiz olarak bırakacağım . İstediğiniz koşulları karşılayan rasgele p , q seçmediğiniz sürece, bu yaklaşımın bir düşmana karşı bağışık olmadığını unutmayın .qppqpqqO(k)p,q
user21820

1
Bence bu çözüm , her seferinde k hash tablolarından sadece birinin bulunması gerektiğini ve böylece bellek gereksinimini azaltarak daha da geliştirilebileceğini düşünüyorum.
Michael Kay

1
@MichaelKay: O ( k ) zamanında bir dizenin olası değişikliklerinin karmasını hesaplamak istiyorsanız bu işe yaramaz . Onları hala bir yerde saklamanız gerekiyor. Yalnızca bir seferde bir pozisyon kontrol ederseniz Yani, alacak k kullanmakta birlikte tüm pozisyonları kontrol ederseniz sürece kez k birçok hashtable'a girdileri gibi kez. kO(k)kk
user21820

2

İşte polinom hash yönteminden daha sağlam hashtable yaklaşımı. İlk olarak, M hashtabl boyutuna denk gelen rastgele pozitif tamsayılar r1 .. k üretin . Yani, 0 r i < M . Daha sonra her dizeyi x 1 .. k - ( k i = 1 x i r i ) mod M'ye hash edin . Bir rakibin çok düzensiz çarpışmalara neden olabileceği neredeyse hiçbir şey yoktur, çünkü çalışma zamanında r 1 .. k üretirsiniz ve böylece kkr1..kM0ri<Mx1..k(i=1kxiri)modMr1..kkbelirli dizgiler çiftinin maksimum çarpışma olasılığını hızlı bir şekilde . O ( k ) zamanında, bir karakter değiştiğinde her bir dize için tüm olası karmaları nasıl hesaplayacağınız da açıktır .1/MO(k)

Eğer gerçekten garanti üniforma Karma işlemi istiyorsanız, bir rasgele doğal sayı üretebilirsiniz az M her çifti için ( i , c ) için i den 1'e kadar k ve her karakter için c her dize karma sonra ve x 1 .. k ila ( k i = 1 r ( i , x i ) ) mod Mr(i,c)M(i,c)i1kcx1..k(i=1kr(i,xi))modM. Daha sonra, herhangi bir ayrı dizginin çarpışma olasılığı tam olarak . Karakter kümeniz n'ye kıyasla nispeten küçükse bu yaklaşım daha iyidir .1/Mn


2

Burada yayınlanan algoritmaların birçoğu karma tablolarda biraz alan kullanır. İşte yardımcı depolama O ( ( n lg n ) k 2 ) çalışma zamanı basit algoritması.O(1)O((nlgn)k2)

Hile kullanmaktır iki değer arasındaki karşılaştırma ürünü, bir ve b bu döner doğru ise bir < b (sözlük sırasında) göz ardı ederken k inci karakter. Sonra algoritma aşağıdaki gibidir.Ck(a,b)aba<bk

İlk olarak, dizeleri düzenli olarak sıralayın ve kopyaları kaldırmak için doğrusal bir tarama yapın.

Sonra, her :k

  1. Karşılaştırıcı olarak ile dizeleri sıralayın .Ck

  2. Yalnızca cinsinden farklı olan dizeler artık bitişiktir ve doğrusal bir taramada algılanabilir.k


1

Uzunluğu iki şeritleri k bir karakter bakımından farklı, uzunluğu bir önek paylaşan l ve uzunluk bir eki m , öyle ki , k = l + m + 1 .

Simon Prins tarafından cevap tüm ön / son ek kombinasyonları açıkça, yani depolayarak bu kodlar abcolur *bc, a*cve ab*. Bu k = 3, l = 0,1,2 ve m = 2,1,0.

ValarMorghulis'in işaret ettiği gibi, bir önek ağacındaki kelimeleri düzenleyebilirsiniz. Çok benzer ek ağacı da var. Her ön ekin veya son ekin altındaki yaprak düğümlerinin sayısı ile ağacı büyütmek oldukça kolaydır; bu, yeni bir kelime eklenirken O (k) olarak güncellenebilir.

Bu kardeş sayımlarını istemenin nedeni, yeni bir sözcük verdiğinizde, tüm önekleri aynı önekle numaralandırmak isteyip istemediğinizi veya tüm ekleri aynı sonekle numaralandırmak isteyip istemediğinizi bilmenizdir. Örneğin giriş olarak "abc" için, olası önekler "", "a" ve "ab" iken karşılık gelen sonekler "bc", "c" ve "" şeklindedir. Açıkça görüldüğü gibi, kısa ekler için önek ağacındaki kardeşleri numaralandırmak ve tersini yapmak daha iyidir.

@Einpoklum'un belirttiği gibi, tüm dizelerin aynı k / 2 önekini paylaşması kesinlikle mümkündür . Bu yaklaşım için sorun değil; önek ağacı k / 2 derinliğine kadar doğrusal olacak ve her bir düğüm k / 2 derinliğine kadar 100.000 yaprak düğümünün atası olacaktır. Sonuç olarak, sonek ağacı (k / 2-1) derinliğe kadar kullanılacaktır, bu da iyidir çünkü dizeler önekleri paylaştıklarından soneklerinde farklılık göstermek zorundadır.

Bir optimizasyon olarak, bir dizenin en kısa benzersiz önekini belirledikten sonra, farklı bir karakter varsa , önekin son karakteri olması gerektiğini ve kısa olan bir öneki denetleme. Yani "abcde" en kısa benzersiz öneki "abc" içeriyorsa, "ab?" İle başlayan başka dizeler de var demektir. "abc" ile değil. Yani, sadece bir karakterden farklı olsaydı, bu üçüncü karakter olurdu. Artık "abc? E" yi kontrol etmenize gerek yok.

Aynı mantıkla, "cde" nin benzersiz bir en kısa sonek olduğunu görürseniz, uzunluk 1 veya 3 öneklerini değil, yalnızca uzunluk-2 "ab" önekini kontrol etmeniz gerektiğini bilirsiniz.

Bu yöntemin yalnızca bir karakter farklılığı için işe yaradığını ve 2 karakter farklılığını genelleştirmediğini, bir karakteri aynı öneklerle özdeş sonekler arasındaki ayrım olarak kullandığını unutmayın.


Her dize için söylüyorsunuz ve her 1 i k , biz düğüm bulmak P [ s 1 , ... , s i - 1 ] Boy-tekabül ( i - 1 ) önek tray içinde önek ve düğüm S [ s i + 1 , , s k ] uzunluğa karşılık gelir- ( k - i - 1 )s1ikP[s1,,si1](i1)S[si+1,,sk](ki1)ek üçte son eki (her biri amortismana tabi tutulmuş zaman alır) ve her birinin torunu sayısını karşılaştırın, hangisinin daha az torununa sahip olduğunu seçtikten sonra, o sıradaki dizenin geri kalanı için "problama"? O(1)
j_random_hacker

1
Yaklaşımınızın çalışma süresi nedir? Bana en kötü durumda ikinci dereceden olabileceği gibi geliyor: her dize aynı karakterle başlayıp biterse ne olacağını düşünün . k/4
DW

Optimizasyon fikri zekice ve ilginç. Akıl hastalığı kontrolünü yapmanın belirli bir yolunu düşündünüz mü? "Abcde" en kısa benzersiz öneki "abc" içeriyorsa, "ab? De" biçiminde başka bir dize olup olmadığına bakmamız gerekir. Bunu yapmanın belirli bir yolu olduğunu düşündünüz mü, bu etkili olacak mı? Ortaya çıkan çalışma süresi nedir?
DW

@DW: Buradaki fikir, "ab? De" biçiminde dizeleri bulmak için, "ab" altında kaç yaprak düğümü bulunduğunu ve sonek ağacında "de" altında kaç düğüm bulunduğunu kontrol edip numaralandırmak için ikisinin en küçüğü. Tüm dizeler aynı k / 4 karakterle başladığında ve bittiğinde; bu, her iki ağaçtaki ilk k / 4 düğümlerinin her birinde bir çocuk olduğu anlamına gelir. Ve evet, bu ağaçlara her ihtiyaç duyduğunuzda, O (n * k) adımı olanların çaprazlanması gerekir.
MSalters

Formun "ab? De" öneki tray, kendi çocuklarının her biri için, daha sonra "ab" için düğüme almak etmeye yeter bir dizi kontrol etmek için , çek yolu "de" altında var olup olmadığını v . Yani, bu alt bölgelerdeki diğer düğümleri numaralandırmayı zahmet etmeyin. Bu O alır (vv zamanını alır, burada a alfabe boyutu ve h üçgendeki ilk düğümün yüksekliğidir. h , O ( k ) 'dir , bu nedenle alfabe boyutu O ( n ) ise, gerçekten O ( n k ) olurO(ah)ahhO(k)O(n)O(nk)ancak daha küçük harfler yaygındır. Yüksekliğin yanı sıra çocuk sayısı (torun değil) önemlidir.
j_random_hacker

1

Dizeleri kovalarda saklamak iyi bir yoldur (bunu özetleyen farklı cevaplar zaten vardır).

Alternatif bir çözüm dizeleri sıralı bir listede saklamak olabilir . İşin püf noktası, yere duyarlı bir karma algoritmasına göre sıralamaktır . Bu, girdi benzer olduğunda benzer sonuçlar veren bir karma algoritmadır [1].

Bir dizeyi her araştırmak istediğinizde, karmasını hesaplayabilir ve bu karmanın sıralı listenizdeki konumunu arayabilirsiniz ( diziler için ( l o g ( n ) ) veyabağlantılı listeler için O ( n ) ) arayabilirsiniz. Komşuların (tüm yakın komşuları göz önünde bulundurarak, sadece +/- 1 endeksine sahip olanlar değil) aynı olduğunu fark ederseniz (bir karakterle kapalı) eşleşmenizi buldunuz. Benzer dizeler yoksa, yeni dizeyi bulduğunuz konuma ekleyebilirsiniz ( bağlantılı listeler için O ( 1 ) vediziler için O ( n ) alır ).O(log(n))O(n)O(1)O(n)

Olası bir yerellik duyarlı karma algoritması olabilir Nilsimsa (örneğin mevcut açık kaynak uygulaması ile piton ).

[1]: SHA1 gibi genellikle karma algoritmaların tersi için tasarlandığını unutmayın: benzer, ancak eşit olmayan girdiler için çok farklı karmalar üretmek.

Feragatname: Dürüst olmak gerekirse, kişisel olarak bir üretim uygulaması için iç içe / ağaç organize edilmiş kova çözümlerinden birini uygularım. Ancak, sıralı liste fikri ilginç bir alternatif olarak beni vurdu. Bu algoritmanın büyük ölçüde seçilen karma algoritmaya bağlı olduğunu unutmayın. Nilsimsa bulduğum bir algoritma - çok daha fazlası var (örneğin TLSH, Ssdeep ve Sdhash). Nilsimsa'nın ana hatları belirlenmiş algoritmamla çalıştığını doğrulamadım.


1
İlginç bir fikir, ama girişleri sadece 1 karakter farklı olduğunda iki karma değerin ne kadar uzak olabileceği konusunda bazı sınırlarımız olması gerektiğini düşünüyorum - o zaman sadece komşular yerine karma değer aralığındaki her şeyi tarayın. ( 1 karakterden farklı tüm olası dize çiftleri için bitişik karma değerleri üreten bir karma işlevine sahip olmak imkansızdır . İkili alfabedeki uzunluk-2 dizelerini göz önünde bulundurun: 00, 01, 10 ve 11. h (00) ise h (10) ve h (01) 'e bitişikse aralarında olmalıdır, bu durumda h (11) her ikisine de bitişik olamaz ve tersi de geçerlidir.)
j_random_hacker

Komşulara bakmak yeterli değil. Abcd, acef, agcd listesini düşünün. Eşleşen bir çift var, ancak abcd agcd'nin bir komşusu olmadığından prosedürünüz bulamaz.
DW

İkiniz de haklısınız! Komşularla sadece "doğrudan komşular" demek istemedim, aynı zamanda yakın konumların "komşuluğunu" düşündüm. Karma algoritmaya bağlı olduğu için kaç komşuya bakılması gerektiğini belirtmedim. Ama haklısın, muhtemelen cevabımda bunu not etmeliyim. teşekkürler :)
tessi

1
"LSH ... benzer öğeler yüksek olasılıkla aynı" kovalarla eşleşir "- olasılık algoritması olduğu için sonuç garanti edilmez. Bu nedenle TS'ye% 100 çözüme mi yoksa% 99,9'a mı ihtiyacı olduğuna bağlıdır.
Bulat

1

Bir solüsyonu elde edebiliriz saat ve O ( n, k ) alanı kullanılarak geliştirilmiş son ek dizileri ( soneki dizisi ile birlikte LCP dizi sabit zaman, LCP (uzun ortak Önek) sorgu sağlar) (yani, göz önüne alındığında, iki bir dizenin indeksleri, bu endekslerden başlayarak soneklerin en uzun önekinin uzunluğu nedir). Burada, tüm dizelerin eşit uzunlukta olması gerçeğinden yararlanabiliriz. özellikle,O(nk+n2)O(nk)

  1. Birlikte birleştirilen tüm dizelerin gelişmiş sonek dizisini oluşturun . Let X = x 1 . x 2 . x 3 . . . . x n burada x i , 1 i n koleksiyondaki bir dizedir. X için sonek dizisini ve LCP dizisini oluşturun .nX=x1.x2.x3....xnxi,1inX

  2. Şimdi her sıfır tabanlı indekslemede ( i - 1 ) k konumunda başlar . Her dize için x i , dize her biriyle LCP almak x j öyle ki j < i . LCP sonuna ötesine giderse x j sonra x i = x j . Aksi takdirde, bir uyumsuzluk vardır (örneğin x i [ p ] x j [ p ]xi(i1)kxixjj<ixjxi=xjxi[p]xj[p] ; bu durumda uyumsuzluğu izleyen ilgili konumlardan başlayarak başka bir LCP alın. İkinci LCP sonuna ötesine giderse sonraxj ve x j yalnızca bir karakter farklılık; aksi halde birden fazla uyumsuzluk vardır.xixj

    for (i=2; i<= n; ++i){
        i_pos = (i-1)k;
        for (j=1; j < i; ++j){
            j_pos = (j-1)k;
            lcp_len = LCP (i_pos, j_pos);
            if (lcp_len < k) { // mismatch
                if (lcp_len == k-1) { // mismatch at the last position
                // Output the pair (i, j)
                }
                else {
                  second_lcp_len = LCP (i_pos+lcp_len+1, j_pos+lcp_len+1);
                  if (lcp_len+second_lcp_len>=k-1) { // second lcp goes beyond
                    // Output the pair(i, j)
                  }
                }
            }
        }
    }
    

Sen kullanabilirsiniz sdsl kütüphane LCP sorguları sıkıştırılmış formda eki dizisini oluşturmak ve cevap.

Analiz: Geliştirilmiş sonek dizisinin oluşturulması uzunluğunda doğrusaldır,yani O ( n k ) . Her LCP sorgusu sabit zaman alır. Bu nedenle, zaman sorgulama olan O ( n, 2 ) .XO(nk)O(n2)

Genelleme: Bu yaklaşım, birden fazla uyumsuzluğa da genelleştirilebilir. Genel olarak, çalışma süresiq izin uyumsuzlukları sayısıdır.O(nk+qn2)q

Bir dizeyi koleksiyondan kaldırmak istiyorsanız, her dizeyi yalnızca 'geçerli' j'nin bir listesini tutabilirsiniz.j<ij


algo'nun önemsiz olduğunu söyleyebilir miyim - sadece her dize çiftini karşılaştırın ve eşleşme sayısını sayın? Ve bu formülde pratik olarak atlanabilir, çünkü SSE ile 16 sembol başına 2 CPU döngüsünde eşleşen baytları sayabilirsiniz (yani k = 40 için 6 döngü). O(kn2)k
Bulat

Özür dilerim ama sorgunuzu anlayamadım. Yukarıdaki yaklaşımdır olup O ( k , n 2 ) . Ayrıca, neredeyse alfabe boyutundan bağımsızdır. Karma tablo yaklaşımıyla birlikte kullanılabilir - İki dizenin aynı karma değerlere sahip olduğu tespit edildiğinde, O ( 1 ) zamanında tek bir uyumsuzluk içerip içermediklerini test edebilirler . O(nk+n2)O(kn2)O(1)
Ritu Kundu

Demek istediğim, soru yazarı için k = 20..40 ve bu küçük dizeleri karşılaştırmak sadece birkaç CPU döngüsü gerektiriyor, bu yüzden kaba kuvvet ve yaklaşımınız arasındaki pratik fark muhtemelen mevcut değil.
Bulat

1

Önerilen tüm çözümlerde bir gelişme. Hepsi en kötü durumda n k ) bellek. Sen ile dizeleri karmaları hesaplayarak bunu azaltabiliryani yerine her karakter,,... ve sadece belirli tamsayı aralığındaki karma değeri ile varyantları her geçişte işleme. İlk geçişte çift karma değerleri ve ikincisinde tek kare değerleri olan Fe.O(nk)**bcdea*cde

Bu yaklaşımı, işi birden çok CPU / GPU çekirdeği arasında bölmek için de kullanabilirsiniz.


Akıllı öneri! Bu durumda, asıl soru ve k 40 diyor , bu yüzden O ( n k ) bellek bir sorun gibi görünmüyor (4 MB gibi bir şey olabilir). Yine de, bunu ölçeklendirmek gerekip gerekmediğini bilmeye değer iyi bir fikir! n=100,000k40O(nk)
DW

0

Bu @SimonPrins'in yanıtlarını içermeyen kısa bir sürümüdür.

Dizelerinizden hiçbirinin yıldız işareti olmadığını varsayarsak:

  1. Dizelerinizin her birinin k varyasyonunda gerçekleştiği, her biri bir yıldız işaretiyle değiştirilen bir harf içeren (çalışma zamanı O ( n k 2 ) ) boyutunda bir liste oluşturunnkkO(nk2)
  2. Bu listeyi sırala (çalışma zamanı O(nk2lognk) )
  3. Sıralanan listenin sonraki girişlerini karşılaştırarak kopyaları kontrol edin (çalışma zamanı O(nk2) )

Python'da karmaların örtülü kullanımı ile alternatif bir çözüm (güzelliğe direnemez):

def has_almost_repeats(strings,k):
    variations = [s[:i-1]+'*'+s[i+1:] for s in strings for i in range(k)]
    return len(set(variations))==k*len(strings)

Teşekkürler. Lütfen tam kopyaların kopyalarından da bahsettiğinizde +1 olurum. (Hmm, sadece ben yaklaşık aynı iddiada fark O ( n k ) Daha İyi düzeltme olduğunu ... ... kendi cevap süresi)kO(nk)
j_random_hacker

@j_random_hacker OP'nin tam olarak ne istediğini bilmiyorum, bu yüzden 3. adımı belirsiz bıraktım, ancak (a) ikili herhangi bir yinelenen / yinelenen sonuç yok veya (b) bir liste bildirmek için bazı ekstra çalışmalarla önemsiz olduğunu düşünüyorum çoğaltmadan en fazla bir konumda farklılık gösteren dizgi çiftleri. OP'yi kelimenin tam anlamıyla alırsak ("... herhangi iki dize olup olmadığını görmek için ..."), o zaman (a) isteniyor gibi görünüyor. Ayrıca, eğer (b) istenmişse, o zaman elbette sadece bir çift listesi oluşturmak , tüm dizeler eşitse alabilirO(n2)
Bananach

0

İşte benim 2+ uyumsuzluk bulucu almak. Bu yazıda her dizeyi dairesel olarak kabul ediyorum, dizindeki uzunluk 2'nin fe alt dizesi k-1, sembolün str[k-1]ardından gelenstr[0] . Ve dizindeki uzunluk 2 alt dizisi -1aynı!

Mecbur kalırsak Muzunluğunun iki dizeleri arasındaki uyumsuzluklar k, onlar uzunlukta alt dize eşleşen var en azından içine kötü durumda, uyumsuz semboller bölünmüş (dairesel) dize beri eşit boyutlu segmentler. Fe ile vemlen(k,M)=k/M1Mk=20M=4 "en kötü" maç paterni olabilir abcd*efgh*ijkl*mnop*.

Şimdi, Msembol dizeleri arasındaki ksembollere kadar tüm uyumsuzlukları arama algoritması :

  • her i için 0'dan k-1'e
    • tüm dizeleri gruplara ayırın str[i..i+L-1], nerede L = mlen(k,M). Fe L=4ve sadece 4 sembolden oluşan bir alfabeniz varsa (DNA'dan), bu 256 grup oluşturacaktır.
    • ~ 100 karakter dizisinden daha küçük gruplar kaba kuvvet algoritması ile kontrol edilebilir
    • Daha büyük gruplar için ikincil bölüm gerçekleştirmeliyiz:
      • LÖnceden eşleştirdiğimiz grup sembollerindeki her dizeden kaldır
      • i-L + 1'den kL-1'e kadar her j için
        • tüm dizeleri gruplara ayırın str[i..i+L1-1], nerede L1 = mlen(k-L,M). Fe if k=20, M=4, alphabet of 4 symbols, yani L=4ve L1=3bu 64 grup oluşturacaktır.
        • geri kalanı okuyucu için egzersiz olarak bırakılır: D

Neden j0'dan başlamıyoruz ? Çünkü bu grupları zaten aynı değerde yaptık i,j<=i-L olan iş, i ve j değerlerinin değiştirildiği işe tam olarak eşdeğer olacaktır.

Diğer optimizasyonlar:

  • Her pozisyonda, ipleri de düşünün str[i..i+L-2] & str[i+L]. Bu sadece yaratılan iş miktarını iki katına çıkarır, ancak L1 matematik artırmaya izin verir (matematik doğru ise). Yani, 256 grup yerine fe, verileri 1024 gruba bölebilirsiniz.
  • L[i]*0..k-1M-1k-1

0

Her gün algoları icat etmek ve optimize etmek için çalışıyorum, bu yüzden performansın her bir bitine ihtiyacınız varsa, plan budur:

  • *Her pozisyonda bağımsız olarak kontrol edin , yani tekli iş işleme n*kdizesi varyantları yerine - kher kontrol dizesinde bağımsız işleri başlatın n. Bu kişleri birden fazla CPU / GPU çekirdeği arasında dağıtabilirsiniz. Bu özellikle 2+ char diffs'yi kontrol edecekseniz önemlidir. Daha küçük iş boyutu da önbellek yerini iyileştirir, bu da kendi başına programı 10 kat daha hızlı hale getirebilir.
  • Karma tabloları kullanacaksanız, doğrusal problama ve ~% 50 yük faktörü kullanarak kendi uygulamanızı kullanın. Uygulaması hızlı ve kolaydır. Veya açık adreslemeli mevcut bir uygulamayı kullanın. Ayrı zincirleme kullanımı nedeniyle STL karma tabloları yavaştır.
  • @AlexReynolds tarafından önerildiği gibi 3 durumlu Bloom filtresi (0/1/1 + oluşumlarını ayırt ederek) kullanarak verileri ön filtrelemeyi deneyebilirsiniz.
  • 0'dan k-1'e kadar olan her i için aşağıdaki işi çalıştırın:
    • Her dizenin ( *i-th konumunda) 4-5 bayt karmasını ve dize dizinini içeren 8 baytlık yapılar oluşturun ve sonra bunları sıralayın veya bu kayıtlardan karma tablosu oluşturun.

Sıralama için aşağıdaki combo'yu deneyebilirsiniz:

  • İlk geçiş, TLB hilesi kullanarak 64-256 şekilde MSD yarıçapı sıralamasıdır
  • ikinci geçiş, TLB hile olmadan 256-1024 şekilde MSD yarıçapı sıralamasıdır (toplam 64K yol)
  • üçüncü geçiş, kalan tutarsızlıkları düzeltmek için ekleme sıralamasıdır
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.