एक बेहद लोकप्रिय सॉर्टिंग एल्गोरिदम QuickSort है। यह एक divide-and-conquer (डिवाइड-एंड-कन्कर) एल्गोरिदम है, जो सरणी (array) में से एक pivot चुनकर उसे तीन भागों में विभाजित करता है—पिवट से छोटे तत्व, पिवट के बराबर तत्व, और पिवट से बड़े तत्व। इसके बाद यह प्रक्रिया तब तक दोहराई जाती है जब तक पूरी सरणी सॉर्ट न हो जाए।
अर्थात, प्रत्येक पास में:
सरणी से एक pivot तत्व चुना जाता है।
पिवट से छोटे सभी तत्वों को पिवट के बाईं ओर ले जाया जाता है।
पिवट से बड़े सभी तत्वों को पिवट के दाईं ओर ले जाया जाता है।
पिवट के बराबर सभी तत्वों को बीच में रखा जाता है।
फिर बाएँ और दाएँ हिस्सों पर यही प्रक्रिया दोहराई जाती है।
def quicksort(a):
if len(a) <= 1:
return a
pivot = a[len(a) // 2]
left = [x for x in a if x < pivot]
middle = [x for x in a if x == pivot]
right = [x for x in a if x > pivot]
return quicksort(left) + middle + quicksort(right)
ऊपर दिए गए QuickSort कोड में left, middle, और right नाम के नए लिस्ट बनाने के लिए अतिरिक्त जगह का उपयोग किया गया है। इसी एल्गोरिदम को इन-प्लेस (in place) बनाया जा सकता है, जहाँ हम इंडेक्स की सहायता से सीधे उसी सरणी में तत्वों को पुनर्व्यवस्थित करते हैं:
def quicksort(a, start, end):
if start >= end: # कोई तत्व नहीं है जिसे छांटना हो
return
pivot = a[start] # एक pivot चुनें
l, r = start + 1, end # a[start+1 ... end] को छांटें
while l <= r: # जब तक दिए गए रेंज में तत्व मौजूद हों
if a[l] < pivot: # यदि a[l] < pivot हो, तो a[l] में कोई बदलाव न करें
l += 1 # left पॉइंटर को आगे बढ़ाएँ
else: # अन्यथा a[l] को अंत में ले जाएँ
a[l], a[r] = a[r], a[l] # a[l] और a[r] को अदल-बदल करें -> a[l] को अंत में ले जाएँ
r -= 1 # right पॉइंटर को कम करें
a[start], a[r] = a[r], a[start] # pivot की अदला-बदली a[r] के साथ करें
quicksort(a, start, r - 1) # a[start ... r-1] को छांटें
quicksort(a, r + 1, end) # a[r+1 ... end] को छांटें
आपने गौर किया होगा कि इन दोनों उदाहरणों में pivot चुनने का तरीका अलग है। असल में QuickSort एल्गोरिदम में पिवट चुनने का कोई एक ही तय तरीका नहीं होता। पिवट चुनने के कई तरीके हो सकते हैं, जैसे:
पहला तत्व: दूसरे उदाहरण में उपयोग किया गया a[start]
आख़िरी तत्व: a[end]
बीच का तत्व: a[(start + end + 1) // 2]
कोई रैंडम तत्व: a[start ... end] में से कोई भी
माध्य (median) तत्व: वर्तमान भाग का माध्य चुनें। यह पक्का करता है कि बायाँ और दायाँ भाग लगभग बराबर हों, पर इसकी अमल बाबत विधि ज़्यादा जटिल हो जाती है।
चलिए, इस एल्गोरिदम का उपयोग कुछ सरणियों पर करते हैं:
QuickSort का औसत समय जटिलता होती है। लेकिन पिवट चुनने के तरीक़े के आधार पर, इसका प्रदर्शन कभी-कभी लगभग तक गिर सकता है।
🤔 क्या आप कोई ऐसा उदाहरण बता सकते हैं जहाँ हर बार start को पिवट चुनने से यह एल्गोरिदम ऑपरेशनों पर पहुँच जाए?
Challenge
मान लीजिए आपको n संख्याओं की एक लिस्ट और q पिवट दिए गए हैं। आपका काम है, प्रत्येक पिवट के लिए, सभी छोटे तत्वों को पिवट के बाईं ओर, सभी बड़े तत्वों को पिवट के दाईं ओर, और पिवट के बराबर तत्वों को बीच में इकट्ठा करना है (छोटे के दाईं ओर और बड़े के बाईं ओर)।
इनपुट
पहली पंक्ति में एकमात्र पूर्णांक n (1 ≤ n ≤ 100 000) दिया है।
अगली पंक्ति में n स्पेस-सेपरेटेड पूर्णांक हैं, जहाँ ।
تیसरी पंक्ति में एकमात्र पूर्णांक q (1 ≤ q ≤ 10) है।
अगली पंक्ति में n स्पेस-सेपरेटेड पूर्णांक दिए गए हैं, जिनमें 1 ≤ ≤ n है और ये पिवट एलिमेंट के इंडेक्स दर्शाते हैं।
आउटपुट
प्रत्येक पिवट के लिए, प्रोग्राम को आवश्यक पुनर्व्यवस्था के बाद की सरणी प्रिंट करनी चाहिए। यदि एक से ज्यादा जवाब संभव हों, तो कोई भी मान्य जवाब प्रिंट किया जा सकता है।