दो अनुक्रमों को देखते हुए, एक के अंत और दूसरे की शुरुआत के बीच अधिकतम ओवरलैप का पता लगाएं


11

मुझे निम्नलिखित समस्या को हल करने के लिए एक कुशल (छद्म) कोड खोजने की आवश्यकता है:

के (जरूरी अलग नहीं) पूर्णांकों दो दृश्यों को देखते हुए (a[1], a[2], ..., a[n])और (b[1], b[2], ..., b[n])अधिकतम लगता है dऐसा है कि a[n-d+1] == b[1], a[n-d+2] == b[2], ..., और a[n] == b[d]

यह होमवर्क नहीं है, मैं वास्तव में इस के साथ आया था जब संभव के रूप में कई आयामों के साथ दो टेनर्स को अनुबंधित करने की कोशिश कर रहा था। मुझे लगता है कि एक कुशल एल्गोरिथ्म मौजूद है (शायद O(n)?), लेकिन मैं कुछ ऐसा नहीं कर सकता जो नहीं है O(n^2)O(n^2)दृष्टिकोण पर स्पष्ट पाश होगा dऔर फिर आइटम पर एक आंतरिक पाश अधिकतम मार जब तक आवश्यक स्थिति की जांच करने के लिए d। लेकिन मुझे संदेह है कि इससे बेहतर कुछ हो सकता है।


यदि एक रोलिंग हैश आपके सरणी में वस्तुओं के समूह के लिए गणना की जा सकती है तो मुझे लगता है कि यह अधिक कुशलता से किया जा सकता है। तत्वों के लिए हैश की गणना करें b[1] to b[d]और फिर उस मैच के aलिए सरणी कंप्यूट हैश पर जाएं, a[1] to a[d]तो यह आपका उत्तर है, यदि हैश के लिए गणना a[2] to a[d+1]करके हैश का उपयोग करके गणना नहीं है a[1] to a[d]। लेकिन मुझे नहीं पता कि अगर सरणी में वस्तुओं को एक रोलिंग हैश के लिए उन पर गणना की जा सकने योग्य है।
SomeDude

2
@becko क्षमा करें, मुझे लगता है कि मैं अंत में समझता हूं कि आप क्या हासिल करने की कोशिश कर रहे हैं। जो aकी शुरुआत के साथ अंत के बीच अधिकतम ओवरलैप खोजने के लिए है bइस तरह
user3386109

1
मुझे लगता है कि समस्या स्ट्रिंग मिलान पर एक भिन्नता है, जिसे नूथ-मॉरिस-प्रैट एल्गोरिथ्म पर भिन्नता के साथ हल किया जा सकता है । चलने का समय O (m + n) होगा जहां mतत्वों की संख्या है a, और nतत्वों की संख्या है b। दुर्भाग्य से, मेरे पास KMP के साथ पर्याप्त अनुभव नहीं है कि आप इसे कैसे अनुकूलित कर सकते हैं।
user3386109

1
@ user3386109 मेरा समाधान रैन -कार्प नामक स्ट्रिंग मिलान एल्गोरिथ्म का भी रूपांतर है , जो हॉर्नर की विधि को हैश फ़ंक्शन के रूप में उपयोग करता है।
डेनियल

1
@ डैनियल आह, मुझे पता था कि मैंने एक रोलिंग हैश का इस्तेमाल कहीं देखा था, लेकिन यह याद नहीं था कि :)
user3386109

जवाबों:


5

आप z एल्गोरिथ्म , एक रेखीय समय ( O (n) ) एल्गोरिथ्म का उपयोग कर सकते हैं जो:

लंबाई n की एक स्ट्रिंग S को देखते हुए , Z एल्गोरिथम एक सरणी Z बनाता है जहां Z [i] S [i] से शुरू होने वाले सबसे लंबे सबस्ट्रिंग की लंबाई है जो कि S का उपसर्ग भी है।

