बीआईटी: एक द्विआधारी अनुक्रमित पेड़ के पीछे अंतर्ज्ञान क्या है और इसके बारे में कैसे सोचा गया था?


99

एक बाइनरी अनुक्रमित पेड़ में अन्य डेटा संरचनाओं की तुलना में बहुत कम या अपेक्षाकृत कोई साहित्य नहीं है। एकमात्र स्थान जहां इसे पढ़ाया जाता है वह टॉपकोडर ट्यूटोरियल है । यद्यपि ट्यूटोरियल सभी स्पष्टीकरणों में पूर्ण है, मैं इस तरह के पेड़ के पीछे अंतर्ज्ञान को नहीं समझ सकता हूं? इसका आविष्कार कैसे हुआ? इसकी शुद्धता का वास्तविक प्रमाण क्या है?


4
विकिपीडिया पर एक लेख में दावा किया गया है कि इन्हें फेनविक ट्री कहा जाता है ।
डेविड हार्कस

2
@ डेविडहर्कनेस- पीटर फेनविक ने डेटा संरचना का आविष्कार किया, इसलिए उन्हें कभी-कभी फेनविक पेड़ कहा जाता है। अपने मूल पत्र में ( citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.14.8917 पर पाया गया ), उन्होंने उन्हें द्विआधारी अनुक्रमित पेड़ों के रूप में संदर्भित किया। दो शब्दों को अक्सर एक दूसरे के साथ इस्तेमाल किया जाता है।
templatetypedef

1
निम्नलिखित उत्तर द्विआधारी अनुक्रमित पेड़ों cs.stackexchange.com/questions/42811/… का एक बहुत अच्छा "दृश्य" अंतर्ज्ञान बताता है
रबीह कोदिह

1
मुझे पता है कि आप कैसा महसूस करते हैं, मैंने पहली बार टॉपकोडर लेख पढ़ा, यह सिर्फ जादू की तरह लग रहा था।
रॉकस्टार

जवाबों:


168

सहज रूप से, आप एक द्विआधारी अनुक्रमित पेड़ को एक द्विआधारी वृक्ष के संकुचित प्रतिनिधित्व के रूप में सोच सकते हैं जो स्वयं एक मानक सरणी प्रतिनिधित्व का अनुकूलन है। यह उत्तर एक संभावित व्युत्पत्ति में जाता है।

उदाहरण के लिए, मान लीजिए कि आप कुल 7 अलग-अलग तत्वों के लिए संचयी आवृत्तियों को संग्रहीत करना चाहते हैं। आप सात बाल्टियों को लिखकर शुरू कर सकते हैं जिसमें संख्याएँ वितरित की जायेंगी:

[   ] [   ] [   ] [   ] [   ] [   ] [   ]
  1     2     3     4     5     6     7

अब, मान लीजिए कि संचयी आवृत्तियों कुछ इस तरह दिखती हैं:

[ 5 ] [ 6 ] [14 ] [25 ] [77 ] [105] [105]
  1     2     3     4     5     6     7

सरणी के इस संस्करण का उपयोग करते हुए, आप उस स्थान पर संग्रहीत संख्या के मूल्य को बढ़ाकर किसी भी तत्व की संचयी आवृत्ति में वृद्धि कर सकते हैं, फिर बाद में आने वाली हर चीज की आवृत्तियों को बढ़ा सकते हैं। उदाहरण के लिए, 3 की संचयी आवृत्ति को 7 से बढ़ाने के लिए, हम 3 या उसके बाद की स्थिति में सरणी में प्रत्येक तत्व में 7 जोड़ सकते हैं, जैसा कि यहाँ दिखाया गया है:

[ 5 ] [ 6 ] [21 ] [32 ] [84 ] [112] [112]
  1     2     3     4     5     6     7

इसके साथ समस्या यह है कि ऐसा करने के लिए O (n) समय लगता है, जो n बड़ा है तो बहुत धीमा है।

एक तरीका है कि हम इस ऑपरेशन को बेहतर बनाने के बारे में सोच सकते हैं कि हम बाल्टी में क्या स्टोर करते हैं, इसे बदलना होगा। दिए गए बिंदु तक संचयी आवृत्ति को संग्रहीत करने के बजाय, आप इसके बजाय केवल उस राशि को संग्रहीत करने के बारे में सोच सकते हैं जो वर्तमान आवृत्ति पिछली बाल्टी के सापेक्ष बढ़ी है। उदाहरण के लिए, हमारे मामले में, हम उपरोक्त बाल्टी को फिर से लिखेंगे:

Before:
[ 5 ] [ 6 ] [21 ] [32 ] [84 ] [112] [112]
  1     2     3     4     5     6     7

After:
[ +5] [ +1] [+15] [+11] [+52] [+28] [ +0]
  1     2     3     4     5     6     7

