स्ट्रिंग्स के बीच अंतर खोजने के लिए डेटा संरचना या एल्गोरिथ्म


19

मेरे पास 100,000 स्ट्रिंग्स की एक सरणी है, सभी की लंबाई । मैं प्रत्येक स्ट्रिंग की तुलना हर दूसरे स्ट्रिंग से करना चाहता हूं ताकि यह देखा जा सके कि कोई दो तार 1 वर्ण से भिन्न हैं या नहीं। अभी, जैसा कि मैं प्रत्येक स्ट्रिंग को सरणी में जोड़ता हूं, मैं इसे पहले से ही सरणी में प्रत्येक स्ट्रिंग के खिलाफ जांच रहा हूं, जिसमें समय जटिलता है ।kn(n1)2k

क्या कोई डेटा संरचना या एल्गोरिथ्म है जो मैं पहले से कर रहे हैं की तुलना में तेजी से एक दूसरे से तार की तुलना कर सकता है?

कुछ अतिरिक्त जानकारी:

  • आदेश मायने रखता है: abcdeऔर xbcde1 वर्ण द्वारा भिन्न, जबकि 4 वर्णों से भिन्न abcdeऔर edcbaभिन्न।

  • तार के प्रत्येक जोड़े के लिए जो एक वर्ण से भिन्न होते हैं, मैं उन स्ट्रिंग में से एक को सरणी से निकालूंगा।

  • अभी, मैं ऐसे तार की तलाश कर रहा हूं जो केवल 1 वर्ण से भिन्न हों, लेकिन यह अच्छा होगा यदि उस 1 वर्ण अंतर को 2, 3, या 4 वर्णों तक बढ़ा दिया जाए। हालांकि, इस मामले में, मुझे लगता है कि चरित्र-अंतर की सीमा को बढ़ाने की क्षमता की तुलना में दक्षता अधिक महत्वपूर्ण है।

  • k आमतौर पर 20-40 की सीमा में है।


4
1 त्रुटि के साथ एक स्ट्रिंग शब्दकोश खोजना एक काफी प्रसिद्ध समस्या है, जैसे cs.nyu.edu/~adi/CGL04.pdf
KWillets

1
20-40 लोग थोड़े से स्थान का उपयोग कर सकते हैं। आप पतले स्ट्रिंग्स - टेस्ट करने के लिए एक ब्लूम फ़िल्टर ( en.wikipedia.org/wiki/Bloom_filter ) को देख सकते हैं - टेस्ट मेर पर एक, दो या दो से अधिक बारीकियों का सेट - शायद "या" निश्चित रूप से -नोट-इन "किमी का एक सेट। यदि आपको "शायद-इन" मिलता है, तो यह निर्धारित करने के लिए कि क्या यह गलत है या नहीं, दोनों तार की तुलना करें। "निश्चित रूप से नहीं" मामले सच्चे नकारात्मक हैं जो आपके द्वारा किए जाने वाले पत्र-दर-अक्षर तुलनाओं की कुल संख्या को कम कर देंगे, केवल संभावित "शायद-इन" हिट्स की तुलना को सीमित करके।
एलेक्स रेनॉल्ड्स

यदि आप k की एक छोटी श्रृंखला के साथ काम कर रहे थे, तो आप सभी पतित स्ट्रिंग्स (जैसे github.com/alexpreynolds/kmer-boolean for toy example) के लिए बूलियन की हैश टेबल को स्टोर करने के लिए एक बिटसेट का उपयोग कर सकते हैं । K = 20-40 के लिए, हालांकि, एक बिटसेट के लिए स्थान की आवश्यकताएं बहुत अधिक हैं।
एलेक्स रेनॉल्ड्स

जवाबों:


12

सबसे खराब स्थिति में चलने का समय प्राप्त करना संभव है ।O(nklogk)

चलो सरल शुरू करते हैं। यदि आप एक आसान समाधान को लागू करने के बारे में परवाह करते हैं जो कई इनपुट पर कुशल होगा, लेकिन सभी नहीं, यहां एक सरल, व्यावहारिक, समाधान को लागू करना आसान है जो कई स्थितियों के लिए अभ्यास में पर्याप्त है। यह सबसे खराब स्थिति में द्विघात चलने के समय में वापस आता है, हालांकि।

प्रत्येक स्ट्रिंग लें और इसे हैशटेबल में स्टोर करें, स्ट्रिंग के पहले आधे हिस्से पर कीज़ करें। फिर, हैशटेबल बाल्टी पर पुनरावृति। एक ही बाल्टी में तार के प्रत्येक जोड़े के लिए, जांचें कि क्या वे 1 वर्ण में भिन्न हैं (यानी, जांचें कि क्या उनका दूसरा आधा 1 वर्ण में भिन्न है)।

फिर, प्रत्येक स्ट्रिंग लें और इसे हैशटेबल में स्टोर करें, इस बार स्ट्रिंग के दूसरे छमाही पर कुंजी लगाई गई है । फिर से एक ही बाल्टी में तार के प्रत्येक जोड़े की जांच करें।

मान लें कि स्ट्रिंग्स अच्छी तरह से वितरित हैं, तो चलने का समय संभवतः बारे में होगा । इसके अलावा, अगर एक तार के जोड़े में मौजूद है जो कि 1 वर्ण से भिन्न होता है, तो यह दो पासों में से एक के दौरान पाया जाएगा (क्योंकि वे केवल 1 वर्ण से भिन्न होते हैं, यह भिन्न वर्ण स्ट्रिंग के पहले या दूसरे भाग में होना चाहिए, इसलिए स्ट्रिंग का दूसरा या पहला आधा हिस्सा समान होना चाहिए)। हालांकि, सबसे खराब स्थिति में (उदाहरण के लिए, यदि सभी तार एक ही वर्णों के साथ शुरू या समाप्त होते हैं ), तो यह चलने के समय के लिए कम हो जाता है, इसलिए इसका सबसे खराब समय चलने का समय नहीं है बल।O(nk)k/2हे(n2)

