हैशटेबल्स कैसे टकराव से निपटते हैं?


97

मैंने अपनी डिग्री कक्षाओं में सुना है कि HashTableयदि नई कुंजी प्रविष्टि दूसरे के साथ टकराती है तो वह 'अगली उपलब्ध' बाल्टी में एक नई प्रविष्टि देगी।

HashTableयदि टकराव कुंजी के साथ एक को वापस बुलाते समय अभी भी सही मान कैसे लौटाएगा?

मैं यह सोचते हैं रहा है कि Keysकर रहे हैं Stringटाइप करें और hashCode()रिटर्न डिफ़ॉल्ट कहना जावा द्वारा उत्पन्न।

अगर मैं अपने खुद के हैशिंग समारोह को लागू करने और एक लुक-अप तालिका (यानी एक के हिस्से के रूप में इसका इस्तेमाल HashMapया Dictionary), क्या रणनीति टकराव से निपटने के लिए मौजूद हैं?

मैंने प्राइम नंबर से संबंधित नोट्स भी देखे हैं! जानकारी Google खोज से इतनी स्पष्ट नहीं है।

जवाबों:


92

हैश टेबल दो तरीकों में से एक में टकराव से निपटते हैं।

विकल्प 1: प्रत्येक बाल्टी में उन तत्वों की एक लिंक्ड सूची होती है जो उस बाल्टी में हैशेड होते हैं। यही कारण है कि एक खराब हैश फ़ंक्शन हैश तालिकाओं में बहुत धीमी गति से लुकअप कर सकता है।

विकल्प 2: यदि हैश तालिका प्रविष्टियाँ पूर्ण हैं, तो हैश तालिका में बाल्टी की संख्या को बढ़ा सकती है और फिर तालिका के सभी तत्वों को पुनर्वितरित कर सकती है। हैश फ़ंक्शन एक पूर्णांक लौटाता है और हैश तालिका को हैश फ़ंक्शन का परिणाम लेना होता है और इसे टेबल के आकार के विरुद्ध मॉड करना होता है जिस तरह से यह सुनिश्चित हो सकता है कि यह बाल्टी को मिलेगा। इसलिए आकार में वृद्धि करके, यह मॉडुलो गणना को फिर से चलाएगा और चलाएगा जो यदि आप भाग्यशाली हैं तो वस्तुओं को अलग बाल्टी में भेज सकते हैं।

जावा अपने हैश टेबल कार्यान्वयन में विकल्प 1 और 2 दोनों का उपयोग करता है।


1
पहले विकल्प के मामले में, क्या कोई कारण है कि एक सरणी या यहां तक ​​कि एक द्विआधारी खोज पेड़ के बजाय एक लिंक की गई सूची का उपयोग किया जाता है?

1
उपरोक्त व्याख्या उच्च स्तर की है, मुझे नहीं लगता कि इससे जुड़ी सूची बनाम सरणी के रूप में बहुत अंतर पड़ता है। मुझे लगता है कि एक बाइनरी सर्च ट्री ओवरकिल होगा। इसके अलावा, अगर आपको लगता है कि आप समवर्ती हाशपा और अन्य चीजों में खोदते हैं, तो कोई भी निम्न स्तर का विवरण लागू करता है, जो प्रदर्शन में अंतर ला सकता है, कि ऊपर उच्च स्तर की व्याख्या का कोई हिसाब नहीं है।
एम्स

2
यदि चाभी का उपयोग किया जाता है, जब एक कुंजी दी जाती है, तो हम कैसे जानते हैं कि किस आइटम को वापस प्राप्त करना है?
ChaoSXDemon

1
@ChaoSXDemon आप कुंजी द्वारा श्रृंखला में सूची को पार कर सकते हैं, डुप्लिकेट कुंजियाँ समस्या नहीं हैं समस्या दो अलग-अलग कुंजियाँ हैं जिनके पास हैशकोड है।
ams

1
@ जाम: कौन सा पसंद किया जाता है? क्या हाश टकराव की कोई सीमा है, जिसके बाद 2 अंक JAVA द्वारा निष्पादित किया जाता है?
शशांक विवेक

77

जब आपने "हैश टेबल के बारे में बात की है, तो नई कुंजी प्रविष्टि 'अगली उपलब्ध' बाल्टी में रखी जाएगी यदि नई कुंजी प्रविष्टि दूसरे के साथ टकराती है।", आप हैश तालिका के टकराव समाधान की ओपन एड्रेसिंग रणनीति के बारे में बात कर रहे हैं ।