अब, हम उस बाल्टी में उचित मात्रा में जोड़कर O (1) समय में एक बाल्टी के भीतर आवृत्ति बढ़ा सकते हैं। हालाँकि, लुकअप करने की कुल लागत अब O (n) हो जाती है, क्योंकि हमें सभी छोटी बाल्टियों में मानों को समेट कर बाल्टी में कुल को पुनः स्थापित करना होता है।

एक बाइनरी अनुक्रमित पेड़ के लिए हमें यहां से प्राप्त करने वाली पहली प्रमुख अंतर्दृष्टि निम्नलिखित है: किसी विशेष तत्व से पहले सरणी तत्वों के योग को लगातार पुन: विभाजित करने के बजाय, यदि हम विशिष्ट सभी तत्वों के कुल योग को पूर्वगामी बनाते हैं, तो क्या होगा? अनुक्रम में अंक? यदि हम ऐसा कर सकते हैं, तो हम इन प्री-कॉम्प्लेक्स रकम के सही संयोजन को जोड़कर एक बिंदु पर संचयी राशि का पता लगा सकते हैं।

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

             4
          /     \
         2       6
        / \     / \
       1   3   5   7

अब, हम उस नोड और इसके बाएँ उप-योग सहित सभी मानों के संचयी योग को संग्रहीत करके प्रत्येक नोड को बढ़ा सकते हैं। उदाहरण के लिए, हमारे मूल्यों को देखते हुए, हम निम्नलिखित संग्रह करेंगे:

Before:
[ +5] [ +1] [+15] [+11] [+52] [+28] [ +0]
  1     2     3     4     5     6     7

After:
                 4
               [+32]
              /     \
           2           6
         [ +6]       [+80]
         /   \       /   \
        1     3     5     7
      [ +5] [+15] [+52] [ +0]

इस पेड़ की संरचना को देखते हुए, संचयी योग को एक बिंदु तक निर्धारित करना आसान है। यह विचार निम्नलिखित है: हम एक काउंटर को बनाए रखते हैं, शुरू में 0, फिर एक सामान्य बाइनरी सर्च करते हैं, जब तक कि हम नोड को प्रश्न में नहीं पाते हैं। जैसा कि हम ऐसा करते हैं, हम निम्न भी करते हैं: किसी भी समय हम सही स्थानांतरित करते हैं, हम काउंटर पर वर्तमान मूल्य में भी जोड़ते हैं।

उदाहरण के लिए, मान लें कि हम 3. के लिए योग देखना चाहते हैं। ऐसा करने के लिए, हम निम्नलिखित कार्य करते हैं:

  • रूट (4) पर शुरू करें। काउंटर 0 है।
  • नोड (2) पर जाएं। काउंटर 0 है।
  • नोड (3) के दाईं ओर जाएं। काउंटर 0 + 6 = 6 है।
  • नोड (3) का पता लगाएं। काउंटर 6 + 15 = 21 है।

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

  • नोड (3) पर शुरू करें। काउंटर 15 है।
  • नोड (2) के ऊपर की ओर जाएं। काउंटर 15 + 6 = 21 है।
  • नोड (4) के ऊपर की ओर जाएं। काउंटर 21 है।

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

उदाहरण के लिए, नोड 1 की आवृत्ति को पांच से बढ़ाना, हम निम्नलिखित करेंगे:

                 4
               [+32]
              /     \
           2           6
         [ +6]       [+80]
         /   \       /   \
      > 1     3     5     7
      [ +5] [+15] [+52] [ +0]

नोड 1 पर शुरू, प्राप्त करने के लिए इसकी आवृत्ति 5 से बढ़ाएँ

                 4
               [+32]
              /     \
           2           6
         [ +6]       [+80]
         /   \       /   \
      > 1     3     5     7
      [+10] [+15] [+52] [ +0]

अब, इसके मूल में जाएं:

                 4
               [+32]
              /     \
         > 2           6
         [ +6]       [+80]
         /   \       /   \
        1     3     5     7
      [+10] [+15] [+52] [ +0]

हमने एक बाएं बच्चे को ऊपर की ओर लिंक किया है, इसलिए हम इस नोड की आवृत्ति को भी बढ़ाते हैं:

                 4
               [+32]
              /     \
         > 2           6
         [+11]       [+80]
         /   \       /   \
        1     3     5     7
      [+10] [+15] [+52] [ +0]

अब हम इसके मूल में जाते हैं:

               > 4
               [+32]
              /     \
           2           6
         [+11]       [+80]
         /   \       /   \
        1     3     5     7
      [+10] [+15] [+52] [ +0]

यह एक बायाँ बच्चा लिंक था, इसलिए हम इस नोड को भी बढ़ाते हैं:

                 4
               [+37]
              /     \
           2           6
         [+11]       [+80]
         /   \       /   \
        1     3     5     7
      [+10] [+15] [+52] [ +0]