एक प्रदर्शन अनुकूलन के रूप में, यदि किसी भी बाल्टी में बहुत सारे तार हैं, तो आप एक जोड़ी के लिए अलग दिखने के लिए उसी प्रक्रिया को पुन: दोहरा सकते हैं जो एक वर्ण द्वारा भिन्न होती है। पुनरावर्ती आहरण लंबाई तारों पर होगा ।/2

अगर आपको सबसे खराब समय चल रहा है:

उपरोक्त प्रदर्शन अनुकूलन के साथ मेरा मानना ​​है कि सबसे खराब स्थिति चलने का समय ।हे(nलॉग)


3
यदि स्ट्रिंग्स समान पहली छमाही को साझा करते हैं, जो वास्तविक जीवन में बहुत अच्छी तरह से हो सकती है, तो आपने जटिलता में सुधार नहीं किया है। Ω(n)
einpoklum

@einpoklum, निश्चित रूप से! इसलिए मैंने अपने दूसरे वाक्य में बयान लिखा है कि यह सबसे खराब स्थिति में द्विघात चल रहे समय पर वापस आता है, साथ ही मेरे अंतिम वाक्य में कथन है कि यदि आप देखभाल करते हैं तो सबसे खराब स्थिति में कैसे प्राप्त करें। सबसे खराब स्थिति के बारे में। लेकिन मुझे लगता है कि शायद मैंने बहुत स्पष्ट रूप से व्यक्त नहीं किया है - इसलिए मैंने अपने उत्तर को तदनुसार संपादित किया है। क्या अब यह बेहतर है? O(nklogk)
डीडब्ल्यू

15

मेरा समाधान j_random_hacker के समान है, लेकिन केवल एक हैश सेट का उपयोग करता है।

मैं स्ट्रिंग्स का हैश सेट बनाऊंगा। इनपुट में प्रत्येक स्ट्रिंग के लिए, सेट स्ट्रिंग्स में जोड़ें । इनमें से प्रत्येक तार में से एक अक्षर को एक विशेष वर्ण से बदल दिया जाता है, जो किसी भी तार में नहीं पाया जाता है। जब आप उन्हें जोड़ते हैं, तो जांचें कि वे पहले से ही सेट में नहीं हैं। यदि वे हैं, तो आपके पास दो तार हैं जो केवल (अधिकतम) एक चरित्र से भिन्न होते हैं।k

तार 'एबीसी', 'एडीसी' के साथ एक उदाहरण

Abc के लिए हम '* bc', 'a * c' और 'ab *' जोड़ते हैं।

Adc के लिए हम '* dc', 'a * c' और 'ad *' जोड़ते हैं।

जब हम 'a * c' जोड़ते हैं तो दूसरी बार जब हम देखते हैं कि यह पहले से ही सेट में है, तो हम जानते हैं कि दो तार हैं जो केवल एक अक्षर से भिन्न होते हैं।

इस एल्गोरिथ्म का कुल चलने का समय । ऐसा इसलिए है क्योंकि हम इनपुट में सभी स्ट्रिंग्स के लिए नए स्ट्रिंग्स बनाते हैं । उन तारों में से प्रत्येक के लिए हमें हैश की गणना करने की आवश्यकता होती है, जो आमतौर पर समय लेता है ।O(nk2)knO(k)

सभी तारों को संचयित करने में स्थान लगता है।O(nk2)

और सुधार

हम संशोधित स्ट्रिंग्स को सीधे संग्रहीत न करके एल्गोरिदम को और बेहतर बना सकते हैं, बल्कि एक वस्तु को मूल स्ट्रिंग और नकाबपोश चरित्र के सूचकांक के संदर्भ में संग्रहीत कर सकते हैं। इस तरह हमें सभी तारों को बनाने की आवश्यकता नहीं है और सभी वस्तुओं को संग्रहीत करने के लिए हमें केवल स्थान की आवश्यकता है।O(nk)