टक्कर को हल करने के लिए हैश तालिका के लिए कई रणनीतियाँ हैं।

पहले प्रकार की बड़ी विधि के लिए यह आवश्यक है कि कुंजियाँ (या उनके पास) तालिका में संग्रहीत की जाएं, साथ में संबंधित मान, जो आगे शामिल हैं:

  • अलग से जंजीर

यहां छवि विवरण दर्ज करें

  • खुला संबोधन

यहां छवि विवरण दर्ज करें

  • सहिष्णु हैशिंग
  • कोयल हैशिंग
  • रॉबिन हूड हैशिंग
  • 2-चुनाव हैशिंग
  • होपस्कॉच हैशिंग

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

  • सभी प्रविष्टियों को कॉपी करके आकार बदलना
  • वृद्धिशील आकार बदलना
  • मोनोटोनिक कुंजी

संपादित करें : ऊपर wiki_hash_table से उधार लिया गया है , जहां आपको अधिक जानकारी प्राप्त करने के लिए एक नज़र रखना चाहिए।


3
"[...] के लिए आवश्यक है कि चाबियां (या उन्हें संकेत) तालिका में संग्रहीत की जाएं, साथ में संबंधित मूल्य"। धन्यवाद, यह वह बिंदु है जो मूल्यों को संग्रहीत करने के तंत्र के बारे में पढ़ते समय हमेशा स्पष्ट नहीं होता है।
mtone

27

टक्कर को संभालने के लिए कई तकनीकें उपलब्ध हैं। मैं उनमें से कुछ की व्याख्या करूंगा

जंजीर: जंजीरों में हम मूल्यों को संग्रहीत करने के लिए सरणी अनुक्रमित का उपयोग करते हैं। यदि दूसरे मान का हैश कोड भी उसी इंडेक्स की ओर इशारा करता है तो हम उस इंडेक्स वैल्यू को एक लिंक्ड लिस्ट से बदल देते हैं और उस इंडेक्स की ओर इशारा करने वाले सभी मान लिंक किए गए लिस्ट में जमा हो जाते हैं और वास्तविक एरे इंडेक्स पॉइंट लिंक्ड लिस्ट के प्रमुख पर आ जाता है। लेकिन अगर सरणी के सूचकांक को इंगित करने वाला केवल एक हैश कोड है तो मान उस सूचकांक में सीधे संग्रहीत किया जाता है। मूल्यों को प्राप्त करते समय एक ही तर्क लागू किया जाता है। इसका उपयोग Java HashMap / Hashtable में टकराव से बचने के लिए किया जाता है।

रैखिक जांच: इस तकनीक का उपयोग तब किया जाता है, जब हमारे पास तालिका में अधिक संचय होता है, जो संचित किए जाने वाले मानों से अधिक होता है। रैखिक जांच तकनीक एक खाली स्लॉट मिलने तक बढ़े रहने की अवधारणा पर काम करती है। छद्म कोड इस तरह दिखता है:

index = h(k) 

while( val(index) is occupied) 

index = (index+1) mod n

डबल हैशिंग तकनीक: इस तकनीक में हम दो हैशिंग फ़ंक्शन एच 1 (के) और एच 2 (के) का उपयोग करते हैं। यदि h1 (k) पर स्लॉट पर कब्जा है, तो दूसरा हैशिंग फ़ंक्शन h2 (k) सूचकांक को बढ़ाने के लिए उपयोग किया जाता है। छद्म कोड इस तरह दिखता है:

index = h1(k)

while( val(index) is occupied)

index = (index + h2(k)) mod n

रैखिक जांच और डबल हैशिंग तकनीक ओपन एड्रेसिंग तकनीक का हिस्सा हैं और इसका उपयोग केवल तभी किया जा सकता है जब उपलब्ध स्लॉट्स को जोड़े जाने वाली वस्तुओं की संख्या से अधिक हो। यह चिनिंग की तुलना में कम मेमोरी लेता है क्योंकि यहां कोई अतिरिक्त संरचना का उपयोग नहीं किया गया है लेकिन बहुत धीमी गति से चलने के कारण यह तब तक होता है जब तक हम एक खाली स्लॉट नहीं पाते हैं। जब किसी आइटम को स्लॉट से हटा दिया जाता है तो हम ओपन एड्रेसिंग तकनीक में भी यह संकेत करने के लिए एक कब्र लगाते हैं कि आइटम को यहां से हटा दिया गया है, इसीलिए यह खाली है।