(आप अपने सरणियों श्रेणीबद्ध करने की जरूरत है + एक ) और पहले तक जिसके परिणामस्वरूप निर्माण सरणी पर एल्गोरिथ्म चलाने मैं ऐसा है कि जेड [i] + मैं == मीटर + एन

उदाहरण के लिए, एक = [१, २, ३, ६, २, ३] और बी = [२, ३, ६, २, १, ०], संघटन [२, ३, ६, २, २, १ होगा] , 0, 1, 2, 3, 6, 2, 3] जो Z [10] = 2 को पूरा करेगा Z [i] + i = 12 = m + n


सुंदर! धन्यवाद।
becko

3

O (n) समय / स्थान की जटिलता के लिए, चाल को प्रत्येक बाद के लिए हैश का मूल्यांकन करना है। सरणी पर विचार करें b:

[b1 b2 b3 ... bn]

हॉर्नर की विधि का उपयोग करके , आप प्रत्येक बाद के लिए सभी संभावित हैश का मूल्यांकन कर सकते हैं। आधार मूल्य चुनें B(आपके दोनों सरणियों में किसी भी मूल्य से बड़ा):

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

ध्यान दें कि आप पिछले अनुक्रम के परिणाम का उपयोग करके ओ (1) समय में प्रत्येक अनुक्रम का मूल्यांकन कर सकते हैं, इसलिए सभी नौकरी की लागत ओ (एन) है।

अब आपके पास एक सरणी है Hb = [h(b1), h(b2), ... , h(bn)], जब तक Hb[i]हैश b1नहीं है bi

सरणी के लिए एक ही काम करें a, लेकिन थोड़ी चाल के साथ:

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)

आपको ध्यान देना चाहिए कि जब आप एक क्रम से दूसरे क्रम में कदम रखते हैं, तो आप पूरे पिछले क्रम को B से गुणा करते हैं और B द्वारा नए मान को गुणा करते हैं: उदाहरण के लिए

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)

अब आपके पास एक सरणी है Ha = [h(an), h(an-1), ... , h(a1)], जब तक Ha[i]हैश aiनहीं है an

अब, आप Ha[d] == Hb[d]सभी dमानों की तुलना n से 1 तक कर सकते हैं , यदि वे मेल खाते हैं, तो आपके पास आपका उत्तर होगा।


ध्यान : यह एक हैश विधि है, मान बड़े हो सकते हैं और आपको एक तेज घातांक विधि और मॉड्यूलर अंकगणित का उपयोग करना पड़ सकता है , जो (शायद ही) आपको टक्कर दे सकता है , जिससे यह विधि पूरी तरह से सुरक्षित नहीं है। एक अच्छा अभ्यास एक आधार Bको वास्तव में बड़ी प्राइम संख्या के रूप में चुनना है (आपकी सरणियों में सबसे बड़े मूल्य से कम से कम बड़ा)। आपको सावधान रहना चाहिए क्योंकि प्रत्येक चरण में संख्याओं की सीमा ओवरफ्लो हो सकती है, इसलिए आपको Kप्रत्येक ऑपरेशन में (मोडुलो ) का उपयोग करना होगा (जहां Kइससे बड़ा प्राइम हो सकता है B)।

इसका मतलब है कि दो अलग-अलग दृश्यों में एक ही हैश हो सकता है, लेकिन दो समान अनुक्रमों में हमेशा एक ही हैश होगा।


क्या आप संसाधन आवश्यकताओं के आकलन के साथ इस उत्तर को शुरू कर सकते हैं?
ग्रेबियर

2

यह वास्तव में रैखिक समय, O (n) , और O (n) अतिरिक्त स्थान में किया जा सकता है। मुझे लगता है कि इनपुट सरणियां चरित्र के तार हैं, लेकिन यह आवश्यक नहीं है।