आपको वस्तुओं के लिए एक कस्टम हैश फ़ंक्शन को लागू करने की आवश्यकता होगी। हम जावा कार्यान्वयन को एक उदाहरण के रूप में ले सकते हैं , जावा प्रलेखन देखें । जावा हैशकोड प्रत्येक वर्ण के यूनिकोड मान को ( के साथ स्ट्रिंग की लंबाई और आधारित चरित्र का एक-एक गुणा करता है । ध्यान दें कि प्रत्येक परिवर्तित स्ट्रिंग केवल मूल से एक वर्ण द्वारा भिन्न होती है। हम आसानी से कर सकते हैं। उस चरित्र के हैश कोड में योगदान की गणना करें। हम इसे घटा सकते हैं और इसके बजाय हमारे मास्किंग चरित्र को जोड़ सकते हैं। यह गणना करने के लिए लेता है । इससे हमें कुल चलने का समय तक लाने की अनुमति मिलती है।31kikiO(1)O(nk)


4
@ जॉली जोकर हाँ, अंतरिक्ष इस पद्धति के साथ एक चिंता का विषय है। आप संशोधित तारों को संग्रहीत न करके स्थान को कम कर सकते हैं, बल्कि स्ट्रिंग और नकाबपोश सूचकांक के संदर्भ के साथ एक वस्तु का भंडारण कर सकते हैं। जो आपको O (nk) स्पेस के साथ छोड़ना चाहिए।
साइमन प्रिन्स

समय में प्रत्येक स्ट्रिंग के लिए हैश की गणना करने के लिए , मुझे लगता है कि आपको एक विशेष होममेड हैश फ़ंक्शन की आवश्यकता होगी (उदाहरण के लिए, समय में मूल स्ट्रिंग के हैश की गणना करें , फिर हटाए गए प्रत्येक के साथ XOR करें समय के प्रत्येक अक्षर (हालांकि यह संभवतः अन्य तरीकों से एक बहुत बुरा हैश फ़ंक्शन है)। BTW, यह मेरे समाधान के समान है, लेकिन बजाय एक ही हैशटेबल के साथ है, और इसे हटाने के बजाय "*" के साथ एक चरित्र को प्रतिस्थापित करता है। kO(k)O(k)O(1)k
j_random_hacker

@SimonPrins कस्टम equalsऔर hashCodeविधियों के साथ काम कर सकता है। बस उन तरीकों में एक * बी-स्टाइल स्ट्रिंग बनाने के लिए इसे बुलेटप्रूफ बनाना चाहिए; मुझे संदेह है कि यहां कुछ अन्य उत्तरों में हैश टकराव की समस्या होगी।
जॉली जोकर

1
@DW मैं तथ्य यह है कि हैश की गणना लेता प्रतिबिंबित करने के लिए मेरी पोस्ट संशोधित समय और कहा कि करने के लिए नीचे की कुल प्रसारण समय वापस लाने के लिए एक समाधान हे ( n * कश्मीर )O(k)O(nk)
शमौन ने

1
@SimonPrins सबसे खराब मामला हो सकता है nk ^ 2 hashset में समानता की जाँच के कारण। जब hashes टकराता है। बेशक, सबसे खराब स्थिति है जब हर स्ट्रिंग एक ही सटीक हैश है, जो तार के एक काफी दस्तकारी सेट की आवश्यकता होगी, विशेष रूप से के लिए एक ही हैश प्राप्त करने के लिए है *bc, a*c, ab*। मुझे आश्चर्य है कि अगर यह असंभव दिखाया जा सकता है?
जॉली जोकर

7

मैं हैशटेबल्स H 1 , ... , H k बनाऊंगा , जिनमें से प्रत्येक के पास कुंजी के रूप में ( k - 1 ) -length स्ट्रिंग और मान के रूप में संख्याओं (स्ट्रिंग आईडी) की एक सूची है। Hashtable एच मैं सभी स्ट्रिंग्स में शामिल होंगे अब तक संसाधित लेकिन स्थिति में चरित्र के साथ मैं नष्ट कर दिया । उदाहरण के लिए, यदि k = 6 , तो H 3 [ A B D E E F ] में अब तक देखे गए सभी तारों की एक सूची होगी जिसमें पैटर्न A होगाkH1,,Hk(k1)Hiik=6H3[ABDEF] , जहां ⋅ का अर्थ है "कोई भी वर्ण"। फिर j -th इनपुट स्ट्रिंग s j को प्रोसेस करने के लिए:ABDEFjsj

  1. प्रत्येक के लिए करने के लिए सीमा 1 में कश्मीर : ik
    • फार्म स्ट्रिंग हटा कर मैं से मई के चरित्र रों जेsjisj
    • ऊपर देखो । यहां प्रत्येक स्ट्रिंग आईडी एक मूल स्ट्रिंग की पहचान करती है जो या तो s के बराबर होती है , या केवल I की स्थिति में भिन्न होती है । स्ट्रिंग एस जे के लिए मैच के रूप में इनका आउटपुट करें । (आप सटीक डुप्लिकेट बाहर रखना चाहते हैं, तो hashtables का मान प्रकार एक (स्ट्रिंग आईडी, नष्ट चरित्र) जोड़ी, कि आप उन है कि एक ही चरित्र के रूप में हम सिर्फ से हटा नष्ट कर दिया पड़ा है के लिए परीक्षण कर सकते हैं ताकि रों जे ।)Hi[sj]sisjsj
    • भविष्य के प्रश्नों का उपयोग करने के लिए i को H i में डालें ।jHi

यदि हम प्रत्येक हैश कुंजी को स्पष्ट रूप से संग्रहीत करते हैं, तो हमें स्थान का उपयोग करना चाहिए और इस प्रकार कम से कम समय की जटिलता होनी चाहिए । लेकिन जैसा कि साइमन प्रिंस द्वारा वर्णित किया गया है , एक स्ट्रिंग में संशोधनों की एक श्रृंखला का प्रतिनिधित्व करना संभव है (उनके मामले में एकल पात्रों को बदलने के रूप में वर्णित है , खदानों में विलोपन के रूप में) इस तरह से निहित है कि किसी विशेष स्ट्रिंग के लिए सभी k हैश कुंजी की जरूरत है हे ( कश्मीर ) अंतरिक्ष, के लिए अग्रणी हे ( एन कश्मीर ) अंतरिक्ष समग्र, और की संभावना खोलने हे ( एन कश्मीर )O(nk2)*kO(k)O(nk)O(nk)समय भी। इस समय की जटिलता को प्राप्त करने के लिए, हमें ( के ) समय में लंबाई- के तार के सभी रूपांतरों के लिए हैश की गणना करने का एक तरीका चाहिए : उदाहरण के लिए, यह बहुपद हैश का उपयोग करके किया जा सकता है, जैसा कि DW द्वारा सुझाया गया है (और यह है) मूल स्ट्रिंग के लिए हैश के साथ हटाए गए चरित्र को बस XORing से बेहतर होने की संभावना है)।kkO(k)

साइमन प्रिन्स की निहित प्रतिनिधित्व चाल का मतलब यह भी है कि प्रत्येक चरित्र का "विलोपन" वास्तव में नहीं किया जाता है, इसलिए हम एक स्ट्रिंग के सामान्य सरणी-आधारित प्रतिनिधित्व का उपयोग बिना किसी प्रदर्शन दंड के कर सकते हैं (लिंक की गई सूचियों के बजाय जैसा कि मैंने मूल रूप से सुझाव दिया था)।


2
अच्छा समाधान है। एक उपयुक्त बीस्पोक हैश फ़ंक्शन का एक उदाहरण एक बहुपद हैश होगा।
डीडब्ल्यू

धन्यवाद @DW क्या आप शायद थोड़ा स्पष्ट कर सकते हैं कि "बहुपद हैश" से आपका क्या मतलब है? इस शब्द को देखने से मुझे ऐसा कुछ नहीं मिला जो निश्चित प्रतीत होता हो। (यदि आप चाहें तो सीधे मेरी पोस्ट को संपादित करने के लिए स्वतंत्र महसूस करें।)
j_random_hacker

1
बस स्ट्रिंग को आधार नंबर मोडुलो पी के रूप में पढ़ें , जहां पी आपके हैशमाप के आकार से कुछ कम है, और क्यू पी की एक आदिम जड़ है , और क्यू अक्षर आकार से अधिक है। इसे "बहुपद हैश" कहा जाता है क्योंकि यह बहुपद का मूल्यांकन करने जैसा है जिसके गुणांक स्ट्रिंग द्वारा q पर दिए गए हैं । मैं इसे ( के ) समय में सभी वांछित हैश की गणना करने के लिए एक अभ्यास के रूप में छोड़ दूँगा । ध्यान दें कि यह दृष्टिकोण एक विरोधी के लिए प्रतिरक्षा नहीं है, जब तक कि आप यादृच्छिक रूप से दोनों पी का चयन न करें , क्यू वांछित परिस्थितियों को संतुष्ट करता है।qppqpqqO(k)p,q
user21820

1
मुझे लगता है कि इस समाधान को यह देखते हुए और अधिक परिष्कृत किया जा सकता है कि किसी भी एक समय में केवल k हैश ताल मौजूद होना चाहिए, जिससे स्मृति आवश्यकता कम हो जाती है।
माइकल काय

1
@MichaelKay: यदि आप O ( k ) समय में स्ट्रिंग के संभावित परिवर्तनों के हैश की गणना करना चाहते हैं तो यह काम नहीं करेगा । आपको अभी भी उन्हें कहीं स्टोर करने की आवश्यकता है। तो अगर आप एक बार में केवल एक स्थिति की जांच, ले जाएगा कश्मीर बार लंबे समय के रूप के रूप में यदि आप एक साथ सभी पदों की जाँच का उपयोग कर कश्मीर में कई hashtable प्रविष्टियों के रूप में कई बार। kO(k)kk
user21820

2

यहां बहुपद-हैश विधि की तुलना में अधिक मजबूत हैशटेबल दृष्टिकोण है। सबसे पहले रैंडम पॉजिटिव पूर्णांकों को उत्पन्न करें r 1 .. k जो हैशटेबल साइज M के लिए है । अर्थात्, 0 r मैं < एम । फिर प्रत्येक स्ट्रिंग हैश एक्स 1 .. कश्मीर को ( Σ k मैं = 1 एक्स मैं r मैं ) आधुनिक एम । वहाँ लगभग कुछ भी नहीं है एक विरोधी बहुत असमान टकराव का कारण बन सकता है, जब से आप आर 1 उत्पन्न करते हैं .. k रन-टाइम पर और के रूप में kkr1..kM0ri<Mx1..k(i=1kxiri)modMr1..kkबढ़ जाती है अलग तार के किसी भी जोड़ी की टक्कर की अधिकतम संभावना को जल्दी से चला जाता है । यह भी स्पष्ट है कि कैसे ( के ) समय में गणना करने के लिए एक स्ट्रिंग के साथ प्रत्येक स्ट्रिंग के लिए सभी संभव हैश बदल गए।1/MO(k)

क्या तुम सच में गारंटी वर्दी हैशिंग करना चाहते हैं, तो आप एक यादृच्छिक प्राकृतिक संख्या उत्पन्न कर सकते हैं की तुलना में कम एम प्रत्येक जोड़ी के लिए ( मैं , ) के लिए मैं से 1 करने के लिए k और हर किरदार के लिए सी , और फिर प्रत्येक स्ट्रिंग हैश x 1 .. k से ( k i = 1 r ( i , x i ) ) mod Mr(i,c)M(i,c)i1kcx1..k(i=1kr(i,xi))modM। तब किसी भी जोड़े के अलग-अलग तारों के टकराने की संभावना ठीक । यह दृष्टिकोण बेहतर है यदि आपका वर्ण सेट n की तुलना में अपेक्षाकृत छोटा है ।1/Mn


2

यहां पोस्ट किए गए बहुत सारे एल्गोरिदम हैश टेबल पर काफी कम जगह का उपयोग करते हैं। यहाँ एक है सहायक भंडारण हे ( ( एन एलजी n ) कश्मीर 2 ) क्रम सरल एल्गोरिथ्म।O(1)O((nlgn)k2)

चाल का उपयोग करने के लिए है , जो दो मानों के बीच एक तुलनित्र है एक और है कि रिटर्न सच अगर एक < b (कोषगत), जबकि अनदेखी कश्मीर वें चरित्र। तब एल्गोरिथ्म इस प्रकार है।Ck(a,b)aba<bk

सबसे पहले, स्ट्रिंग्स को नियमित रूप से सॉर्ट करें और किसी भी डुप्लिकेट को हटाने के लिए एक रैखिक स्कैन करें।

फिर, प्रत्येक :k

  1. तुलनित्र के रूप में साथ तार को क्रमबद्ध करें ।Ck

  2. स्ट्रिंग्स जो केवल में भिन्न होती हैं, अब आसन्न हैं और एक रैखिक स्कैन में पता लगाया जा सकता है।k


1

लंबाई k के दो तार , एक वर्ण में भिन्न, लंबाई l का एक उपसर्ग और लंबाई m का एक प्रत्यय जैसे कि k = l + m + 1

साइमन Prins द्वारा जवाब भंडारण सभी उपसर्ग / प्रत्यय संयोजन स्पष्ट रूप से, यानी करके इस encodes abcहो जाता है *bc, a*cऔर ab*। वह k = 3, l = 0,1,2 और m = 2,1,0 है।

जैसा कि वैलारमोर्गुलिस बताता है, आप एक उपसर्ग वृक्ष में शब्दों को व्यवस्थित कर सकते हैं। वहाँ भी बहुत समान प्रत्यय वृक्ष है। प्रत्येक उपसर्ग या प्रत्यय के नीचे पत्ती नोड्स की संख्या के साथ पेड़ को बढ़ाना काफी आसान है; नया शब्द डालने पर इसे O (k) में अपडेट किया जा सकता है।

जिस कारण से आप चाहते हैं कि इन भाई-बहनों की गिनती है, इसलिए आप जानते हैं, एक नया शब्द दिया गया है, चाहे आप एक ही प्रत्यय के साथ सभी तारों को सम्मिलित करना चाहते हैं या एक ही प्रत्यय के साथ सभी तारों को गणना करना चाहते हैं। उदाहरण के लिए इनपुट के रूप में "एबीसी" के लिए, संभावित उपसर्ग "", "ए" और "एब" हैं, जबकि संबंधित प्रत्यय "बीसी", "सी" और "" हैं। जैसा कि स्पष्ट है, छोटे प्रत्ययों के लिए उपसर्ग वृक्ष में भाई-बहनों की गणना करना बेहतर है और इसके विपरीत।

जैसा कि @inpoklum बताते हैं, यह निश्चित रूप से संभव है कि सभी तार एक ही k / 2 उपसर्ग साझा करें । यह इस दृष्टिकोण के लिए एक समस्या नहीं है; उपसर्ग वृक्ष k / 2 की गहराई तक प्रत्येक नोड के साथ k / 2 गहराई तक 100.000 लीफ डायोड के पूर्वज होंगे। नतीजतन, प्रत्यय के पेड़ का उपयोग (k / 2-1) गहराई तक किया जाएगा, जो अच्छा है क्योंकि तारों को उनके प्रत्ययों में अंतर करना पड़ता है, क्योंकि वे उपसर्ग साझा करते हैं।

[संपादित करें] एक अनुकूलन के रूप में, एक बार जब आप एक स्ट्रिंग के सबसे कम अद्वितीय उपसर्ग का निर्धारण कर लेते हैं, तो आप जानते हैं कि यदि एक अलग चरित्र है, तो यह उपसर्ग का अंतिम चरित्र होना चाहिए, और जब आप पास-डुप्लिकेट पाएंगे उपसर्ग की जाँच करना जो एक छोटा था। तो अगर "एबकेड" में सबसे छोटा अनोखा उपसर्ग "एबीसी" है, तो इसका मतलब है कि अन्य स्ट्रिंग्स हैं जो "एब्स" से शुरू होते हैं? " लेकिन "एबीसी" के साथ नहीं। यानी अगर वे केवल एक ही चरित्र में भिन्न होते, तो वह तीसरा चरित्र होता। अब आपको "abc? E" के लिए जाँच करने की आवश्यकता नहीं है।

उसी तर्क से, यदि आप पाएंगे कि "cde" एक अद्वितीय सबसे छोटा प्रत्यय है, तो आप जानते हैं कि आपको केवल लंबाई -2 "ab" उपसर्ग की जाँच करने की आवश्यकता है और लंबाई 1 या 3 उपसर्ग नहीं।

ध्यान दें कि यह विधि केवल एक वर्ण अंतर के लिए काम करती है और 2 वर्ण अंतरों के लिए सामान्यीकरण नहीं करती है, यह एक समान चरित्र को समान उपसर्गों और समान प्रत्ययों के बीच पृथक्करण होने से निर्भर करती है।


आप सुझाव दे रहे हैं कि प्रत्येक स्ट्रिंग के लिए और प्रत्येक 1 मैं k , हम नोड खोजने के पी [ रों 1 , ... , एस मैं - 1 ] length- करने के लिए इसी ( मैं - 1 ) उपसर्ग trie में उपसर्ग, और नोड एस [ रों मैं + 1 , ... , रों कश्मीर ] length- करने के लिए इसी ( कश्मीर - मैं - 1 )s1ikP[s1,,si1](i1)S[si+1,,sk](ki1)प्रत्यय ट्राइ में प्रत्यय (प्रत्येक को समय लगता है), और प्रत्येक के वंशजों की संख्या की तुलना करें, जिसमें से किसी के भी कम वंशज हैं, और फिर उस त्रि में बाकी स्ट्रिंग के लिए "जांच"? O(1)
j_random_hacker

1
आपके दृष्टिकोण का रनिंग टाइम क्या है? यह मुझे लगता है कि सबसे खराब स्थिति में यह द्विघात हो सकता है: विचार करें कि क्या होता है यदि प्रत्येक स्ट्रिंग एक ही वर्णों के साथ शुरू होता है और समाप्त होता है । k/4
डीडब्ल्यू

अनुकूलन का विचार चतुर और दिलचस्प है। क्या आपके पास mtaches की जांच करने का एक विशेष तरीका है? यदि "abcde" में सबसे छोटा अद्वितीय उपसर्ग "abc" है, तो इसका मतलब है कि हमें "ab-de" फॉर्म के कुछ अन्य स्ट्रिंग की जांच करनी चाहिए। क्या आपके पास ऐसा करने का एक विशेष तरीका है, जो कुशल होगा? परिणामस्वरूप चलने वाला समय क्या है?
DW

@DW: विचार यह है कि "ab? De" रूप में तार खोजने के लिए, आप उपसर्ग वृक्ष की जाँच करें कि "ab" के नीचे कितने पत्ती नोड्स मौजूद हैं और प्रत्यय ट्री में "de" के तहत कितने नोड मौजूद हैं, फिर चुनें दो सबसे छोटा। जब सभी तार एक ही k / 4 वर्णों के साथ शुरू और समाप्त होते हैं; इसका मतलब है कि दोनों पेड़ों में पहले k / 4 नोड्स में एक-एक बच्चा है। और हाँ, हर बार जब आपको उन पेड़ों की ज़रूरत होती है, तो उन्हें पीछे हटना पड़ता है जो एक O (n * k) कदम है।
MSalters

उपसर्ग त्रिक में "ab? De" फ़ॉर्म की एक स्ट्रिंग की जांच करने के लिए, यह "ab" के लिए नोड को प्राप्त करने के लिए पर्याप्त है, फिर इसके प्रत्येक बच्चे , जांचें कि क्या पथ "de" v के नीचे मौजूद है या नहीं । यही है, इन सबट्रीज़ में किसी अन्य नोड्स की गणना करने से परेशान न हों। इसमें O ( a h ) समय लगता है, जहाँ a वर्णमाला का आकार है और h त्रि में प्रारंभिक नोड की ऊँचाई है। है हे ( कश्मीर ) , इसलिए यदि वर्णमाला आकार है हे ( एन ) तो यह वास्तव में है हे ( एन कश्मीर )vvO(ah)ahhO(k)O(n)O(nk)कुल मिलाकर समय, लेकिन छोटे अक्षर आम हैं। बच्चों की संख्या (वंशज नहीं) महत्वपूर्ण है, साथ ही ऊंचाई भी।
j_random_hacker

1

बाल्टी में तारों को जमा करना एक अच्छा तरीका है (इस बारे में पहले से ही अलग-अलग उत्तर हैं)।

एक वैकल्पिक समाधान एक में दुकान तार करने के लिए हो सकता है क्रमबद्ध सूची। चाल को एक स्थानीय-संवेदनशील हैशिंग एल्गोरिथ्म द्वारा सॉर्ट करना है । यह एक हैश एल्गोरिथ्म है जो समान परिणाम देता है जब इनपुट समान है [1]।

हर बार जब आप एक स्ट्रिंग की जाँच करना चाहते हैं, तो आप अपने हैश की गणना करने और अपने क्रमबद्ध सूची में उस हैश की स्थिति देखने के (लेने सकता सरणियों या के लिए हे ( एन ) से जुड़ा हुआ सूचियों के लिए)। यदि आप पाते हैं कि पड़ोसी (सभी करीबी पड़ोसियों पर विचार कर रहे हैं, तो केवल उस स्थिति के +/- 1 के सूचकांक वाले) समान नहीं हैं (एक चरित्र से दूर) जो आपने अपना मैच पाया। यदि समान तार नहीं हैं, तो आप नई स्ट्रिंग को उस स्थान पर रख सकते हैं जिसे आपने पाया था (जो लिंक की गई सूचियों के लिए O ( 1 ) और सरणियों के लिए O ( n ) है)।O(log(n))O(n)O(1)O(n)

एक संभावित स्थानीय-संवेदनशील हैशिंग एल्गोरिथ्म निलिमिस्सा हो सकता है ( अजगर में उदाहरण के लिए खुला स्रोत कार्यान्वयन उपलब्ध है )।

[१]: ध्यान दें कि अक्सर हैश एल्गोरिदम, जैसे SHA1, विपरीत के लिए डिज़ाइन किए जाते हैं: समान के लिए बहुत अलग हैश का उत्पादन करते हैं, लेकिन समान इनपुट के लिए नहीं।

अस्वीकरण: ईमानदार होने के लिए, मैं व्यक्तिगत रूप से एक उत्पादन अनुप्रयोग के लिए नेस्टेड / ट्री-संगठित बाल्टी-समाधानों में से एक को लागू करूंगा। हालांकि, क्रमबद्ध सूची विचार ने मुझे एक दिलचस्प विकल्प के रूप में मारा। ध्यान दें कि यह एल्गोरिथ्म बहुत चुना हैश एल्गोरिथ्म पर निर्भर करता है। Nilsimsa एक एल्गोरिथ्म है जो मैंने पाया - हालांकि कई और भी हैं (उदाहरण के लिए TLSH, Ssdeep और Sdhash)। मैंने यह सत्यापित नहीं किया है कि निलिमिस्सा मेरे उल्लिखित एल्गोरिथ्म के साथ काम करता है।


1
दिलचस्प विचार है, लेकिन मुझे लगता है कि हमें दो सीमा के अलावा कुछ हैश पर कुछ सीमाएं लगानी पड़ सकती हैं, जब उनके इनपुट सिर्फ 1 वर्ण से भिन्न होते हैं - तो बस पड़ोसी के बजाय, हैश मूल्यों की सीमा के भीतर सब कुछ स्कैन करें। (एक हैश फ़ंक्शन का होना असंभव है जो 1 वर्ण द्वारा भिन्न होने वाले सभी संभावित जोड़े के लिए आसन्न हैश मान पैदा करता है । एक द्विआधारी वर्णमाला में लंबाई -2 तार पर विचार करें: 00, 01, 10 और 11. यदि एच (00)। h (10) और h (01) दोनों के समीप, तो यह उनके बीच होना चाहिए, जिस स्थिति में h (11) उन दोनों के समीप नहीं हो सकता है, और इसके विपरीत।)
j_random_hacker

पड़ोसियों को देखना पर्याप्त नहीं है। सूची abcd, acef, agcd पर विचार करें। एक मेल खाने वाली जोड़ी मौजूद है, लेकिन आपकी प्रक्रिया इसे नहीं ढूंढेगी, क्योंकि abcd agcd का पड़ोसी नहीं है।
डीडब्ल्यू

तुम दोनों सही हो! पड़ोसियों के साथ मेरा मतलब केवल "प्रत्यक्ष पड़ोसी" नहीं था, बल्कि करीबी पदों के "एक पड़ोस" के बारे में सोचा। मैंने निर्दिष्ट नहीं किया कि कितने पड़ोसियों को देखने की आवश्यकता है क्योंकि यह हैश एल्गोरिथ्म पर निर्भर करता है। लेकिन आप सही कह रहे हैं, मुझे शायद अपने जवाब में इस पर ध्यान देना चाहिए। धन्यवाद :)
tessi

