গ্রিডি অ্যালগরিদম৫ মিনিট পড়া

ফ্র্যাকশনাল ন্যাপস্যাক বা ভগ্নাংশ ন্যাপস্যাক (Fractional Knapsack)

একটি ব্যাকপ্যাক সোনার ধুলো দিয়ে ভরছেন — প্রতি গ্রামের সর্বোচ্চ মূল্যের বস্তু আগে নিন
time:দ্রুত · \(O(n \log n)\)space:ন্যূনতম · \(O(1)\)works because:বস্তুগুলো বিভাজ্য — কোনো অতিরিক্ত ক্ষমতা নষ্ট হয় না

কল্পনা করুন আপনি একজন স্বর্ণ সন্ধানী যার কাছে একটি ব্যাকপ্যাক আছে যা ঠিক ১০ কেজি বহন করতে পারে। আপনি সোনার ধুলো, রুপার গুঁড়ো এবং তামার টুকরো খুঁজে পেয়েছেন। আপনি যেকোনো স্তূপ থেকে যেকোনো পরিমাণ তুলে নিতে পারেন — এমনকি আধা-স্কুপও। আপনি কোন স্তূপ আগে নেবেন?

খুব সহজ: প্রতিটি উপাদানের প্রতি গ্রামের মূল্য হিসেব করুন, তারপর সবচেয়ে মূল্যবানটি আপনার ব্যাগ পূর্ণ হওয়া পর্যন্ত (বা স্তূপ শেষ হওয়া পর্যন্ত) নিতে থাকুন, এবং এরপর পরবর্তী মূল্যবানটিতে যান। যেহেতু আপনি ভগ্নাংশ পরিমাণ নিতে পারেন, তাই আপনার ব্যাগের কোনো জায়গা বা ক্ষমতা নষ্ট হবে না।

এটাই হলো ফ্র্যাকশনাল ন্যাপস্যাক সমস্যা, এবং গ্রিডি (greedy) পদ্ধতি এটি \(O(n \log n)\) সময়ে নিখুঁতভাবে সমাধান করে।

অ্যালগরিদম

১. প্রতিটি বস্তুর জন্য এর মূল্য-ওজন অনুপাত (value-to-weight ratio) বের করুন: v_i / w_i।
২. অনুপাত অনুযায়ী বস্তুগুলোকে বড় থেকে ছোট (descending order) ক্রমে সাজান।
৩. গ্রিডি বা লোভী হয়ে ব্যাগ ভর্তি করা শুরু করুন: যদি পুরোটা ব্যাগে ধরে তবে সেটি নিন; যদি না ধরে তবে যতটুকু জায়গা খালি আছে ঠিক ততটুকুই নিন। ব্যাগ পূর্ণ হলে থেমে যান।

এটি কেন সঠিক

আপনার ব্যাগের ক্ষমতার প্রতিটি গ্রাম থেকে যেন সর্বোচ্চ সম্ভাব্য মূল্য পাওয়া যায় তা নিশ্চিত করতে হবে। যদি কখনো দেখেন একটি উচ্চ-অনুপাতের বস্তু তখনও অবশিষ্ট থাকা সত্ত্বেও আপনি আপনার ব্যাগের কোনো গ্রাম নিম্ন-অনুপাতের বস্তু দিয়ে ভরে রেখেছেন, তবে আপনি সেই গ্রামটি পাল্টে আরও বেশি আয় করতে পারতেন। একে বলা হয় এক্সচেঞ্জ আর্গুমেন্ট (exchange argument): নিম্ন-অনুপাতের উপাদানের পরিবর্তে সমপরিমাণ উচ্চ-অনুপাতের উপাদান নিন — এতে মোট মূল্য নিশ্চিতভাবে বাড়বে। সুতরাং, সর্বোচ্চ অনুপাতের ক্রম থেকে বিচ্যুত হওয়ার কোনো সুবিধা নেই।

একটি গুরুত্বপূর্ণ পার্থক্য: 0/1 ন্যাপস্যাক

এখন একই সমস্যার কথা চিন্তা করুন, কিন্তু প্রতিটি উপাদান যদি নিরেট পিণ্ড হিসেবে থাকত — যেখানে হয় পুরোটা নিতে হবে অথবা কিছুই নয়। এটাই হলো 0/1 ন্যাপস্যাক (0/1 Knapsack), এবং এখানে গ্রিডি পদ্ধতি কাজ করে না।

