मुझे लगता है कि इस विषय में कई प्रश्न दफन हैं:
- आप
buildHeap
इसे कैसे लागू करते हैं ताकि यह O (n) समय में चले ?
- जब आप सही तरीके से लागू होते हैं तो ओ (एन) समय
buildHeap
में कैसे चलते हैं ?
- क्यों एक ही तर्क काम ढेर प्रकार में चलाने के लिए नहीं है कि करने के लिए हे (एन) समय के बजाय O (n n लॉग इन करें) ?
आप buildHeap
इसे कैसे लागू करते हैं ताकि यह O (n) समय में चले ?
अक्सर, इन सवालों के जवाब के बीच siftUp
और अंतर पर ध्यान केंद्रित करते हैं siftDown
। के बीच सही चुनाव करना siftUp
और ओ (एन) प्रदर्शन siftDown
प्राप्त करने के लिए महत्वपूर्ण है , लेकिन किसी के बीच और सामान्य रूप से अंतर को समझने में मदद करने के लिए कुछ भी नहीं करता है । वास्तव में, दोनों का उचित कार्यान्वयन और केवल उपयोग करेगा । आपरेशन केवल एक मौजूदा ढेर में आवेषण प्रदर्शन करने की जरूरत है, तो यह एक द्विआधारी ढेर का उपयोग कर, उदाहरण के लिए एक प्राथमिकता कतार लागू करने के लिए इस्तेमाल किया जाएगा।buildHeap
buildHeap
heapSort
buildHeap
heapSort
siftDown
siftUp
मैंने यह वर्णन करने के लिए लिखा है कि अधिकतम ढेर कैसे काम करता है। यह आमतौर पर हीप सॉर्ट के लिए या प्राथमिकता कतार के लिए उपयोग किया जाने वाला हीप का प्रकार है जहां उच्च मान उच्च प्राथमिकता का संकेत देते हैं। एक मिनट का ढेर भी उपयोगी है; उदाहरण के लिए, आरोही क्रम में पूर्णांक कुंजियों वाली वस्तुओं को प्राप्त करते समय या वर्णमाला क्रम में तार। सिद्धांत बिल्कुल समान हैं; बस सॉर्ट क्रम को स्विच करें।
ढेर संपत्ति निर्दिष्ट करता है कि एक द्विआधारी ढेर में प्रत्येक नोड में कम से कम अपने बच्चों की दोनों के रूप में बड़े रूप में किया जाना चाहिए। विशेष रूप से, इसका तात्पर्य है कि ढेर में सबसे बड़ी वस्तु जड़ में है। शिफ्टिंग और शिफ्टिंग अनिवार्य रूप से विपरीत दिशाओं में एक ही ऑपरेशन है: एक आक्रामक नोड को स्थानांतरित करें जब तक कि यह ढेर संपत्ति को संतुष्ट न करे:
siftDown
एक नोड स्वैप करता है जो अपने सबसे बड़े बच्चे के साथ बहुत छोटा है (इस तरह इसे नीचे ले जाता है) जब तक कि यह कम से कम दोनों नोड्स के रूप में बड़ा न हो।
siftUp
एक नोड को स्वैप करता है जो अपने माता-पिता के साथ बहुत बड़ा होता है (इस तरह इसे ऊपर ले जाता है) जब तक कि यह ऊपर के नोड से बड़ा न हो।
के लिए आवश्यक संचालन की संख्या siftDown
और siftUp
दूरी के अनुपात में है जो नोड को स्थानांतरित करना पड़ सकता है। के लिए siftDown
, यह पेड़ के नीचे की दूरी है, इसलिए पेड़ siftDown
के शीर्ष पर नोड्स के लिए महंगा है। के साथ siftUp
, काम पेड़ के शीर्ष पर दूरी के लिए आनुपातिक है, इसलिए पेड़ siftUp
के नीचे नोड्स के लिए महंगा है। हालांकि दोनों ऑपरेशन ओ (लॉग एन) सबसे खराब स्थिति में हैं, एक ढेर में, केवल एक नोड शीर्ष पर है जबकि आधा नोड्स नीचे की परत में स्थित है। इसलिए यह बहुत आश्चर्यजनक नहीं होना चाहिए कि अगर हमें हर नोड के लिए एक ऑपरेशन लागू करना है, तो हम siftDown
अधिक पसंद करेंगे siftUp
।
buildHeap
समारोह जब तक वे सभी, ढेर संपत्ति को संतुष्ट जिससे एक वैध ढेर उत्पादन अवर्गीकृत वस्तुओं और उन्हें चाल की एक सरणी लेता है। हमारे द्वारा बताए गए और संचालन buildHeap
का उपयोग करने के लिए दो दृष्टिकोण हो सकते हैं ।siftUp
siftDown
ढेर के शीर्ष पर शुरू करें (सरणी की शुरुआत) और siftUp
प्रत्येक आइटम पर कॉल करें । प्रत्येक चरण में, पहले से sifted आइटम (सरणी में वर्तमान आइटम से पहले आइटम) एक मान्य हीप बनाते हैं, और अगले आइटम को ऊपर ले जाकर इसे हीप में एक मान्य स्थिति में रखता है। प्रत्येक नोड को ऊपर ले जाने के बाद, सभी आइटम ढेर संपत्ति को संतुष्ट करते हैं।
या, विपरीत दिशा में जाएं: सरणी के अंत में शुरू करें और पीछे की ओर सामने की ओर बढ़ें। प्रत्येक पुनरावृत्ति पर, आप एक आइटम को तब तक निचोड़ते हैं जब तक कि वह सही स्थान पर न हो।
कौन सा कार्यान्वयन buildHeap
अधिक कुशल है?
ये दोनों समाधान एक वैध ढेर का उत्पादन करेंगे। अप्रत्याशित रूप से, अधिक कुशल एक दूसरा ऑपरेशन है जो उपयोग करता है siftDown
।
चलो एच = लॉग n ढेर की ऊंचाई का प्रतिनिधित्व करते हैं। siftDown
दृष्टिकोण के लिए आवश्यक कार्य योग द्वारा दिया जाता है
(0 * n/2) + (1 * n/4) + (2 * n/8) + ... + (h * 1).
योग में प्रत्येक पद के लिए दी गई ऊंचाई पर एक नोड की अधिकतम दूरी होती है, उस ऊंचाई पर नोड्स की संख्या से गुणा (नीचे की परत के लिए शून्य, जड़ के लिए) करना होगा। इसके विपरीत, siftUp
प्रत्येक नोड पर कॉल करने का योग है
(h * n/2) + ((h-1) * n/4) + ((h-2)*n/8) + ... + (0 * 1).
यह स्पष्ट होना चाहिए कि दूसरा योग बड़ा है। अकेले पहला शब्द hn / 2 = 1/2 n लॉग एन है , इसलिए इस दृष्टिकोण में सबसे अच्छा O (n लॉग एन) में जटिलता है ।
हम siftDown
वास्तव में O (n) के दृष्टिकोण के लिए योग कैसे साबित करते हैं ?
एक विधि (अन्य विश्लेषण भी हैं जो काम भी करते हैं) परिमित राशि को एक अनंत श्रृंखला में बदलना है और फिर टेलर श्रृंखला का उपयोग करना है। हम पहले कार्यकाल की उपेक्षा कर सकते हैं, जो शून्य है:
यदि आप सुनिश्चित नहीं हैं कि उनमें से प्रत्येक चरण क्यों काम करता है, तो शब्दों में इस प्रक्रिया का औचित्य है:
- शब्द सभी सकारात्मक हैं, इसलिए परिमित राशि अनंत राशि से छोटी होनी चाहिए।
- श्रृंखला x = 1/2 पर मूल्यांकन की गई एक शक्ति श्रृंखला के बराबर है ।
- वह पावर सीरीज़ (एक स्थिर समय) के बराबर है टेलर श्रृंखला के व्युत्पन्न f (x) = 1 / (1-x) के लिए ।
- x = 1/2 उस टेलर श्रृंखला के अभिसरण के अंतराल के भीतर है।
- इसलिए, हम टेलर श्रृंखला को 1 / (1-x) से बदल सकते हैं , अंतर कर सकते हैं, और अनंत श्रृंखला के मूल्य का पता लगाने के लिए मूल्यांकन कर सकते हैं।
चूंकि अनंत योग बिल्कुल n है , इसलिए हम निष्कर्ष निकालते हैं कि परिमित राशि कोई बड़ी नहीं है, और इसलिए, हे (n) ।
हीप सॉर्ट को ओ (एन लॉग एन) समय की आवश्यकता क्यों है ?
यदि यह buildHeap
रैखिक समय में चलना संभव है , तो हेप सॉर्ट को ओ (एन लॉग एन) समय की आवश्यकता क्यों है ? ठीक है, ढेर प्रकार दो चरणों के होते हैं। सबसे पहले, हम buildHeap
उस सरणी पर कॉल करते हैं, जिसके लिए ओ (एन) समय की आवश्यकता होती है, अगर इसे आशावादी रूप से लागू किया जाता है। अगला चरण ढेर में सबसे बड़ी वस्तु को बार-बार हटाना और इसे सरणी के अंत में रखना है। क्योंकि हम एक आइटम को हीप से हटाते हैं, वहाँ हमेशा ढेर के अंत के बाद एक खुला स्थान होता है जहाँ हम आइटम को स्टोर कर सकते हैं। इसलिए ढेर क्रम क्रमिक रूप से अगले सबसे बड़े आइटम को हटाने और अंतिम स्थिति में शुरू होने वाले सरणी में डालकर और सामने की ओर बढ़ते हुए एक क्रमबद्ध क्रम को प्राप्त करता है। यह इस अंतिम भाग की जटिलता है जो ढेर प्रकार में हावी है। लूप इस तरह दिखता है:
for (i = n - 1; i > 0; i--) {
arr[i] = deleteMax();
}
स्पष्ट रूप से, लूप ओ (एन) बार ( एन - 1 सटीक होने के लिए, अंतिम आइटम पहले से ही जगह पर है) चलाता है । deleteMax
एक ढेर के लिए जटिलता हे (लॉग एन) है । यह आम तौर पर रूट (ढेर में छोड़ी गई सबसे बड़ी वस्तु) को हटाकर और ढेर में अंतिम वस्तु के साथ प्रतिस्थापित किया जाता है, जो एक पत्ती है, और इसलिए सबसे छोटी वस्तुओं में से एक है। यह नई जड़ निश्चित रूप से ढेर संपत्ति का उल्लंघन करेगी, इसलिए आपको siftDown
तब तक कॉल करना होगा जब तक आप इसे स्वीकार्य स्थिति में वापस नहीं ले जाते। यह अगले सबसे बड़े आइटम को जड़ तक ले जाने का भी प्रभाव है। ध्यान दें कि, पेड़ के नीचे से buildHeap
कॉल करने वाले अधिकांश नोड्स के विपरीत siftDown
, हम अब कॉल कर रहे हैं,siftDown
प्रत्येक पुनरावृत्ति पर पेड़ के ऊपर से !हालांकि पेड़ सिकुड़ रहा है, यह काफी तेजी से सिकुड़ता नहीं है: जब तक आप नोड्स के पहले आधे हिस्से को नहीं हटा देते (जब आप नीचे की परत को पूरी तरह से साफ नहीं करते हैं) तब तक पेड़ की ऊंचाई स्थिर रहती है। फिर अगली तिमाही के लिए, ऊंचाई h - 1 है । तो इस दूसरे चरण के लिए कुल काम है
h*n/2 + (h-1)*n/4 + ... + 0 * 1.
स्विच को नोटिस करें: अब शून्य काम का मामला एक नोड से मेल खाता है और एच काम का मामला आधे नोड से मेल खाता है। यह योग ओ (n लॉग एन) के अक्षम संस्करण की तरह हैbuildHeap
siftUp का उपयोग करके लागू किया गया है। लेकिन इस मामले में, हमारे पास कोई विकल्प नहीं है क्योंकि हम छांटने की कोशिश कर रहे हैं और हमें अगले सबसे बड़े आइटम को हटाने की आवश्यकता है।
संक्षेप में, हीप सॉर्ट के लिए कार्य दो चरणों का योग है: प्रत्येक नोड को हटाने के लिए बिल्डहाइप और ओ (एन लॉग एन) के लिए ओ (एन) समय , इसलिए जटिलता ओ (एन लॉग एन) है । आप साबित कर सकते हैं (सूचना सिद्धांत से कुछ विचारों का उपयोग करते हुए) कि तुलना-आधारित सॉर्ट के लिए, O (n log n) सबसे अच्छा है जिसे आप किसी भी तरह से आशा कर सकते हैं, इसलिए इससे निराश होने की कोई वजह नहीं है या इस प्रकार की उम्मीद की जा सकती है O (n) टाइम बाउंड जो buildHeap
करता है।