1
"LSH ... समान संभावना के साथ समान" बाल्टी "के समान आइटम मैप -" क्योंकि यह संभावना एल्गोरिथ्म है, परिणाम की गारंटी नहीं है। तो यह टीएस पर निर्भर करता है कि क्या उसे 100% समाधान की आवश्यकता है या 99.9% पर्याप्त है।
बुलट

1

कोई समय और O ( n k ) स्थान को बढ़ाए गए प्रत्यय सरणियों ( LCP सरणी के साथ प्रत्यय सरणी ) का उपयोग करके प्राप्त कर सकता है जो निरंतर LCP (सबसे लंबे समय तक सामान्य उपसर्ग) क्वेरी (यानी दो बार) की अनुमति देता है एक स्ट्रिंग के सूचकांक, उन सूचकांकों पर शुरू होने वाले प्रत्ययों के सबसे लंबे उपसर्ग की लंबाई क्या है)। यहां, हम इस तथ्य का लाभ उठा सकते हैं कि सभी तार समान लंबाई के हैं। विशेष रूप से,O(nk+n2)O(nk)

  1. सभी स्ट्रिंग्स के बढ़े हुए प्रत्यय सरणी का निर्माण एक साथ करें। आज्ञा देना एक्स = एक्स 1x x एक्स एन जहां एक्स मैं , 1 मैं n संग्रह में एक स्ट्रिंग है। X के लिए प्रत्यय सरणी और LCP सरणी बनाएँ ।nX=x1.x2.x3....xnxi,1inX

  2. अब प्रत्येक शून्य-आधारित अनुक्रमण में स्थिति ( i - 1 ) k पर शुरू होता है । प्रत्येक स्ट्रिंग x i के लिए , LCP को स्ट्रिंग x j में से प्रत्येक के साथ लें जैसे कि j < i । यदि LCP x j के अंत से आगे जाता है तो x i = x j । अन्यथा, वहाँ एक बेमेल है (जैसे कि x मैं [ पी ] एक्स जे [ पी ]xi(i1)kxमैंxजेj<मैंxजेएक्समैं=एक्सजेएक्समैं[पी]एक्सजे[पी]); इस मामले में बेमेल के बाद संबंधित पदों पर शुरू होने वाला एक और एलसीपी लें। यदि दूसरा LCP के अंत से आगे जाता है तो x i और x j केवल एक वर्ण से भिन्न होता है; अन्यथा एक से अधिक बेमेल विवाह होते हैं।एक्सजेएक्समैंएक्सजे

    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)
                  }
                }
            }
        }
    }
    