अधिक जानकारी के लिए इस साइट को देखें ।


18

मैं आपको इस ब्लॉग पोस्ट को पढ़ने के लिए दृढ़ता से सुझाव देता हूं जो हाल ही में हैकरन्यूज पर दिखाई दिया था: हाशपॅ जावा में कैसे काम करता है

संक्षेप में, इसका उत्तर है

यदि दो अलग-अलग हाशप की प्रमुख वस्तुओं में समान हैशकोड होगा तो क्या होगा?

उन्हें एक ही बाल्टी में संग्रहीत किया जाएगा, लेकिन लिंक की गई सूची का कोई अगला नोड नहीं। और HashMap में सही कुंजी मूल्य जोड़ी की पहचान करने के लिए कुंजी बराबरी () विधि का उपयोग किया जाएगा।


3
HashMaps बहुत दिलचस्प हैं और वे गहरे जाते हैं! :)
एलेक्स

1
मुझे लगता है कि सवाल हैशटेबल्स के बारे में नहीं हैशपॉप
प्रशांत शुभम

10

मैंने अपनी डिग्री कक्षाओं में सुना है कि एक हैशटेबल 'अगली उपलब्ध' बाल्टी में एक नई प्रविष्टि रखेगा यदि नई कुंजी प्रविष्टि दूसरे के साथ टकराती है।

यह वास्तव में सच ओरेकल JDK के लिए कम से कम (यह नहीं है, है एक कार्यान्वयन विस्तार है कि एपीआई के विभिन्न कार्यान्वयन के बीच भिन्न हो सकता है)। इसके बजाय, प्रत्येक बकेट में जावा 8 से पहले की प्रविष्टियों की एक लिंक की गई सूची और जावा 8 या उससे ऊपर का संतुलित पेड़ शामिल है।

फिर कैसे हैशटेबल अभी भी सही मान लौटाएगा यदि टक्कर की कुंजी के साथ एक वापस बुलाते समय यह टक्कर होती है?

यह equals()वास्तव में मिलान प्रविष्टि खोजने के लिए उपयोग करता है ।

अगर मैं अपने स्वयं के हैशिंग फ़ंक्शन को लागू करता हूं और इसे लुक-अप टेबल (यानी हाशप या डिक्शनरी) के हिस्से के रूप में उपयोग करता हूं, तो टकराव से निपटने के लिए क्या रणनीति मौजूद है?

विभिन्न फायदे और नुकसान के साथ विभिन्न टकराव से निपटने की रणनीतियां हैं। हैश टेबल पर विकिपीडिया का प्रवेश एक अच्छा अवलोकन देता है।


यह दोनों के लिए सच है Hashtableऔर HashMapसूर्य / ओरेकल द्वारा JDK 1.6.0_22 में।
निकिता रायबाक

@ निकिता: हैशटेबल के बारे में निश्चित नहीं है, और मेरे पास अभी स्रोतों तक पहुंच नहीं है, लेकिन मैं 100% निश्चित हूं हशपैन ने अपने हर डिबगर में कभी भी देखे गए हर एक संस्करण में चेनिंग और रैखिक परिवीक्षा का उपयोग नहीं किया है।
माइकल बॉर्गवर्ड

@ मीकल वेल, मैं अभी से हैशपॉप के स्रोत को देख रहा हूं public V get(Object key)(ऊपर जैसा संस्करण)। यदि आप सटीक संस्करण खोजते हैं, जहाँ वे लिंक की गई सूची दिखाई देती है, तो मुझे जानने में दिलचस्पी होगी।
निकिता रायबाक

@ निकी: मैं अब एक ही विधि देख रहा हूं, और मैं इसे लूप के लिए Entryवस्तुओं की एक लिंक्ड सूची के माध्यम से पुनरावृति के लिए देख रहा हूं :localEntry = localEntry.next
माइकल बोर्गवर्ड

@ मिचेल क्षमा करें, यह मेरी गलती है। मैंने कोड की गलत तरीके से व्याख्या की। स्वाभाविक रूप से, e = e.nextनहीं है ++index। +1
निकिता रायबाक

7

Java 8 के बाद से अपडेट: Java 8 टकराव से निपटने के लिए स्व-संतुलित पेड़ का उपयोग करता है, लुकअप के लिए O (n) से O (लॉग एन) तक के सबसे खराब मामले में सुधार करता है। स्वयं-संतुलित वृक्ष का उपयोग जावा 8 में चेनिंग पर सुधार के रूप में पेश किया गया था (जावा 7 तक उपयोग किया जाता है), जो एक लिंक्ड-लिस्ट का उपयोग करता है, और लुकअप के लिए ओ (एन) का सबसे खराब मामला है (क्योंकि इसे पीछे करने की आवश्यकता है सूचि)

