পাঠ ১৯ · ৫৭-এর মধ্যে · মডিউল ৫
Home / Courses / Design and Analysis of Algorithms / গ্রিডি প্যারাডাইম

গ্রিডি প্যারাডাইম ও এক্সচেঞ্জ আর্গুমেন্ট

The greedy paradigm & the exchange argument
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • গ্রিডি অ্যালগরিদমের সাধারণ কাঠামো এবং এটি ডিভাইড-অ্যান্ড-কনকার বা DP থেকে কীভাবে আলাদা
  • গ্রিডি-চয়েস প্রপার্টি ও অপটিমাল সাবস্ট্রাকচার — একটি গ্রিডি স্ট্র্যাটেজি সঠিক হওয়ার জন্য ঠিক কী শর্ত লাগে
  • একটি প্রকৃত, কম্পিউট করা কাউন্টার-এক্সাম্পল — কেন "কয়েকটা উদাহরণে কাজ করেছে" যথেষ্ট প্রমাণ নয়
  • এক্সচেঞ্জ আর্গুমেন্ট — গ্রিডি সঠিকতা প্রমাণের সাধারণ টেমপ্লেট, যা M5-এর বাকি চারটি পাঠে নির্দিষ্ট সমস্যায় প্রয়োগ হবে

১ · গ্রিডি প্যারাডাইম কী

গ্রিডি অ্যালগরিদমGreedy Algorithmএমন একটি অ্যালগরিদমিক কৌশল যা প্রতিটি ধাপে বর্তমান পরিস্থিতিতে স্থানীয়ভাবে (locally) সবচেয়ে ভালো দেখতে চয়েসটি বেছে নেয়, এবং সেই সিদ্ধান্ত পরে আর পরিবর্তন করে না। এটি DSA কোর্সে ইতিমধ্যে পরিচিত একটি ধারণা — সেখানে অ্যাক্টিভিটি সিলেকশন, হাফম্যান কোডিং, বা MST-এর মতো নির্দিষ্ট অ্যালগরিদমগুলো কীভাবে কাজ করে তা দেখানো হয়েছে। এই কোর্স ধরে নেয় আপনি সেই মেকানিক্স জানেন — এখানে প্রশ্নটা ভিন্ন: কেন এই লোকাল সিদ্ধান্তগুলোর সমষ্টি গ্লোবাল অপটিমাম দেয়, তা কীভাবে প্রমাণ করব।

একটি গ্রিডি অ্যালগরিদমের সাধারণ কাঠামো প্রায় সবসময় একই রকম:

সমস্যা P (পুরো ইনপুট) লোকালি সেরা চয়েস g নিন, আর পুনর্বিবেচনা নয় ছোট সাবপ্রবলেম P' (g বাদ দিয়ে অবশিষ্ট) P' খালি না হলে পুনরাবৃত্তি চূড়ান্ত সমাধান (P' খালি হলে)
প্রতি ধাপে একটিমাত্র লোকাল চয়েস নেওয়া হয় এবং তা আর পাল্টানো হয় না — এখানেই গ্রিডি DP থেকে আলাদা, যেখানে একাধিক সাবপ্রবলেম একসাথে বিবেচনা করে সেরাটা বেছে নেওয়া হয় (M6-এ বিস্তারিত)।
গ্রিডি-চয়েস প্রপার্টি
সমস্যা P-এর কমপক্ষে একটি অপটিমাল সমাধান আছে যা গ্রিডির প্রথম চয়েস g-কে অন্তর্ভুক্ত করে।
অপটিমাল সাবস্ট্রাকচার
g নেওয়ার পর P-এর একটি অপটিমাল সমাধান = g + সাবপ্রবলেম P'-এর একটি অপটিমাল সমাধান।

এই দুটো শর্ত একসাথে থাকলে ইনডাকশন দিয়ে দেখানো যায় যে ধাপে ধাপে লোকাল চয়েস নেওয়াই গ্লোবাল অপটিমাম দেয়। অপটিমাল সাবস্ট্রাকচার M6/L24-এ DP-র প্রেক্ষিতেও ফিরে আসবে — পার্থক্য হলো DP-তে ওভারল্যাপিং সাবপ্রবলেম সমাধান করে সেরাটা তুলনা করা হয়, গ্রিডিতে একটিমাত্র চয়েস নিয়েই এগিয়ে যাওয়া হয় (কারণ প্রমাণ করা থাকে যে অন্য কোনো চয়েস তুলনা করার দরকারই নেই)।

২ · কেন "কয়েকটা উদাহরণে কাজ করেছে" যথেষ্ট নয়

একটি গ্রিডি স্ট্র্যাটেজি দেখতে স্বাভাবিক ও যুক্তিসঙ্গত মনে হতে পারে, এমনকি কিছু টেস্ট কেসে সঠিক উত্তরও দিতে পারে — কিন্তু তার মানে এই নয় যে এটি সবসময় সঠিক। নিচের কোড সেলে একটি ক্লাসিক উদাহরণ দেখানো হয়েছে: কয়েন-চেঞ্জ সমস্যায় "সবসময় সবচেয়ে বড় ডিনমিনেশন থেকে যতটা সম্ভব নাও" — এই গ্রিডি স্ট্র্যাটেজি প্রচলিত মুদ্রা ব্যবস্থায় ({1, 5, 10, 25}) ঠিকই কাজ করে, কিন্তু একটি ভিন্ন (কিন্তু সম্পূর্ণ বৈধ) ডিনমিনেশন সেটে ({1, 3, 4}) ভুল উত্তর দেয় — এবং আমরা তা ধরে নিচ্ছি না, প্রকৃতপক্ষে ব্রুট-ফোর্স দিয়ে যাচাই করছি।

Python
from itertools import combinations_with_replacement

def greedy_coin_change(amount, denominations):
    # সবসময় সবচেয়ে বড় ডিনমিনেশন থেকে যতটা সম্ভব নাও
    denominations = sorted(denominations, reverse=True)
    count = 0
    used = []
    remaining = amount
    for d in denominations:
        take = remaining // d
        if take:
            used.extend([d] * take)
            count += take
            remaining -= d * take
    return count, used, remaining

def brute_force_min_coins(amount, denominations):
    # প্রকৃত ন্যূনতম কয়েন সংখ্যা: k = 0, 1, 2, ... ক্রমান্বয়ে বাড়িয়ে প্রথম বৈধ কম্বিনেশন খোঁজা
    for k in range(0, amount + 1):
        for combo in combinations_with_replacement(denominations, k):
            if sum(combo) == amount:
                return k, combo
    return None, None

# কেস ১: প্রচলিত মুদ্রা ব্যবস্থা -- গ্রিডি এখানে কাকতালীয়ভাবে সঠিক
std_coins = [1, 5, 10, 25]
amount1 = 41
g_count1, g_used1, rem1 = greedy_coin_change(amount1, std_coins)
b_count1, b_used1 = brute_force_min_coins(amount1, std_coins)
print("প্রচলিত ডিনমিনেশন {1,5,10,25}, amount=41")
print("  গ্রিডি:      ", g_count1, "কয়েন ->", g_used1)
print("  ব্রুট-ফোর্স: ", b_count1, "কয়েন ->", list(b_used1))
assert rem1 == 0
print("  গ্রিডি কি অপটিমাল?", g_count1 == b_count1)

# কেস ২: একটি ইচ্ছাকৃতভাবে বেছে নেওয়া ডিনমিনেশন সেট -- গ্রিডি ব্যর্থ হয়
odd_coins = [1, 3, 4]
amount2 = 6
g_count2, g_used2, rem2 = greedy_coin_change(amount2, odd_coins)
b_count2, b_used2 = brute_force_min_coins(amount2, odd_coins)
print("\nঅস্বাভাবিক ডিনমিনেশন {1,3,4}, amount=6")
print("  গ্রিডি:      ", g_count2, "কয়েন ->", g_used2)
print("  ব্রুট-ফোর্স: ", b_count2, "কয়েন ->", list(b_used2))
assert rem2 == 0
print("  গ্রিডি কি অপটিমাল?", g_count2 == b_count2)

    
কেস ১-এ গ্রিডি ৪টি কয়েন (২৫+১০+৫+১) ব্যবহার করে, যা ব্রুট-ফোর্সের খুঁজে পাওয়া অপটিমাম-এর সমান। কিন্তু কেস ২-এ গ্রিডি প্রথমে সবচেয়ে বড় ডিনমিনেশন ৪ নেয়, তারপর ১+১ দিয়ে বাকিটা পূরণ করে — মোট ৩টি কয়েন (৪+১+১)। অথচ ব্রুট-ফোর্স দেখাচ্ছে ৩+৩ দিয়ে মাত্র ২টি কয়েনেই ৬ বানানো সম্ভব। গ্রিডি এখানে ভুল — এবং এই ভুলটা ধরা পড়ল কারণ আমরা এটিকে সত্যিকারের ব্রুট-ফোর্সের বিপরীতে যাচাই করেছি, শুধু "যুক্তিসঙ্গত মনে হচ্ছে" বলে মেনে নিইনি।

৩ · এক্সচেঞ্জ আর্গুমেন্ট — গ্রিডি সঠিকতা প্রমাণের সাধারণ কৌশল

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

  1. ধরে নাও: OPT একটি অপটিমাল সমাধান, এবং OPT প্রথম যে ধাপে গ্রিডি থেকে ভিন্ন সিদ্ধান্ত নেয় সেটি হলো ধাপ $i$।
  2. বদলাও (exchange): OPT-এর ধাপ $i$-এর চয়েসের জায়গায় গ্রিডির চয়েসটি বসিয়ে একটি নতুন সমাধান $\text{OPT}'$ তৈরি করো।
  3. দেখাও কোনো ক্ষতি হয়নি: $\text{OPT}'$ এখনও বৈধ (feasible) এবং এর মান OPT-এর সমান অথবা ভালো — অর্থাৎ $\text{OPT}'$ ও একটি অপটিমাল সমাধান।
  4. ইনডাকশন: এখন $\text{OPT}'$ ও গ্রিডি ধাপ $i$ পর্যন্ত একমত। একই যুক্তি বাকি ধাপগুলোর উপর প্রয়োগ করলে দেখা যায় গ্রিডির সম্পূর্ণ সমাধান নিজেই একটি অপটিমাল সমাধান।

লক্ষ করুন — এই যুক্তিটি এখনো বিমূর্ত (abstract); এটি কোনো নির্দিষ্ট সমস্যার কথা বলছে না। এর শক্তি ঠিক এখানেই: একবার এই টেমপ্লেট বুঝে গেলে, একই কাঠামো (ধাপ ১-৪) যেকোনো নির্দিষ্ট গ্রিডি সমস্যায় প্রয়োগ করা যায় — কেবল "ধাপ ৩"-এর প্রমাণটা প্রতিটি সমস্যার জন্য আলাদাভাবে করতে হয়। যেমন ধাপ ২-এর কাউন্টার-এক্সাম্পলে, এই এক্সচেঞ্জ আর্গুমেন্ট প্রয়োগ করলে ধাপ ৩-তেই আটকে যেত — কারণ OPT-এর ৩+৩ চয়েসকে গ্রিডির ৪-চয়েস দিয়ে বদলালে অবশিষ্ট সমস্যাটি (৬-৪=২, যা {1,3,4} দিয়ে ২টি কয়েনে তৈরি হয়, ১+১) OPT-এর চেয়ে খারাপ হয়ে যায় — এই ব্যর্থতাই বলে দেয় যে এই সমস্যায় (এই নির্দিষ্ট গ্রিডি নিয়মে) গ্রিডি-চয়েস প্রপার্টি সত্যি নয়।

L20-L23-এ কী আসছে

পরবর্তী চারটি পাঠ ঠিক এই এক্সচেঞ্জ আর্গুমেন্ট টেমপ্লেটটি চারটি ভিন্ন, সত্যিকারের অপটিমাল সমস্যায় প্রয়োগ করবে — এবং প্রতিবারই ধাপ ৩ (কোনো ক্ষতি হয়নি তা দেখানো) সফল হবে, তাই গ্রিডি সেখানে সত্যিই অপটিমাম দেয়: L20 অ্যাক্টিভিটি সিলেকশন (সবচেয়ে আগে শেষ হওয়া অ্যাক্টিভিটি বেছে নেওয়া), L21 হাফম্যান কোডিং (দুটি সবচেয়ে কম ফ্রিকোয়েন্সির নোড মার্জ করা), L22 ফ্র্যাকশনাল ন্যাপস্যাক (সবচেয়ে বেশি value/weight অনুপাতের আইটেম আগে নেওয়া), এবং L23 মিনিমাম স্প্যানিং ট্রি (কাট প্রপার্টি — একটি বিশেষ ধরনের এক্সচেঞ্জ আর্গুমেন্ট)। প্রতিটি পাঠে প্রমাণের পাশাপাশি ছোট ইনস্ট্যান্সে ব্রুট-ফোর্সের বিপরীতে কম্পিউটেশনালি যাচাইও করা হবে — ঠিক যেমন উপরের কাউন্টার-এক্সাম্পলে করা হয়েছে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ উপরের কোড সেলে গ্রিডি {1,5,10,25}-এ সঠিক উত্তর দিয়েছে। তাহলে কেন এটাকে "প্রমাণ" বলা যাবে না?

একটি বা কয়েকটি উদাহরণে সঠিক উত্তর আসা মানেই সব ইনপুটে সঠিক উত্তর আসবে তার নিশ্চয়তা নয় — এটি নিছক পর্যবেক্ষণ (observation), প্রমাণ নয়। {1,3,4} ডিনমিনেশন সেটটিই দেখাচ্ছে একই "সবচেয়ে বড় ডিনমিনেশন আগে নাও" নিয়ম একটি ভিন্ন ইনপুটে ব্যর্থ হতে পারে। প্রকৃত প্রমাণের জন্য দরকার এক্সচেঞ্জ আর্গুমেন্টের মতো একটি যুক্তি যা সব সম্ভাব্য ইনপুটের জন্য সাধারণভাবে প্রযোজ্য, শুধু নির্দিষ্ট কয়েকটি টেস্ট কেসের জন্য নয়।

প্র ০২ গ্রিডি ও DP উভয়ের জন্যই অপটিমাল সাবস্ট্রাকচার দরকার। তাহলে গ্রিডিতে মেমোয়াইজেশন/টেবিল দরকার হয় না কেন?

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

প্র ০৩ {1,3,4} ডিনমিনেশনে গ্রিডি ব্যর্থ হলো amount=6-এ। এর মানে কি এই ডিনমিনেশন সেটে গ্রিডি সবসময়ই ব্যর্থ হবে?

না — এর মানে শুধু এটাই যে এই নির্দিষ্ট গ্রিডি নিয়মটি (সবচেয়ে বড় ডিনমিনেশন আগে) {1,3,4}-এর জন্য সবসময় সঠিক এমন কোনো প্রমাণ নেই, এবং amount=6 একটি কাউন্টার-এক্সাম্পল যা প্রমাণ করে দেয় এটি সব সময় সঠিক নয়। অন্য কোনো amount-এ হয়তো কাকতালীয়ভাবে সঠিক উত্তর আসতেও পারে — কিন্তু "কিছু amount-এ সঠিক" আর "সব amount-এ সঠিক" সম্পূর্ণ ভিন্ন দাবি। একটি মাত্র কাউন্টার-এক্সাম্পলই একটি সাধারণ দাবি (universal claim) খণ্ডন করার জন্য যথেষ্ট।

অনুশীলন

  1. চিন্তা করুন: ডিনমিনেশন সেট {1,3,4}-এ amount=8-এর জন্য গ্রিডি কী উত্তর দেবে বলে মনে হয়, এবং প্রকৃত অপটিমাম কত হতে পারে?

    গ্রিডি: ৪+৪ = ২টি কয়েন (৮ কে প্রথমে ৪ দিয়ে ভাগ করলে ঠিক ২ বার নেওয়া যায়, অবশিষ্ট ০)। এক্ষেত্রে গ্রিডিই কাকতালীয়ভাবে অপটিমাম (২টি কয়েনের কমে ৮ বানানো সম্ভব নয়, কারণ একক কয়েনের মান সর্বোচ্চ ৪)। এটাই দেখায় গ্রিডি "কখনো কখনো" সঠিক হতে পারে এমনকি এমন ডিনমিনেশন সেটেও যেখানে প্রমাণ নেই — কিন্তু তা এখনও প্রমাণ নয়, নিছক কাকতালীয়তা।

  2. পরীক্ষা করুন: উপরের কোড সেলে amount2 = 6-এর জায়গায় amount2 = 8 বসিয়ে Run চেপে আপনার অনুমান যাচাই করুন। তারপর amount2 = 10 দিয়ে আবার চালিয়ে দেখুন গ্রিডি আবার ব্যর্থ হয় কি না।

    amount2 = 8-এ গ্রিডি ও ব্রুট-ফোর্স উভয়ই ২টি কয়েন দেবে (৪+৪) — একমত। কিন্তু amount2 = 10-এ গ্রিডি নেবে ৪+৪+১+১ = ৪টি কয়েন, অথচ ব্রুট-ফোর্স খুঁজে পাবে ৩+৩+৪ = ৩টি কয়েনের একটি সমাধান — ফের গ্রিডি ব্যর্থ। এটাই প্রমাণ করে যে গ্রিডি এই ডিনমিনেশন সেটে নির্ভরযোগ্য নয় — কিছু amount-এ কাজ করে, কিছুতে করে না।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
স্ট্রাসেনের গুণন ও মিডিয়ান ফাইন্ডিং অ্যালগরিদম