आप SDSL लाइब्रेरी का उपयोग कर सकते हैं ताकि संक्षिप्त रूप में प्रत्यय सरणी का निर्माण किया जा सके और LCP प्रश्नों का उत्तर दिया जा सके।

विश्लेषण: बढ़ाया प्रत्यय सरणी बिल्डिंग की लंबाई में रेखीय है यानी हे ( एन कश्मीर ) । प्रत्येक LCP क्वेरी को निरंतर समय लगता है। इस प्रकार, क्वेरी समय O ( n 2 ) हैएक्सहे(n)हे(n2)

सामान्यीकरण: इस दृष्टिकोण को एक से अधिक मिसमैच के लिए भी सामान्यीकृत किया जा सकता है। सामान्य तौर पर, चलने का समय जहां q अनुमत बेमेल संख्या होती है।हे(n+क्षn2)क्ष

यदि आप संग्रह से एक स्ट्रिंग को हटाने की इच्छा रखते हैं, तो हर की जाँच करने के बजाय , आप केवल 'मान्य' j की सूची रख सकते हैं ।जे<मैंजे


हे(n2)k

हे(n+n2)हे(n2)हे(1)

मेरा कहना यह है कि प्रश्न लेखक के लिए k = 20..40 और इस तरह के छोटे तारों की तुलना करने के लिए केवल कुछ सीपीयू चक्रों की आवश्यकता होती है, इसलिए जानवर बल और आपके दृष्टिकोण के बीच व्यावहारिक अंतर शायद मौजूद नहीं है।
बुलट