अपने प्रश्न के दूसरे भाग का उत्तर देने के लिए, किसी दिए गए तत्व को हैशमैप के अंतर्निहित सरणी में मैपिंग के द्वारा सम्मिलन किया जाता है, हालांकि, जब कोई टक्कर होती है, तो सभी तत्वों को अभी भी संरक्षित किया जाना चाहिए (एक द्वितीयक डेटा-संरचना में संग्रहीत) , और सिर्फ अंतर्निहित सरणी में प्रतिस्थापित नहीं)। यह आमतौर पर प्रत्येक ऐरे-कंपोनेंट (स्लॉट) को सेकेंडरी डेटास्ट्रक्चर (उर्फ बकेट) बनाकर किया जाता है, और दिए गए एरे-इंडेक्स पर रहने वाली बकेट में तत्व जोड़ा जाता है (यदि कुंजी पहले से ही बकेट में मौजूद नहीं है,) जो मामला बदल दिया गया है)।

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

कोलिशन-हैंडलिंग, ओ (1) से ओ (1) के सम्मिलन और लुकअप के मामले में सबसे खराब स्थिति का प्रदर्शन करता है, ओ (एन) को जंजीर से जोड़ने के मामले में (एक लिंक्ड-लिस्ट को द्वितीयक डेटास्ट्रक्चर के रूप में उपयोग किया जाता है) और ओ (लॉग एन) स्व-संतुलित वृक्ष के लिए।

संदर्भ:

जावा 8 उच्च टकराव के मामले में हाशप ऑब्जेक्ट्स के निम्नलिखित सुधार / परिवर्तन के साथ आया है।

  • जावा 7 में जोड़ा गया वैकल्पिक स्ट्रिंग हैश फ़ंक्शन हटा दिया गया है।

  • बड़ी संख्या में टकराने वाली कुंजियों वाली बाल्टी कुछ थ्रेशोल्ड तक पहुंचने के बाद एक लिंक किए गए सूची के बजाय एक संतुलित पेड़ में अपनी प्रविष्टियों को संग्रहीत करेगी।

उपरोक्त परिवर्तन सबसे खराब स्थिति में ओ (लॉग (एन)) का प्रदर्शन सुनिश्चित करते हैं ( https://www.nagarro.com/en/blog/post/24/performance-improvement-for-hashmap-in-java-8 )


क्या आप समझा सकते हैं कि एक लिंक्ड-सूची हाशप के लिए सबसे खराब केस प्रविष्टि केवल O (1) है, और O (N) नहीं? मुझे लगता है कि यदि आपके पास गैर-डुप्लिकेट कुंजी के लिए 100% की टक्कर की दर है, तो आप लिंक की गई सूची के अंत को खोजने के लिए हैशपॉप में हर वस्तु को पार करना चाहते हैं, है ना? मैं क्या खो रहा हूँ?
mbm29414

हैशमाप कार्यान्वयन के विशिष्ट मामले में आप वास्तव में सही हैं, लेकिन इसलिए नहीं कि आपको सूची का अंत खोजने की आवश्यकता है। एक सामान्य मामले में लिंक्ड-लिस्ट कार्यान्वयन में, एक पॉइंटर को सिर और पूंछ दोनों में जमा किया जाता है, और इसलिए अगली नोड को सीधे पूंछ से जोड़कर ओ (1) में सम्मिलन किया जा सकता है, लेकिन हैशमैप के मामले में, सम्मिलित विधि कोई डुप्लिकेट सुनिश्चित करने की आवश्यकता नहीं है, और इस प्रकार यह जांचने के लिए सूची की खोज करनी चाहिए कि क्या तत्व पहले से मौजूद है, और इसलिए हम O (n) के साथ समाप्त होते हैं। और इसलिए यह एक लिंक-सूची पर लगाई गई संपत्ति है जो O (N) का कारण बन रही है। मैं अपने उत्तर में सुधार करूंगा :)
डैनियल वालैंड

4

यह देखने के लिए बराबरी पद्धति का उपयोग करेगा कि क्या कुंजी मौजूद है और विशेष रूप से अगर एक ही बाल्टी में एक से अधिक तत्व हैं।


4