और अब हम कर चुके हैं!

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

                100
               [+37]
              /     \
          010         110
         [+11]       [+80]
         /   \       /   \
       001   011   101   111
      [+10] [+15] [+52] [ +0]

यहां, हम एक बहुत, बहुत अच्छा अवलोकन कर सकते हैं। इनमें से कोई भी बाइनरी संख्या लें और उस अंतिम 1 को खोजें जो संख्या में सेट किया गया था, फिर उस बिट को हटा दें, इसके साथ आने वाले सभी बिट्स के साथ। अब आप निम्नलिखित के साथ बचे हैं:

              (empty)
               [+37]
              /     \
           0           1
         [+11]       [+80]
         /   \       /   \
        00   01     10   11
      [+10] [+15] [+52] [ +0]

यहाँ एक वास्तव में, वास्तव में अच्छा अवलोकन है: यदि आप 0 का मतलब "बाएं" और 1 का अर्थ "सही" है, तो प्रत्येक संख्या पर शेष बिट्स ठीक से बताएंगे कि रूट पर कैसे शुरू किया जाए और फिर उस संख्या तक नीचे जाएं। उदाहरण के लिए, नोड 5 में बाइनरी पैटर्न 101 है। अंतिम 1 अंतिम बिट है, इसलिए हम 10. प्राप्त करने के लिए छोड़ देते हैं। वास्तव में, यदि आप रूट पर शुरू करते हैं, दाएं (1), तो बाएं जाएं (0), आप समाप्त करें नोड 5 पर!

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

मुख्य ट्रिक इस संपूर्ण बाइनरी ट्री की निम्न संपत्ति है:

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

उदाहरण के लिए, नोड 7 के लिए एक्सेस पथ पर एक नज़र डालें, जो 111 है। रूट के एक्सेस मार्ग पर नोड्स जो हम लेते हैं, जिसमें एक सही पॉइंटर ऊपर की ओर शामिल है।

  • नोड 7: 111
  • नोड 6: 110
  • नोड 4: 100

ये सभी सही लिंक हैं। यदि हम नोड 3 के लिए पहुंच पथ लेते हैं, जो कि 011 है, और नोड्स को देखें जहां हम सही हैं, हम प्राप्त करते हैं

  • नोड 3: 011
  • नोड 2: 010
  • (नोड 4: 100, जो एक बाएं लिंक का अनुसरण करता है)

इसका मतलब यह है कि हम बहुत ही कुशलता से, कुल राशि तक संचयी राशि की गणना कर सकते हैं:

  • बाइनरी एन नोड बाइनरी में लिखें।
  • काउंटर को 0 पर सेट करें।
  • निम्नलिखित को दोहराएं जबकि n: 0:
    • नोड n पर मान में जोड़ें।
    • N से सबसे सही 1 बिट साफ़ करें।

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

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

उम्मीद है की यह मदद करेगा!



आपने मुझे दूसरे पैराग्राफ में खो दिया। 7 अलग-अलग तत्वों की संचयी आवृत्तियों का क्या मतलब है?
जेसन गोएमाट

20
यह अब तक मेरे द्वारा इंटरनेट पर पाए गए सभी स्रोतों के बीच इस विषय पर अब तक का सबसे अच्छा विवरण है। बहुत बढ़िया !
अनमोल सिंह जग्गी

2
फेनविक को यह स्मार्ट कैसे मिला?
रॉकस्टार

1
यह एक बहुत बड़ी व्याख्या है, लेकिन हर दूसरे स्पष्टीकरण के साथ-साथ फेनविक के स्वयं के कागज के समान समस्या से ग्रस्त है, एक प्रमाण नहीं देता है!
डार्थपागिहस

3

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

फेनविक ने बस इतना कहा कि पूछताछ के पेड़ में प्रत्येक नोड की जिम्मेदारी रेंज अपने अंतिम सेट बिट के अनुसार होगी:

फेनविक वृक्ष जिम्मेदारियों को निभाते हैं

उदाहरण के लिए, अंतिम सेट बिट 6== 00110"2-बिट" है, यह 2 नोड्स की सीमा के लिए जिम्मेदार होगा। के लिए 12== 01100, यह एक "4-बिट" है, इसलिए यह 4 नोड्स की एक श्रृंखला के लिए जिम्मेदार होगा।

इसलिए F(12)== क्वेरी करते समय F(01100), हम बिट्स को एक-एक करके, निकालते हैं F(9:12) + F(1:8)। यह लगभग एक कठोर प्रमाण नहीं है, लेकिन मुझे लगता है कि यह अधिक स्पष्ट है जब इतनी संख्याओं को केवल अक्ष पर रखा जाता है और एक परिपूर्ण बाइनरी ट्री पर नहीं, प्रत्येक नोड की जिम्मेदारियां क्या होती हैं, और क्वेरी की लागत संख्या के बराबर क्यों होती है बिट्स सेट करें।

यदि यह अभी भी अस्पष्ट है तो कागज बहुत अनुशंसित है।

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