1

हे(n)**bcdea*cde

आप कई सीपीयू / जीपीयू कोर के बीच काम को विभाजित करने के लिए इस दृष्टिकोण का उपयोग कर सकते हैं।


n=100,00040हे(n)

0

यह @SimonPrins के उत्तर में लघु संस्करण है जिसमें हैश शामिल नहीं है।

मान लें कि आपके किसी तार में तारांकन नहीं है:

  1. आकार की एक सूची बनाएं n जहां आपके प्रत्येक तार में होता है विविधताएं, प्रत्येक में एक अक्षर की जगह एक तारांकन (रनटाइम) होता है हे(n2))
  2. उस सूची को क्रमबद्ध करें (रनटाइम) हे(n2लॉगn))
  3. क्रमबद्ध सूची (रनटाइम) के बाद की प्रविष्टियों की तुलना करके डुप्लिकेट की जांच करें हे(n2))

पायथन में हैश के निहित उपयोग के साथ एक वैकल्पिक समाधान (सौंदर्य का विरोध नहीं कर सकता):

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)

धन्यवाद। कृपया उल्लेख भी करेंसटीक डुप्लिकेट की प्रतियां, और मैं +1 करूंगा। (हम्म, बस मैंने इस बारे में एक ही दावा किया हैहे(n) मेरे अपने जवाब में समय ... बेहतर तय है कि ...)
j_random_hacker

