হিপ সর্ট (Heap Sort)
একটি টুর্নামেন্ট ব্র্যাকেটের কথা ভাবুন যেখানে প্রতিটি ম্যাচ প্রতিটি রাউন্ডে একই সাথে খেলা হয়। নিয়ম হলো: প্রতিটি ম্যাচের বিজয়ীকে অবশ্যই তার দুটি সন্তানের চেয়ে শক্তিশালী হতে হবে। যেকোনো রাউন্ডের শেষে, ব্র্যাকেটের একদম শীর্ষে সবচেয়ে শক্তিশালী খেলোয়াড় অবস্থান করে। তাকে বের করে নিন, ব্র্যাকেটটি সংকুচিত করুন এবং আবার খেলা শুরু করুন — এবার পরবর্তী সবচেয়ে শক্তিশালী খেলোয়াড়টি ওপরে উঠে আসবে। এটিই হলো হিপ সর্ট।
আরও নিখুঁতভাবে বললে: এটি একটি ম্যাক্স-হিপ (max-heap) ব্যবহার করে — এটি এমন একটি বাইনারি ট্রি যেখানে প্রতিটি অভিভাবক (parent) তার সন্তানদের (children) চেয়ে বড় হয়। অ্যারের মাধ্যমে এটিকে খুব সংক্ষেপে সংরক্ষণ করা যায়: i ইনডেক্সের সন্তানরা 2i+1 এবং 2i+2 এ থাকে; i এর অভিভাবক ⌊(i−1)/2⌋ এ থাকে। এর জন্য কোনো পয়েন্টারের প্রয়োজন হয় না।
ধাপ ১: O(n) সময়ে হিপ তৈরি করা
আপনি হয়তো ভাবতে পারেন যে একটি হিপের মধ্যে n সংখ্যক উপাদান একে একে ঢোকাতে (insert) O(n log n) সময় লাগে। তবে এর একটি আরও বুদ্ধিদীপ্ত উপায় আছে: ফ্লয়েডের হিপিফাই (Floyd's heapify)। একদম শেষের নন-লিফ নোড (ইনডেক্স ⌊n/2⌋−1) থেকে শুরু করুন এবং উল্টো দিকে কাজ করুন, প্রতিটি নোডকে তার সঠিক অবস্থানে নামিয়ে দিন (sift down)। লিফ নোডগুলোর (leaves) কোনো কাজ করতে হয় না; নিচের দিকের নোডগুলোর খুব সামান্য কাজ করতে হয়। সবগুলো কাজের যোগফল একটি জ্যামিতিক ধারায় (geometric series) পরিণত হয় যা O(n) এ গিয়ে মেশে — এটি একটি চমকপ্রদ তথ্য যা সমস্ত উচ্চতা h এর সাপেক্ষে h·n/2^h এর যোগফল বের করে প্রমাণ করা যায়।
ধাপ ২: বারবার এক্সট্র্যাক্ট-ম্যাক্স (Extract-Max) করে সর্ট করা
এখন সবচেয়ে বড় উপাদানটি রুটে (ইনডেক্স 0) রয়েছে। এটিকে হিপের সর্বশেষ উপাদানের সাথে অদলবদল (swap) করুন, হিপটিকে এক ঘর ছোট করুন এবং হিপের প্রপার্টি বা নিয়ম ধরে রাখতে নতুন রুটটিকে নিচের দিকে নামিয়ে দিন। বের করে আনা উপাদানটি এখন অ্যারের শেষে সঠিক সারিবদ্ধ (sorted) অবস্থানে চলে গেছে। এই প্রক্রিয়াটি n−1 বার পুনরাবৃত্তি করুন। প্রতিটি নামিয়ে দেওয়ার বা সিফট-ডাউন কাজে O(log n) সময় লাগে, এবং আমরা এমন কাজ n বার করি: মোট O(n log n) কাজ।
এই সর্টিং পুরোপুরি ইন-প্লেস (in-place) কাজ করে। হিপটি অ্যারের সামনের দিকে থাকে; আর সাজানো বা সর্ট করা অংশটি পেছন থেকে বাড়তে থাকে। এর জন্য কোনো অতিরিক্ত বাফারের প্রয়োজন পড়ে না।
কেন হিপ সর্ট বাস্তবে হেরে যায়
এর চমৎকার বৈশিষ্ট্যগুলো থাকা সত্ত্বেও — যেমন O(n log n) গ্যারান্টিযুক্ত সময়, O(1) স্পেস, কোনো প্রতিকূল ইনপুট নেই — হিপ সর্ট বাস্তবে কুইক সর্টের চেয়ে ধীরগতি সম্পন্ন। এর মূল কারণ হলো: ক্যাশ মেমরির সাথে অসহযোগিতা (cache unfriendliness)। সিফট-ডাউন প্রক্রিয়াটি অভিভাবক (ইনডেক্স i) এবং সন্তানের (ইনডেক্স 2i+1) মধ্যে লাফিয়ে লাফিয়ে কাজ করে, যা বড় অ্যারের ক্ষেত্রে একে অপরের থেকে অনেক দূরে থাকে। এই ধরনের চলাচলের কারণে বারবার সিপিইউ ক্যাশ মিস (CPU cache misses) হয়, যার ফলস্বরূপ হিপ সর্ট বাস্তব ক্ষেত্রে কুইক সর্টের চেয়ে ২-৫ গুণ ধীর হয়ে যায়।
মার্জ সর্ট ক্রমিক বা ক্রমানুসারে মেমরি অ্যাক্সেস করে (ক্যাশ-বান্ধব)। কুইক সর্ট কাছাকাছি থাকা অংশ বা কন্টিগুয়াস রেঞ্জকে ভাগ করে (ক্যাশ-বান্ধব)। কিন্তু হিপ সর্ট এদিক-ওদিক লাফিয়ে বেড়ায় — যা ক্যাশ-বান্ধব নয়। এর সবচেয়ে বড় উপযোগিতা হলো এটি ইন্ট্রোসর্ট (Introsort) এ একটি সেফটি নেট (safety net) হিসেবে কাজ করে: যখন কুইক সর্ট বুঝতে পারে যে তার পারফরম্যান্স নিচে নেমে যাচ্ছে (রিকার্শন গভীরতা > 2 log n), তখন এটি কোনো অতিরিক্ত মেমরি ছাড়াই O(n log n) গ্যারান্টির জন্য হিপ সর্টের আশ্রয় নেয়।