মেমোইজেশন বনাম ট্যাবুলেশন (Memoization vs Tabulation)
একটি ডিপি বা DP সমস্যা (problem) চিনে ফেলার পর এবং আপনার স্টেট (state) ও রিকারেন্সকে (recurrence) সুস্পষ্টভাবে সংজ্ঞায়িত বা ডিফাইন (defined) করার পর, আপনি আরও একটি প্রশ্নের মুখোমুখি হয়ে থাকবেন: আমি আসলে কীভাবে এটিকে গণনায় আনবো বা কম্পিউট (compute) করবো? এটিকে করার প্রধানত দুটি পদ্ধতি বা অ্যাপ্রোচ (approaches) রয়েছে, এবং তারা ঠিক দুটি ভিন্ন ধরনের ব্যক্তিত্বের (personalities) মতো আচরণ করে।
মেমোইজেশন বা টপ-ডাউন (Memoization - top-down): সবচেয়ে বড় বা মূল সমস্যাটি থেকে শুরু করুন। এরপর ক্রমশ নিচের দিকে রিকার্স বা যান (Recurse down)। আপনি যদি আগে থেকেই কোনো সাব-প্রোব্লেম বা উপ-সমস্যার সমাধান করে থাকেন, তবে অবিলম্বে বা সাথে সাথেই শুধু সেই ক্যাশ (cached) করা উত্তরটি ফিরিয়ে দিন। আর যদি তা না হয়, তবে এটিকে আগে সমাধান করুন, তারপর ক্যাশে সংরক্ষণ (cache) করুন এবং সবশেষে সেটি ঘুরিয়ে ফেরত বা রিটার্ন (return) দিন। এটি অনেকটা কোনো বন্ধুকে কল করার মতো — যে কিনা তারপর হয়তো অন্য কোনো বন্ধুকে কল করতে পারে — যে হয়তো আবার অন্য কাউকে কল করতে পারে। আর এভাবেই ধীরে ধীরে এই চেইনটি বা শেকলটি একেবারের শেষ তলার তলানিতে গিয়ে পৌঁছায়, এবং তারপর সব উত্তরগুলো পুনরায় উপরের দিকে প্রচার (propagate) হতে থাকে।
ট্যাবুলেশন বা বটম-আপ (Tabulation - bottom-up): একেবারের সবচেয়ে ছোট সাব-প্রোব্লেমগুলো থেকে আপনার যাত্রা শুরু করুন। একটি টেবিল বা ছককে সবচেয়ে ছোট থেকে নিয়ে সবচেয়ে বড় পর্যন্ত মান দিয়ে পূরণ করুন, তবে এসময় খেয়াল রাখবেন যে প্রতিটি সেলের বা ঘরের নির্ভরতা বা ডিপেন্ডেন্সিগুলো (dependencies) যেন ইতিমধ্যেই আগে থেকে পূরণ করা থাকে। এটি ঠিক একটি স্প্রেডশিট (spreadsheet) পূরণ করার মতো ব্যাপার: যেখানে প্রতিটি ঘর বা সেল (cell) মূলত তার বাম দিকে বা উপরের দিকে থাকা ইতিমধ্যে গণা হয়ে যাওয়া বা কম্পিউট করা সেলগুলোকে (already-computed cells) ব্যবহার করে। এখানে কোনো রিকার্সন (recursion) নেই, কিংবা কোনো কল স্ট্যাকও (call stack) থাকে না।
দুটি অ্যাপ্রোচই (approaches) আপনাকে ঠিক একই ধরনের অ্যাসিম্পটোটিক সময় এবং স্পেস (same asymptotic time and space) প্রদান করবে। এখানকার পছন্দটি বা চয়েসটি মূলত সম্পূর্ণ নির্ভর করে বাস্তব নানা ধরনের ট্রেডঅফ (practical tradeoffs) বা সুবিধা-অসুবিধার ওপর।
মেমোইজেশন বা টপ-ডাউন — একটু আলসে পদ্ধতি (Memoization — The Lazy Approach)
রিকারেন্স বা পুনরাবৃত্তির ঠিক যেমনটা নির্দেশ করেছে, রিকার্সিভ সলিউশনটি বা রিকার্সিভ সমাধানটিও ঠিক সেভাবেই লিখুন, আর তারপর একেবারে এর উপরের অংশে একটি ক্যাশ চেকের (cache check) ব্যবস্থা করে দিন।
সুবিধাসমূহ (Advantages):
• এটি লেখা অনেক বেশি স্বাভাবিক বা ন্যাচারাল (Natural) হয়ে থাকে — এখানকার কোডটি সরাসরি এর রিকারেন্সকেই প্রতিফলিত (mirrors) করে থাকে।
• লেজি (Lazy) বা অলস: এটি কেবল সেই সমস্ত সাব-প্রোব্লেমগুলোকেই কম্পিউট (computes) বা গণনা করে, যেগুলো আসলে তার দরকার হয়। যদি আপনার সলিউশন্টি (solution) মূলত স্টেট স্পেসের (state space) কেবল একটি ছোট অংশকে স্পর্শ করে বা টাচ করে থাকে, তবে মেমোইজেশন ট্যাবুলেশনের (tabulation) ওই বাড়তি অপ্রয়োজনীয় কাজগুলো (wasted work) করা থেকে আপনাকে বাঁচাবে।
• পাইথনের (Python) @lru_cache, জাভাস্ক্রিপ্টের বা JavaScript-এর ক্লোজারস (closures), অথবা একটি ডিকশনারি (dictionary) দিয়ে একে বেশ সহজেই ইমপ্লিমেন্ট (implement) বা প্রয়োগ করে নেওয়া যায়।
অসুবিধাসমূহ (Disadvantages):
• ফাংশন কল ওভারহেড (Function call overhead) এবং কল স্ট্যাক মেমোরির (call-stack memory) প্রয়োজন হয়।
• স্ট্যাক ওভারফ্লোর রিস্ক বা ঝুঁকি (Stack overflow risk): যদি n=1,000,000 হয় এবং প্রতিটি রিকার্সিভ কল (recursive call) একটি করে নতুন ফ্রেম (frame) যোগ করতে থাকে, তবে বেশিরভাগ সিস্টেমের ডিফল্ট বা পূর্ব-নির্ধারিত কল স্ট্যাক (সাধারণত ১,০০০-১০,০০০ ফ্রেমস হয়ে থাকে) পুরোপুরি উপচে বা ওভারফ্লো (overflow) করে ফেলবে।
• স্পেস অপ্টিমাইজেশন (Space optimization) বা রোলিং অ্যারেস (rolling arrays) প্রয়োগ করা এতে বেশ কঠিন — কারণ এর রিকার্সিভ স্ট্রাকচার বা কাঠামোগত কারণে আপনি ঠিক কোন ক্যাশ করা স্টেটগুলোকে বা মানগুলোকে ফেলে বা বাদ দিতে (discard) পারবেন, তা নির্ধারণ করা খুব স্পষ্ট (non-obvious) হয়ে থাকে না।
ট্যাবুলেশন বা বটম-আপ — একটি খুব আগ্রহী বা ইগার পদ্ধতি (Tabulation — The Eager Approach)
পূরণ করার সঠিক ক্রম বা ফিল অর্ডার (fill order) (সাধারণত ছোট থেকে শুরু করে সবচেয়ে বড় ইনডেক্স বা সূচক পর্যন্ত) নির্ধারণ করুন, তারপর ইটারেট (iterate) করুন এবং প্রতিটি ঘর বা সেলকে (cell) পূরণ করতে থাকুন।
সুবিধাসমূহ (Advantages):
• কোনো স্পেশাল রিকার্সন ওভারহেড (recursion overhead) থাকে না, বা কোনো স্ট্যাক ওভারফ্লোরও (stack overflow) ভয় থাকে না।
• এর ফিল অর্ডার বা পূরণের ক্রমটি (Fill order) বেশ এক্সপ্লিসিট বা স্পষ্ট — আপনি চাইলেই পুরনো সারিগুলোকে বা রোলিং উইন্ডোগুলোকে (rolling window) বাদ দিয়ে মূলত \(O(n^2)\) থেকে \(O(n)\) তে বা \(O(n)\) থেকে \(O(1)\) তে স্পেস অপ্টিমাইজ (optimize space) করে নিতে পারেন।
• বাস্তবে এটি বেশ ভালো একটি বেটার ক্যাশ লোক্যালিটি (Better cache locality) প্রদান করে — এখানকার সিকোয়েনশিয়াল অ্যারে অ্যাক্সেস (sequential array access) অপেক্ষাকৃত দ্রুত কাজ করে।
অসুবিধাসমূহ (Disadvantages):
• 2D অথবা অনিয়মিত কোনো ডিপি-র (irregular DP) ক্ষেত্রে সঠিক ফিল অর্ডার বা পূরণের ক্রম নির্ধারণ করাটি বেশ অস্পষ্ট বা কঠিন (non-obvious) হতে পারে।
• এটি সমস্ত (all) সাব-প্রোব্লেম বা উপ-সমস্যাগুলোকেই কম্পিউট বা গণনা করে রাখে, এমনকি ফাইনাল উত্তরের জন্য যেগুলির কোনও প্রয়োজনই (not needed for the final answer) নেই সেগুলোকেও।
স্পেস অপ্টিমাইজেশন — ট্যাবুলেশনের সুপারপাওয়ার বা অসীম ক্ষমতা (Space Optimization — Tabulation's Superpower)
বেশিরভাগ ডিপি রিকারেন্সই (DP recurrences) মূলত কেবল এক বা দুটি সারির (rows) দিকেই ফিরে তাকায়। যখন ঠিক এমনটাই ঘটে বা রিকোয়ারমেন্ট থাকে, তখন আপনি চাইলে সহজেই আপনার পুরনো সারির (rows) ডেটাগুলোকে ফেলে দিয়ে শুধুমাত্র আপনার জন্য প্রয়োজনীয় টুকুকেই জমিয়ে রাখতে পারবেন।
ফিবোনাচি (Fibonacci): dp[i] = dp[i-1] + dp[i-2]। এখানে শুধুমাত্র সর্বশেষ দুটো (last two) মানেরই প্রয়োজন। পরিমাণ বা স্পেস (Space): \(O(n)\) → \(O(1)\)।
0/1 ন্যাপস্যাক (0/1 Knapsack): dp[i][w] = max(dp[i-1][w], dp[i-1][w-wt]+val)। এক্ষেত্রে শুধু আপনার পূর্ববর্তী সারিরই (previous row) প্রয়োজন। স্পেস (Space): \(O(n \cdot W)\) → \(O(W)\)। (আপনার প্রয়োজনীয় ডেটা বা ভ্যালুর যেন কোনো ক্ষতি (overwriting) না হয়, এজন্য w-কে উল্টোদিকে বা রিভার্স (reverse) ডিরেকশনে ইটারেট (Iterate) করতে পারেন।)
অপ্টিমাইজেশনের (optimization) এই কাজগুলো মূলত ট্যাবুলেশনের (tabulation) মাধ্যমে করা অত্যন্ত সোজাসাপ্টা ও সহজ (straightforward) একটি ব্যাপার। পক্ষান্তরে, মেমোইজেশনের (memoization) সাহায্যে এটি করার জন্য আপনাকে ঠিক কোন কোন ক্যাশড স্টেটগুলো (cached states) দরকারি তা নিজের হাতে ট্র্যাক (track) করতে হবে — যা তুলনামূলক অনেক বেশি দুঃসাধ্য ও কঠিন (much harder) একটা কাজ।
মেমোইজেশন বনাম ট্যাবুলেশন — ফিবোনাচি এবং 0/1 ন্যাপস্যাক
Complexity
ছোট কুইজ
পড়া চালিয়ে যান