@j_random_hacker मुझे नहीं पता कि वास्तव में ओपी क्या रिपोर्ट करना चाहता है, इसलिए मैंने चरण 3 अस्पष्ट छोड़ दिया है, लेकिन मुझे लगता है कि यह किसी भी अतिरिक्त काम के साथ तुच्छ है या तो रिपोर्ट करने के लिए (क) किसी भी द्विआधारी / कोई डुप्लिकेट परिणाम नहीं (बी) एक सूची बिना किसी डुप्लिकेट के अधिकांश तारों के जोड़े अलग-अलग स्थिति में होते हैं। यदि हम ओपी को शाब्दिक रूप से लेते हैं ("... यह देखने के लिए कि क्या कोई दो तार ..."), तो (ए) वांछित प्रतीत होता है। इसके अलावा, अगर (बी) वांछित थे, तो निश्चित रूप से केवल जोड़े की सूची बना सकते हैंहे(n2) यदि सभी तार बराबर हैं
बैनाच

0

यहाँ 2+ बेमेल खोजक पर मेरा लेना है। ध्यान दें कि इस पोस्ट में मैं प्रत्येक स्ट्रिंग को परिपत्र के रूप में मानता हूं, सूचकांक में लंबाई 2 के फ़ेयर प्रतिस्थापन के बाद k-1प्रतीक होते हैं । और इंडेक्स पर लंबाई 2 का विकल्प एक ही है!str[k-1]str[0]-1

