टिमसॉर्ट

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

यह पीटर मैकिलरॉय के 1993 के पेपर "ऑप्टिमिस्टिक सॉर्टिंग एंड इंफॉर्मेशन थियोरेटिक कॉम्प्लेक्सिटी" की तकनीकों का उपयोग करता है।

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

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

मर्ज मानदंड
टिमसॉर्ट एक स्थिर सॉर्टिंग एल्गोरिदम है (समान कुंजी (की) वाले अवयवों का क्रम रखा जाता है) और संतुलित मर्ज करने का प्रयास करता है (इस प्रकार एक मर्ज समान आकार के रन को मर्ज करता है)।

सॉर्टिंग स्थिरता प्राप्त करने के लिए, केवल लगातार रन मर्ज किए जाते हैं। दो गैर-क्रमिक रन के बीच, रन के अंदर एक ही कुंजी वाला एक अवयव हो सकता है। उन दो रन को मिलाने से समान कुंजियों का क्रम बदल जाएगा। इस स्थिति का उदाहरण ([] रन क्रमबद्ध हैं): [1 2 2] 1 4 2 [0 1 2]

एक संतुलित मर्ज की खोज में, टाइमसॉर्ट स्टैक के शीर्ष पर तीन रन, X, Y, Z पर विचार करता है, और अपरिवर्तनीय को संरक्षित करता है:1. $|Z|$ > $|Y|$ + $|X|$

2. $|Z|$ > $|Y|$

यदि इनमें से किसी भी अपरिवर्तनीय का उल्लंघन किया जाता है, तो Y को X या Z में से छोटे के साथ विलय कर दिया जाता है और अपरिवर्तनीयों की दोबारा जाँच की जाती है। एक बार जब अपरिवर्तनीय पकड़ में आ जाते हैं, तो डेटा में एक नए रन की खोज प्रारम्भ हो सकती है। संतुलन के लिए विलय में देरी, कैश मेमोरी में रनों की ताज़ा घटना का फायदा उठाने और मर्ज निर्णयों को अपेक्षाकृत सरल बनाने के बीच समझौता बनाए रखते हुए ये अपरिवर्तनीय मर्ज को लगभग संतुलित बनाए रखते हैं।

मर्ज स्पेस ओवरहेड
मूल मर्ज सॉर्ट कार्यान्वयन यथास्थान नहीं है और इसमें N (डेटा आकार) का एक अतिरिक्त स्थान है। इन-प्लेस मर्ज सॉर्ट कार्यान्वयन मौजूद हैं, लेकिन इसमें काफी समय लगता है। एक मध्य अवधि प्राप्त करने के लिए, टिमसॉर्ट एन की तुलना में छोटे समय ओवरहेड और छोटे स्थान ओवरहेड के साथ मर्ज सॉर्ट करता है।

सबसे पहले, टिमसॉर्ट उस स्थान को ढूंढने के लिए एक बाइनरी खोज करता है जहां दूसरे रन का पहला अवयव पहले ऑर्डर किए गए रन में डाला जाएगा, इसे क्रम में रखते हुए। फिर, यह उस स्थान को ढूंढने के लिए उसी एल्गोरिदम को निष्पादित करता है जहां पहले रन का अंतिम अवयव दूसरे ऑर्डर किए गए रन में डाला जाएगा, इसे क्रम में रखते हुए। इन स्थानों के पहले और बाद के अवयव पहले से ही अपने सही स्थान पर हैं और उन्हें विलय की आवश्यकता नहीं है। फिर, दो रन के शेष अवयवों में से छोटे को अस्थायी मेमोरी में कॉपी किया जाता है, और अवयवों को अब खाली स्थान में बड़े रन के साथ विलय कर दिया जाता है। यदि पहला रन छोटा है, तो मर्ज शुरुआत में ही प्रारंभ हो जाता है; यदि दूसरा छोटा है, तो मर्ज अंत में प्रारंभ होता है। यह अनुकूलन सामान्य स्थिति में आवश्यक अवयव आंदोलनों की संख्या, चलने का समय और अस्थायी स्थान ओवरहेड को कम कर देता है।