एक अनुभवहीन विधि होगा - मिलान के बाद कश्मीर अक्षर हैं जो बराबर हैं - एक चरित्र से मेल नहीं खाता, और वापस जाने के लिए कश्मीर -1 में इकाइयों एक , सूचकांक में रीसेट , और फिर वहाँ से मिलान प्रक्रिया शुरू करते हैं। यह स्पष्ट रूप से O (n²) सबसे खराब स्थिति का प्रतिनिधित्व करता है ।

इस बैकट्रैकिंग प्रक्रिया से बचने के लिए, हम यह देख सकते हैं कि यदि पिछले k-1 वर्णों को स्कैन करते समय हमने b [0] वर्ण का सामना नहीं किया है तो वापस जाना उपयोगी नहीं है । अगर हम किया था कि चरित्र लगता है, तो उस स्थिति को उलटे पांव लौटने से केवल उपयोगी है, अगर है कि में होगा कश्मीर सबस्ट्रिंग आकार हम एक आवधिक पुनरावृत्ति था।

उदाहरण के लिए, अगर हम "abcabc" में कहीं सबस्ट्रिंग को देखने के लिए एक , और "abcabd" है, और हम पाते हैं कि के अंतिम चरित्र से मेल नहीं खाता है, हम पर विचार करना चाहिए कि एक सफल मैच दूसरा "एक" में शुरू हो सकता है विकल्प में, और हमें तुलना जारी रखने से पहले अपने वर्तमान सूचकांक को बी बैक में स्थानांतरित करना चाहिए ।

फिर विचार यह है कि बी -बैक-रेफरेंस लॉग करने के लिए स्ट्रिंग बी पर आधारित कुछ प्रीप्रोसेसिंग करें जो कि बेमेल होने पर जांचने के लिए उपयोगी हों। उदाहरण के लिए, यदि b "acaacaacd" है, तो हम इन 0-आधारित बैकरेफ़र (प्रत्येक वर्ण के नीचे रख) की पहचान कर सकते हैं:

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

उदाहरण के लिए, यदि हमारे पास एक के बराबर "acaacaaca" पहले बेमेल अंतिम चरित्र पर होता है। उपरोक्त जानकारी तब एल्गोरिथ्म को b से इंडेक्स 5 में वापस जाने के लिए कहती है , क्योंकि "acaac" आम है। और उसके बाद ही में मौजूदा सूचकांक में बदलाव के साथ हम की वर्तमान सूचकांक में मिलान जारी रख सकते हैं एक । इस उदाहरण में अंतिम चरित्र का मैच तब सफल होता है।

इसके साथ ही हम खोज अनुकूलन और यह सुनिश्चित करें कि में सूचकांक कर सकते हैं एक हमेशा आगे प्रगति कर सकते हैं।

यहाँ जावास्क्रिप्ट में उस विचार का एक कार्यान्वयन है, केवल उस भाषा के सबसे बुनियादी वाक्यविन्यास का उपयोग करना:

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

हालांकि नेस्टेड whileलूप हैं, इनमें n की तुलना में कुल पुनरावृत्तियां नहीं हैं । ऐसा इसलिए है क्योंकि शरीर में कश्मीर का मान सख्ती से घटता है while, और नकारात्मक नहीं हो सकता। यह केवल तब हो सकता है जब k++निष्पादित किया गया था कि कई बार ऐसे घटने के लिए पर्याप्त जगह देने के लिए। इसलिए सभी में, whileशरीर के अधिक निष्पादन नहीं हो सकते हैं, जहां निष्पादन होते हैं k++, और उत्तरार्द्ध स्पष्ट रूप से ओ (एन) है।

पूर्ण करने के लिए, आप ऊपर जैसा कोड पा सकते हैं, लेकिन एक इंटरैक्टिव स्निपेट में: आप अपने स्वयं के तार इनपुट कर सकते हैं और परिणाम को अंतःक्रियात्मक रूप से देख सकते हैं:

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.