यदि हम Mलंबाई के दो तारों के बीच बेमेल हैं k, तो उनके पास कम से कम लंबाई के साथ मैचिंग सबस्ट्रिंग हैएलn(,)=/-1चूंकि, सबसे खराब स्थिति में, बेमेल प्रतीकों को Mसमान आकार के खंडों में विभाजित (परिपत्र) स्ट्रिंग । Fe के साथ k=20और M=4"सबसे खराब" मैच का पैटर्न हो सकता है abcd*efgh*ijkl*mnop*

अब, Mप्रतीकों के तार के बीच प्रतीकों तक सभी बेमेल को खोजने के लिए एल्गोरिथ्म k:

  • प्रत्येक के लिए मैं 0 से k-1 तक
    • सभी समूहों को समूहों में विभाजित करें str[i..i+L-1], जहां L = mlen(k,M)। Fe यदि L=4और आपके पास केवल 4 प्रतीकों (डीएनए से) की वर्णमाला है, तो यह 256 समूह बना देगा।
    • ~ 100 स्ट्रिंग्स से छोटे समूहों को ब्रूट-फोर्स एल्गोरिथम के साथ जांचा जा सकता है
    • बड़े समूहों के लिए, हमें माध्यमिक विभाजन करना चाहिए:
      • Lहमारे द्वारा पहले से मिलान किए गए समूह प्रतीकों में प्रत्येक स्ट्रिंग से निकालें
      • प्रत्येक j के लिए i-L + 1 से kL-1
        • सभी समूहों को समूहों में विभाजित करें str[i..i+L1-1], जहां L1 = mlen(k-L,M)। फे अगर k=20, M=4, alphabet of 4 symbolsहै, तो L=4और L1=3, इस 64 समूहों कर देगा।
        • बाकी को पाठक के लिए व्यायाम के रूप में छोड़ दिया गया है: डी

हम j0 से क्यों नहीं शुरू करते ? क्योंकि हमने पहले से ही इन समूहों को उसी मूल्य के साथ बनाया है i, इसलिए इसके साथ j<=i-Lनौकरी i और j मानों की अदला-बदली के समान होगी।

आगे अनुकूलन:

  • हर स्थिति में, स्ट्रिंग्स पर भी विचार करें str[i..i+L-2] & str[i+L]। यह केवल नौकरियों की मात्रा को दोगुना करता है, लेकिन L1 तक बढ़ाने की अनुमति देता है (यदि मेरा गणित सही है)। तो, 256 समूहों के बजाय, आप 1024 समूहों में डेटा विभाजित करेंगे।
  • अगर कुछ एल[मैं]बहुत छोटा हो जाता है, हम हमेशा *चाल का उपयोग कर सकते हैं : प्रत्येक में मैं के लिए 0..k-1, प्रत्येक स्ट्रिंग से i'th प्रतीक को हटा दें और M-1लंबाई के उन तारों में बेमेल खोज की नौकरी बनाएँ k-1

0

मैं हर रोज अल्गोस के आविष्कार और अनुकूलन पर काम करता हूं, इसलिए यदि आपको हर अंतिम प्रदर्शन की आवश्यकता है, तो यह योजना है:

  • *प्रत्येक स्थिति में स्वतंत्र रूप से जाँच करें , यानी एकल नौकरी प्रसंस्करण n*kस्ट्रिंग वेरिएंट के बजाय - kस्वतंत्र नौकरी शुरू करें प्रत्येक जाँच nस्ट्रिंग। आप इन kनौकरियों को कई सीपीयू / जीपीयू कोर के बीच फैला सकते हैं । यह विशेष रूप से महत्वपूर्ण है यदि आप 2+ चार डिफ की जाँच करने जा रहे हैं। छोटी नौकरी का आकार भी कैश इलाके में सुधार करेगा, जो स्वयं कार्यक्रम को 10x तेज बना सकता है।
  • यदि आप हैश टेबल का उपयोग करने जा रहे हैं, तो रैखिक परिवीक्षा और ~ 50% लोड फैक्टर को नियोजित करने वाले अपने स्वयं के कार्यान्वयन का उपयोग करें। यह तेजी से और लागू करने के लिए बहुत आसान है। या खुले पते के साथ मौजूदा कार्यान्वयन का उपयोग करें। एसटीएल हैश टेबल अलग चेनिंग के उपयोग के कारण धीमी हैं।
  • आप @AlexReynolds द्वारा प्रस्तावित 3-राज्य ब्लूम फ़िल्टर (0/1/1 + घटनाओं को अलग करते हुए) का उपयोग करके डेटा को प्रीफ़िल्टर करने का प्रयास कर सकते हैं।
  • प्रत्येक i से 0 से k-1 के लिए निम्नलिखित कार्य चलाते हैं:
    • प्रत्येक स्ट्रिंग के 4-5 बाइट हैश ( *i-th स्थिति के साथ) और स्ट्रिंग इंडेक्स वाले 8-बाइट संरचना उत्पन्न करें , और फिर या तो उन्हें सॉर्ट करें या इन रिकॉर्ड्स से हैश तालिका बनाएँ।

छँटाई के लिए, आप निम्न कॉम्बो आज़मा सकते हैं:

  • पहले पास में 64-256 एमएसएल मूलांक सॉर्ट है जो टीएलबी ट्रिक को नियोजित करता है
  • दूसरा पास 256-1024 तरीके से एमएसडी मूलांक छंटनी w / o TLB चाल (कुल 64K तरीके) है
  • शेष असंगतताओं को ठीक करने के लिए तीसरा पास प्रविष्टि प्रकार है
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.