उदाहरण: दो रन [1, 2, 3, 6, 10] और [4, 5, 7, 9, 12, 14, 17] को मिलाना होगा। ध्यान दें कि दोनों रन पहले से ही व्यक्तिगत रूप से क्रमबद्ध हैं। दूसरे रन का सबसे छोटा अवयव 4 है और इसके क्रम को संरक्षित करने के लिए इसे पहले रन के चौथे स्थान पर जोड़ना होगा (यह मानते हुए कि रन की पहली स्थिति 1 है)। पहले रन का सबसे बड़ा अवयव 10 है और इसका क्रम बनाए रखने के लिए इसे दूसरे रन के पांचवें स्थान पर जोड़ना होगा। इसलिए, [1, 2, 3] और [12, 14, 17] पहले से ही अपनी अंतिम स्थिति में हैं और वे रन जिनमें अवयवों की गति की आवश्यकता होती है वे हैं [6, 10] और [4, 5, 7, 9]। इस ज्ञान के साथ, हमें केवल 4 के स्थान पर आकार 2 का अस्थायी बफर आवंटित करने की आवश्यकता है।

मर्ज दिशा
मर्ज दोनों दिशाओं में किया जा सकता है: बाएं से दाएं, पारंपरिक मर्जसॉर्ट की तरह, या दाएं से बाएं किया जा सकता है।

मर्ज पर्यन्त गैलोपिंग मोड
रन R1 और R2 का एक अलग मर्ज एक रन से चुने गए लगातार अवयवों की गिनती रखता है। जब यह संख्या न्यूनतम गैलोपिंग की सीमा (मिन-गैलोप) तक पहुंच जाती है, तो टिमसॉर्ट का मानना ​​है कि यह संभावना है कि उस रन से कई लगातार अवयव अभी भी चुने जा सकते हैं और गैलोपिंग मोड में स्विच हो जाते हैं। चलिए मान लेते हैं कि R1 इसे ट्रिगर करने के लिए जिम्मेदार है। इस मोड में, एल्गोरिथ्म रन R1 में रन R2 के अगले अवयव x के लिए एक घातांकीय खोज करता है, जिसे सरपट खोज के रूप में भी जाना जाता है। यह दो चरणों में किया जाता है: पहला चरण (2k − 1, 2k+1 - 1) का दायरा ढूँढता है जहाँ x है। दूसरा चरण पहले चरण में पाई गई श्रेणी में अवयव x के लिए एक द्विआधारी खोज करता है। गैलोपिंग रन में अवयवों के बीच अंतराल के पैटर्न के लिए मर्ज एल्गोरिदम को अनुकूलित करने का एक प्रयास है।

गैलोपिंग हमेशा कुशल नहीं होता है। कुछ मामलों में गैलोपिंग मोड में साधारण रैखिक खोज की तुलना में अधिक तुलना की आवश्यकता होती है। डेवलपर द्वारा किए गए बेंचमार्क के अनुसार, गैलोपिंग केवल तभी फायदेमंद होता है जब एक रन का प्रारंभिक अवयव दूसरे रन के पहले सात अवयवों में से एक नहीं होता है। इसका तात्पर्य 7 की प्रारंभिक सीमा से है। गैलोपिंग मोड की कमियों से बचने के लिए, दो क्रियाएं की जाती हैं: (1) जब गैलोपिंग को बाइनरी खोज से कम कुशल पाया जाता है, तो गैलोपिंग मोड से बाहर निकल जाता है। (2) गैलोपिंग की सफलता या विफलता का उपयोग न्यूनतम_गैलप को समायोजित करने के लिए किया जाता है। यदि चयनित अवयव उसी सरणी से है जिसने पहले एक अवयव लौटाया था, तो मिन-गैलोप को एक से कम कर दिया जाता है, इस प्रकार गैलोपिंग मोड में वापसी को प्रोत्साहित किया जाता है। अन्यथा, मूल्य एक से बढ़ जाता है, इस प्रकार गैलोपिंग मोड पर वापसी को हतोत्साहित करता है। यादृच्छिक डेटा के मामले में, मिन-गैलोप का मान इतना बड़ा हो जाता है कि गैलोपिंग मोड की पुनरावृत्ति कभी नहीं होती है।



अवरोही रन
अवरोही क्रम में क्रमबद्ध किए गए डेटा का लाभ उठाने के लिए, टिमसॉर्ट उन्हें मिलने पर कड़ाई से अवरोही रनों को परिवर्तित कर दिया जाता है और उन्हें रनों के संचय में जोड़ दिया जाता है। चूँकि अवरोही रन बाद में अंधवत् परिवर्तिति कर दिए जाते हैं, समान अवयवों वाले रन को छोड़कर एल्गोरिदम की स्थिरता बनी रहती है; अर्थात, समान अवयव परिवर्तित नहीं होंगे।

