এক্সপোনেনশিয়াল এবং ইন্টারপোলেশন সার্চ (Exponential & Interpolation Search)
কল্পনা করুন আপনি একটি রাস্তায় কোনো একটি বাড়ি খুঁজছেন, কিন্তু আপনি জানেন না রাস্তাটি ঠিক কত লম্বা। আপনি প্রথমে ১ পা নিলেন, তারপর ২ পা, তারপর ৪ পা, তারপর ৮ পা — প্রতিবার আপনার পদক্ষেপের আকার দ্বিগুণ করছেন — যতক্ষণ না আপনি আপনার গন্তব্যকে ছাড়িয়ে (overshoot) চলে যাচ্ছেন। এখন আপনি জানেন যে বাড়িটি অবশ্যই আপনার পার হওয়া শেষ গ্যাপের বা প্রসারণের ভেতরেই কোথাও আছে। এরপর আপনি সেই গ্যাপের শুরুতে ফিরে যান এবং সেখান হতে ঘর ধরে ধরে বা সাবধানে পুনরায় অনুসন্ধান শুরু করেন।
এটাই হলো এক্সপোনেনশিয়াল সার্চ। প্রথম পর্যায়: দ্বিগুণ হতে হতে লক্ষ্যকে অতিক্রম করুন। দ্বিতীয় পর্যায়: নির্ধারিত ক্ষুদ্র সীমার ভেতর বাইনারি সার্চ চালান।
কেন শুধু বাইনারি সার্চ নয়?
যখন আপনি অ্যারের আকার জানেন তখন বাইনারি সার্চ একদম নিখুঁত। কিন্তু কখনো কখনো আপনি আকার নাও জানতে পারেন — যেমন কোনো একটি সর্টেড ডেটা স্ট্রীম আসছে, অথবা এমন কোনো এপিআই (API) যা আপনাকে যেকোনো ইনডেক্স অ্যাক্সেস করতে দেয় কিন্তু মোট উপাদানের সংখ্যা জানায় না। আপনি যদি n নাই জানেন তবে আপনি hi = n - 1 সেট করতে পারবেন না।
এক্সপোনেনশিয়াল সার্চ এটি সমাধান করে: এটি দ্বিগুণ হওয়ার মাধ্যমে প্রথমে রেঞ্জ বা পরিসীমা আবিষ্কার করে, এবং তারপর সেটি সার্চ করে। এমনকি আরও ভালো ব্যাপার হলো — আপনার টার্গেট বা লক্ষ্য যদি শুরুর কাছাকাছি (যেমন পজিশন p-তে) থাকে, তবে পুরো অ্যারে যত বিশালই হোক না কেন আপনি মাত্র \(O(\log p)\) সময়ে সেটি খুঁজে পাবেন। বাইনারি সার্চের ক্ষেত্রে পূর্ণ অ্যারের সাইজ অনুযায়ী \(O(\log n)\) সময় লাগত যেখানে n \gg p।
দুটি পর্যায়
পর্যায় ১ — পরিসীমা নির্ধারণ (Bounding): ইনডেক্স ১ থেকে শুরু করুন। যদি arr[1] < target হয়, তবে ইনডেক্স ২-এ লাফ দিন, তারপর ৪, তারপর ৮, এভাবে প্রতিবার দ্বিগুণ করুন। যখন আপনি arr[i] >= target পাবেন অথবা অ্যারের সীমানা ছাড়িয়ে যাবেন তখন থামুন। আপনার টার্গেট অবশ্যই \([i/2, i]\) সীমার মধ্যে থাকবে।
পর্যায় ২ — বাইনারি সার্চ: এখন \([i/2, \min(i, n-1)]\) সীমার মধ্যে স্ট্যান্ডার্ড বাইনারি সার্চ চালান। যেহেতু এই পরিসরের আকার সর্বোচ্চ i/2 যা টার্গেটের পজিশনের কাছাকাছি বা সমান, তাই এই বাইনারি সার্চের খরচ হবে মাত্র \(O(\log p)\)।
সর্বমোট খরচ: বাউন্ডিংয়ের জন্য \(O(\log p)\) + বাইনারি সার্চের জন্য \(O(\log p)\) = \(O(\log p)\)। সাধারণত র্যান্ডম কোনো টার্গেটের জন্য p \approx n/2 হয়, তাই এটি গড়ে \(O(\log n)\)-এ রূপ নেয়।