উদাহরণ: ব্যাগের ক্ষমতা ১০ কেজি। বস্তুসমূহ: A (৬ কেজি, ১২$, অনুপাত ২.০), B (৫ কেজি, ১০$, অনুপাত ২.০), C (৪ কেজি, ৮$, অনুপাত ২.০)। গ্রিডি পদ্ধতি (অনুপাত অনুযায়ী) হয়তো প্রথমে A নেবে (৬ কেজি, ১২$), তারপর B আর ব্যাগে ধরবে না (৬+৫=১১>১০), তাই এরপর C নেবে (৬+৪=১০, মোট ২০$)। কিন্তু দেখুন B+C মিলে কিন্তু ৫+৪=৯ কেজি হয় এবং মোট মূল্য হয় ১৮$ — এটি খারাপ। আসলে এখানে গ্রিডি কাজ করে গেছে। অন্য একটি উদাহরণ দেখা যাক: ক্ষমতা W=১০, A=(৬,১০), B=(৫,৯), C=(৫,৯)। অনুপাতসমূহ: ১.৬৭, ১.৮, ১.৮। গ্রিডি প্রথমে B+C নেবে? না — যদি টাই-ব্রেকিং এ ভুল হয় তবে হয়তো এটি প্রথমে A নিতে পারে (১০$, ৬কেজি ব্যবহৃত, ৪কেজি বাকি), তখন B বা C কেউই আর ধরবে না। মোট ১০$। কিন্তু B+C নিলে ১৮$ হতো। গ্রিডি পদ্ধতিতে এখানে ৮$ কম পাওয়া গেল।

যে মুহূর্তে বস্তুগুলো অবিভাজ্য হয়ে যায়, গ্রিডি চয়েস প্রোপার্টি ভেঙে পড়ে। তখন আপনার 2D ডায়নামিক প্রোগ্রামিং প্রয়োজন: dp[i][w] = প্রথম i সংখ্যক বস্তু এবং w ক্ষমতা ব্যবহার করে সর্বোচ্চ কত মান পাওয়া যায়।

দ্রষ্টব্য: ভগ্নাংশ নেওয়ার ক্ষমতাই হলো এখানে মূল শক্তি। আপনার ব্যাকপ্যাকের শেষ এক গ্রামও আপনি বাকি থাকা সেরা উপাদানের ঠিক সমপরিমাণ অংশ দিয়ে পূর্ণ করতে পারেন। এই নিখুঁতভাবে পূর্ণ করার ক্ষমতার কারণেই মূলত গ্রিডি চয়েস প্রোপার্টিটি সঠিক থাকে।

ফ্র্যাকশনাল ন্যাপস্যাক — মূল্য/ওজন অনুপাত অনুযায়ী গ্রিডি

def fractional_knapsack(capacity, items):
# items: list of (weight, value)
# Sort by value/weight ratio descending
items_sorted = sorted(items, key=lambda x: x[1]/x[0], reverse=True)
total_value = 0.0
remaining = capacity
for weight, value in items_sorted:
if remaining <= 0:
break
take = min(weight, remaining)
total_value += take * (value / weight)
remaining -= take
return total_value
# Items: (weight, value)
items = [(10, 60), (20, 100), (30, 120)]
capacity = 50
result = fractional_knapsack(capacity, items)
print(f"Max value: {result:.1f}")
# Take all of item 3 (30kg, $120), all of item 2 (20kg, $100),
# Total weight 50kg. Value = $240.
Output
Max value: 240.0

Complexity

⚖️ অনুপাত বের করা এবং সর্ট করা
মূল খরচ — অনুপাত বের করতে \(O(n)\) এবং সর্ট করতে \(O(n \log n)\) সময় লাগে
একবার সর্ট করা \(O(n \log n)\)
🎒 গ্রিডি ফিল স্ক্যান
অনুপাতের ক্রমানুসারে বস্তুগুলো স্ক্যান করুন এবং ব্যাগ পূর্ণ হওয়া পর্যন্ত নিতে থাকুন
একবার স্ক্যান করা \(O(n)\)
📦 স্পেস বা জায়গা
শুধুমাত্র বাকি ক্ষমতা এবং রানিং টোটাল মূল্য ট্র্যাক করতে হয়
ধ্রুবক বা কন্সট্যান্ট \(O(1)\)
🧱 0/1 ন্যাপস্যাক (তুলনা)
অবিভাজ্য বস্তু হওয়ার কারণে গ্রিডি এখানে কাজ করে না — এর জন্য ২D ডিপি টেবিল প্রয়োজন
Pseudo-polynomial \(O(n · W)\)

ছোট কুইজ

বস্তুসমূহ: A (ওজন ৪, মূল্য ২০), B (ওজন ৩, মূল্য ১৮), C (ওজন ৬, মূল্য ২৪)। ব্যাগ ৮ কেজি নিতে পারে। ফ্র্যাকশনাল ন্যাপস্যাকে সর্বোচ্চ মূল্য কত হবে?

পড়া চালিয়ে যান

গ্রিডি প্যাটার্ন বা লোভী পদ্ধতি (Greedy Pattern)
দাওয়াতের মাংসের সবচেয়ে বড় টুকরোটি সবসময় তুলে নিন — কখনো এটি সেরা সমাধান দেয়, আবার কখনো দেয় না
0/1 ন্যাপস্যাক (0/1 Knapsack)
সর্বোচ্চ মূল্যের (value) জন্য একটি ব্যাগ বা স্যুটকেস প্যাক করুন — যেখানে প্রতিটি জিনিস বা আইটেম হয় পুরোপুরি নিতে হবে নতুবা একদমই নেওয়া যাবে না (all-or-nothing)
ইন্টারভাল শিডিউলিং (Interval Scheduling)
সর্বোচ্চ সংখ্যক মিটিং সিডিউল করুন — সবসময় সেই মিটিংটি আগে বেছে নিন যা সবচেয়ে আগে শেষ হয়