न्यूनतम रन आकार
क्योंकि मर्ज सबसे अधिक कुशल होता है जब रनों की संख्या दो की घात के बराबर या उससे थोड़ी कम होती है, और विशेष रूप से कम कुशल होती है जब रनों की संख्या दो की घात से थोड़ी अधिक होती है, टिमसॉर्ट ने पूर्व स्थिति सुनिश्चित करने के प्रयास के लिए मिनरन को चुना।

मिनरन को श्रेणी 32 से 64 तक चुना जाता है, जैसे कि डेटा का आकार, मिनरन द्वारा विभाजित, दो की शक्ति के बराबर या उससे थोड़ा कम होता है। अंतिम एल्गोरिदम सरणी के आकार के छह सबसे महत्वपूर्ण बिट्स लेता है, यदि शेष बिट्स में से कोई भी सेट किया जाता है तो एक जोड़ता है, और उस परिणाम को मिनरून के रूप में उपयोग करता है। यह एल्गोरिथम सभी सरणियों के लिए काम करता है, जिसमें 64 से छोटी सरणियाँ भी सम्मिलित हैं; 63 या उससे कम आकार की सरणियों के लिए, यह minrun को सरणी आकार के बराबर सेट करता है और टिमसॉर्ट एक सम्मिलन सॉर्ट में कम हो जाता है।

विश्लेषण
निकृष्ट स्थिति में, टिमसॉर्ट n अवयवों की एक सरणी को सॉर्ट करने के लिए $$O(n \log n)$$ तुलना लेता है। सर्वोत्तम स्थिति में, जो तब होता है जब इनपुट पहले से ही सॉर्ट किया गया हो, यह रैखिक समय में चलता है, जिसका अर्थ है कि यह एक अनुकूली सॉर्टिंग एल्गोरिदम है।

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

औपचारिक सत्यापन
2015 में, ईयू एफपी7 परिकल्पना प्रोजेक्ट में डच और जर्मन शोधकर्ताओं ने टिमसॉर्ट के मानक कार्यान्वयन में एक बग पाया। इसे 2015 में पायथन, जावा और एंड्रॉइड में ठीक किया गया था। विशेष रूप से, स्टैक्ड रन साइज़ पर अपरिवर्तनीय आवश्यक स्टैक के अधिकतम आकार पर एक तंग ऊपरी सीमा सुनिश्चित करते हैं। कार्यान्वयन ने इनपुट के 264 बाइट्स को सॉर्ट करने के लिए पर्याप्त स्टैक पूर्व-आवंटित किया, और आगे अतिप्रवाह जांच से बचा।

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

परिणामस्वरूप, कुछ इनपुट के लिए आवंटित आकार सभी अनमर्ज किए गए रनों को रखने के लिए पर्याप्त नहीं है। जावा में, यह उन इनपुट के लिए एक ऐरे-आउट-ऑफ-बाउंड अपवाद उत्पन्न करता है। जावा और एंड्रॉइड v7 में इस अपवाद को ट्रिगर करने वाला सबसे छोटा इनपुट आकार 67108864 (226) है। (पुराने एंड्रॉइड संस्करणों ने आकार 65536 (216) के कुछ इनपुट के लिए पहले से ही इस अपवाद को ट्रिगर कर दिया है)

अद्यतन निकृष्ट स्थिति के विश्लेषण के आधार पर पूर्व-आवंटित स्टैक के आकार को बढ़ाकर जावा कार्यान्वयन को ठीक किया गया था। लेख में औपचारिक तरीकों से यह भी दिखाया गया है कि स्टैक में चार शीर्षतम रन ऊपर दिए गए दो नियमों को पूरा करते हैं या नहीं, इसकी जांच करके इच्छित अपरिवर्तनीय को कैसे स्थापित किया जाए। यह दृष्टिकोण पायथन और एंड्रॉइड द्वारा अपनाया गया था।

अग्रिम पठन

 * Auger, Jugé, Nicaud, Pivoteau (2018). "On the Worst-Case Complexity of TimSort". ESA 2018.
 * Sam Buss, Alexander Knop. "Strategies for Stable Merge Sorting." SODA 2019.
 * Sam Buss, Alexander Knop. "Strategies for Stable Merge Sorting." SODA 2019.

बाहरी संबंध

 * timsort.txt – original explanation by Tim Peters