जैसा कि कुछ भ्रम है कि कौन सा एल्गोरिथ्म जावा के हैशपॉप का उपयोग कर रहा है (सूर्य / ओरेकल / ओपनजेडके कार्यान्वयन में), यहां प्रासंगिक स्रोत कोड स्निपेट्स (ओपेनजेडके, 1.6.0_20, उबंटू से):

/**
 * Returns the entry associated with the specified key in the
 * HashMap.  Returns null if the HashMap contains no mapping
 * for the key.
 */
final Entry<K,V> getEntry(Object key) {
    int hash = (key == null) ? 0 : hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

यह विधि (पंक्तियों 355 से 371 तक है) को कहा जाता है जब तालिका में प्रविष्टि की तलाश की जाती है, उदाहरण के लिए get(), containsKey()और कुछ अन्य। यहां लूप के लिए प्रविष्टि ऑब्जेक्ट्स द्वारा बनाई गई लिंक की गई सूची से गुजरती है।

यहाँ प्रविष्टि ऑब्जेक्ट्स के लिए कोड (लाइनें 691-705 + 759):

static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;

    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

  // (methods left away, they are straight-forward implementations of Map.Entry)

}

इसके ठीक बाद addEntry()विधि आती है :

/**
 * Adds a new entry with the specified key, value and hash code to
 * the specified bucket.  It is the responsibility of this
 * method to resize the table if appropriate.
 *
 * Subclass overrides this to alter the behavior of put method.
 */
void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    if (size++ >= threshold)
        resize(2 * table.length);
}

यह पुरानी पहली प्रविष्टि (या अशक्त, यदि ऐसा नहीं है) के लिंक के साथ, बाल्टी के मोर्चे पर नई प्रविष्टि जोड़ता है । इसी तरह,removeEntryForKey() विधि सूची के माध्यम से जाती है और केवल एक प्रविष्टि को हटाने का ख्याल रखती है, जिससे बाकी सूची बरकरार रहती है।

इसलिए, यहां प्रत्येक बाल्टी के लिए एक लिंक प्रविष्टि सूची है, और मुझे बहुत संदेह है कि यह इस से बदल _20गया _22, क्योंकि यह 1.2 पर इस तरह से था।

(यह कोड 1997-2007 सन माइक्रोसिस्टम्स है, और जीपीएल के तहत उपलब्ध है, लेकिन कॉपी करने के लिए बेहतर है कि मूल फ़ाइल का उपयोग करें, जो सूर्य / ओरेकल से प्रत्येक JDK में src.zip में निहित है, और OpenJDK में भी है।)


1
मैंने इसे समुदाय विकी के रूप में चिह्नित किया है , क्योंकि यह वास्तव में एक उत्तर नहीं है, अन्य उत्तरों के लिए अधिक चर्चा का विषय है। टिप्पणियों में बस ऐसे कोड उद्धरणों के लिए पर्याप्त जगह नहीं है।
पाओलो एबरमन

3

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

interface Hashable {
  int getHash();
}

और यदि आप चाहें तो इसे कुंजियों द्वारा कार्यान्वित करने के लिए बाध्य करें।

public class Hashtable<K, V> {
    private static class Entry<K,V> {
        private final K key;
        private final V val;

        Entry(K key, V val) {
            this.key = key;
            this.val = val;
        }
    }

    private static int BUCKET_COUNT = 13;

    @SuppressWarnings("unchecked")
    private List<Entry>[] buckets = new List[BUCKET_COUNT];

    public Hashtable() {
        for (int i = 0, l = buckets.length; i < l; i++) {
            buckets[i] = new ArrayList<Entry<K,V>>();
        }
    }

    public V get(K key) {
        int b = key.hashCode() % BUCKET_COUNT;
        List<Entry> entries = buckets[b];
        for (Entry e: entries) {
            if (e.key.equals(key)) {
                return e.val;
            }
        }
        return null;
    }

    public void put(K key, V val) {
        int b = key.hashCode() % BUCKET_COUNT;
        List<Entry> entries = buckets[b];
        entries.add(new Entry<K,V>(key, val));
    }
}

2

टकराव के समाधान के लिए विभिन्न तरीके हैं। उनमें से कुछ हैं अलग-अलग चेनिंग, ओपन एड्रेसिंग, रॉबिन हुड हैशिंग, कुक्कू हाशिंग आदि।

हाश तालिकाओं में टकरावों को हल करने के लिए जावा अलग चाइनिंग का उपयोग करता है। यह कैसे होता है इसके लिए एक शानदार लिंक है: http://javapapers.com/core-java/java-hashtable/

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