পাঠ ৫৩ · ৫৭-এর মধ্যে · মডিউল ১২
Home / Courses / Design and Analysis of Algorithms / লাস ভেগাস বনাম মন্টি কার্লো

র‍্যান্ডোমাইজড অ্যালগরিদম — লাস ভেগাস বনাম মন্টি কার্লো

Randomized algorithms — Las Vegas vs Monte Carlo
১২ মিনিট পড়া মধ্যম-কঠিন · Intermediate-Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • লাস ভেগাস ও মন্টি কার্লো অ্যালগরিদমের আনুষ্ঠানিক সংজ্ঞা এবং মূল পার্থক্য
  • একটি সম্পূর্ণ লাস ভেগাস উদাহরণ — র‍্যান্ডোমাইজড কুইকসিলেক্ট, সবসময়ের সঠিকতা যাচাইসহ ও রানটাইম তারতম্য দেখানো
  • একটি সম্পূর্ণ মন্টি কার্লো উদাহরণ — স্যাম্পলিং-ভিত্তিক ব্যালেন্স-চেকার, সৎভাবে গণনা করা এরর রেটসহ
  • কেন fixed seed ব্যবহার করেও "এক্সপেক্টেড" আচরণ বুঝতে বহু ভিন্ন ট্রায়াল/seed দরকার

১ · র‍্যান্ডোমাইজেশন — ইনট্র্যাক্টেবিলিটি সামলানোর আরেকটি টুল

L50-L52-এ আমরা দেখেছি অ্যাপ্রক্সিমেশন কীভাবে একটি প্রমাণিত মানের গ্যারান্টি বজায় রেখে পলিনোমিয়াল সময় নিশ্চিত করে। র‍্যান্ডোমাইজেশন একটি ভিন্ন কৌশল: অ্যালগরিদমকে তার নিজের সিদ্ধান্তে (যেমন কোন পিভট বাছাই, কোন স্যাম্পল দেখা) কিছুটা র‍্যান্ডমনেস দেওয়া হয়। ফলাফল সবসময় ডিটারমিনিস্টিক অ্যালগরিদমের চেয়ে ভালো নাও হতে পারে — কিন্তু সঠিকভাবে ডিজাইন করা হলে, এটি এমন গ্যারান্টি দিতে পারে যা কোনো ডিটারমিনিস্টিক অ্যালগরিদমের পক্ষে (ঐ একই সময়ের মধ্যে) দেওয়া কঠিন — যেমন L54-এ দেখা যাবে, র‍্যান্ডোমাইজড কুইকসর্ট এমন ইনপুটেও দ্রুত থাকে যেখানে যেকোনো ফিক্সড-পিভট সংস্করণ ধীর হতে বাধ্য (L16)।

র‍্যান্ডোমাইজড অ্যালগরিদমকে দুটি মৌলিক শ্রেণিতে ভাগ করা যায় — প্রশ্নটা হলো: কোন জিনিসটা র‍্যান্ডম, আউটপুট নাকি রানটাইম?

র‍্যান্ডোমাইজড অ্যালগরিদম লাস ভেগাস সঠিকতা: সবসময় ঠিক ✓ রানটাইম: র‍্যান্ডম ভ্যারিয়েবল উদাহরণ: র‍্যান্ডোমাইজড কুইকসিলেক্ট মন্টি কার্লো সঠিকতা: র‍্যান্ডম ভ্যারিয়েবল রানটাইম: বাউন্ডেড, ডিটারমিনিস্টিক উদাহরণ: স্যাম্পলিং ব্যালেন্স-চেকার
প্রশ্নটা সবসময় এই একটাই: র‍্যান্ডমনেস কী প্রভাবিত করছে — আউটপুটের সঠিকতা, নাকি অ্যালগরিদম শেষ হতে কত সময় লাগে?

২ · লাস ভেগাস অ্যালগরিদম — র‍্যান্ডোমাইজড কুইকসিলেক্ট

লাস ভেগাস অ্যালগরিদমLas Vegas Algorithmএকটি র‍্যান্ডোমাইজড অ্যালগরিদম যা প্রতিটি রানে অবশ্যই সঠিক আউটপুট দেয়; শুধু রানটাইম (কতটুকু সময় লাগবে) একটি র‍্যান্ডম ভ্যারিয়েবল। একটি ক্লাসিক উদাহরণ: L18-এ ভূমিকা পাওয়া কুইকসিলেক্ট — একটি অ্যারেতে $k$-তম ক্ষুদ্রতম এলিমেন্ট খুঁজে বের করার অ্যালগরিদম, যা কুইকসর্টের পার্টিশন ধাপ ব্যবহার করে কিন্তু শুধু একটি দিকেই রিকার্স করে। পিভট র‍্যান্ডম বেছে নেওয়া হলে:

  • আউটপুট সবসময় সঠিক — পার্টিশনিং লজিক নিজেই সঠিক, পিভট যেটাই হোক না কেন।
  • রিকার্শন কতগুলো ধাপে শেষ হবে (তাই কতগুলো তুলনা লাগবে) তা পিভটের উপর নির্ভরশীল, এবং পিভট র‍্যান্ডম — তাই রানটাইম রান থেকে রানে ভিন্ন হতে পারে।
Python
import random

def randomized_select(rng, arr, k):
    # arr-এর মধ্যে k-তম ক্ষুদ্রতম (0-ইনডেক্সড) এলিমেন্ট বের করা -- সবসময় সঠিক, রানটাইম র‍্যান্ডম
    arr = list(arr)
    lo, hi = 0, len(arr) - 1
    comparisons = 0
    while True:
        if lo == hi:
            return arr[lo], comparisons
        pivot_idx = rng.randint(lo, hi)          # -- এই লাইনটাই একমাত্র র‍্যান্ডম সিদ্ধান্ত
        arr[pivot_idx], arr[hi] = arr[hi], arr[pivot_idx]
        pivot = arr[hi]
        store = lo
        for i in range(lo, hi):
            comparisons += 1
            if arr[i] < pivot:
                arr[i], arr[store] = arr[store], arr[i]
                store += 1
        arr[store], arr[hi] = arr[hi], arr[store]
        if store == k:
            return arr[store], comparisons
        elif k < store:
            hi = store - 1
        else:
            lo = store + 1

master_rng = random.Random(2026)
n = 50
k = n // 2   # মিডিয়ানের কাছাকাছি
test_arr = [master_rng.randint(-1000, 1000) for _ in range(n)]
true_answer = sorted(test_arr)[k]

comparison_counts = []
all_correct = True
for seed in range(20):
    sort_rng = random.Random(seed)
    result, c = randomized_select(sort_rng, test_arr, k)
    comparison_counts.append(c)
    if result != true_answer:
        all_correct = False
        print(f"seed {seed}: MISMATCH! পেয়েছি {result}, প্রত্যাশিত {true_answer}")
    assert result == true_answer

print(f"একই অ্যারেতে ২০টি ভিন্ন seed-এ randomized_select চালানো হলো (একই k={k} খুঁজতে):")
print(f"সবগুলো রানই সঠিক উত্তর ({true_answer}) দিয়েছে: {all_correct}")
print(f"\nতুলনার সংখ্যা -- সর্বনিম্ন: {min(comparison_counts)}, সর্বোচ্চ: {max(comparison_counts)}, "
      f"গড়: {sum(comparison_counts)/len(comparison_counts):.1f}")
print("লক্ষ্য করুন: আউটপুট সবসময় একই (সঠিক), কিন্তু তুলনার সংখ্যা (রানটাইম) seed অনুযায়ী পাল্টায় -- এটাই লাস ভেগাসের সংজ্ঞা।")

    
random.Random(seed) দিয়ে প্রতিটি ট্রায়ালে একটি স্বতন্ত্র, নির্দিষ্ট (কিন্তু ভিন্ন) র‍্যান্ডম জেনারেটর তৈরি করা হয়েছে — একই ইনপুট অ্যারেতে বারবার চালিয়েও প্রতিটি ফলাফল পুনরুৎপাদনযোগ্য (reproducible) থাকে, অথচ ভিন্ন seed ভিন্ন পিভট-ক্রম বেছে নেয় বলে তুলনার সংখ্যা বদলায়। এই আচরণটাই — আউটপুট স্থির, রানটাইম পরিবর্তনশীল — লাস ভেগাস অ্যালগরিদমের সংজ্ঞায়িত বৈশিষ্ট্য।

৩ · মন্টি কার্লো অ্যালগরিদম — একটি স্যাম্পলিং-ভিত্তিক ব্যালেন্স-চেকার

মন্টি কার্লো অ্যালগরিদমMonte Carlo Algorithmএকটি র‍্যান্ডোমাইজড অ্যালগরিদম যার রানটাইম বাউন্ডেড (নির্দিষ্ট বা প্রায় নির্দিষ্ট), কিন্তু আউটপুট ভুল হওয়ার একটি নির্দিষ্ট, পরিমাপযোগ্য (এবং সাধারণত ছোট) সম্ভাবনা থাকে। বাস্তব জগতে মিলার-রেবিন প্রাইমালিটি টেস্ট বা ফ্রেইভাল্ডসের ম্যাট্রিক্স-গুণন-যাচাই এর সুপরিচিত উদাহরণ — এখানে আমরা ধারণাটি concrete করতে একটি সরল, নিজস্ব উদাহরণ তৈরি করব।

সমস্যা: একটি বাইনারি স্ট্রিং $s$ (দৈর্ঘ্য $N$) দেওয়া আছে — বলো এটি ব্যালেন্সড কি না, অর্থাৎ $1$-এর সংখ্যা কি $N/2$-এর ৫%-এর মধ্যে আছে ($|{\text{count}(1)}/N - 0.5| \le 0.05$)। পুরো স্ট্রিং স্ক্যান করলে ($O(N)$) নিশ্চিত সঠিক উত্তর পাওয়া যায় — কিন্তু $N$ বিশাল হলে, মন্টি কার্লো পদ্ধতি হলো শুধু $k \ll N$টি র‍্যান্ডম পজিশন স্যাম্পল করে সেই স্যাম্পলের অনুপাত দিয়ে সিদ্ধান্ত নেওয়া।

Python
import random

def is_balanced_exact(bits):
    N = len(bits)
    ones = sum(bits)
    return abs(ones / N - 0.5) <= 0.05

def is_balanced_monte_carlo(rng, bits, sample_size):
    # শুধু sample_size টি র‍্যান্ডম পজিশন দেখে সিদ্ধান্ত -- সম্পূর্ণ স্ট্রিং স্ক্যান করা হয় না
    N = len(bits)
    positions = rng.sample(range(N), sample_size)
    ones_in_sample = sum(bits[i] for i in positions)
    fraction = ones_in_sample / sample_size
    return abs(fraction - 0.5) <= 0.05

rng = random.Random(1729)
N = 3000
sample_size = 300
p_values = [0.25, 0.38, 0.47, 0.50, 0.53, 0.62, 0.75]   # কিছু স্পষ্ট ব্যালেন্সড/আনব্যালেন্সড, কিছু বাউন্ডারির কাছাকাছি

num_trials = 300
mismatches = 0
for _ in range(num_trials):
    p = rng.choice(p_values)
    bits = [1 if rng.random() < p else 0 for _ in range(N)]
    exact = is_balanced_exact(bits)
    mc = is_balanced_monte_carlo(rng, bits, sample_size)
    if exact != mc:
        mismatches += 1

error_rate = mismatches / num_trials
print(f"মোট ট্রায়াল: {num_trials}, sample_size={sample_size} (পূর্ণ N={N}-এর মাত্র {100*sample_size/N:.0f}%)")
print(f"এক্স্যাক্ট চেক ও মন্টি কার্লো চেক ভিন্নমত হয়েছে এমন ট্রায়াল: {mismatches}")
print(f"সৎভাবে গণনা করা এরর রেট: {error_rate:.3f} ({100*error_rate:.1f}%)")
print("\nএটি লাস ভেগাস নয় -- মাঝেমধ্যে ভুল উত্তর আসে (বিশেষত p=0.47/0.53-এর মতো বাউন্ডারির কাছাকাছি কেসে),")
print("কিন্তু রানটাইম সবসময় O(sample_size)-এ বাউন্ডেড, N যত বড়ই হোক না কেন।")

    
লক্ষ্য করুন এই কোডে কোনো assert error_rate == 0 নেই — এটা ইচ্ছাকৃত এবং সৎ। মন্টি কার্লো অ্যালগরিদমের সংজ্ঞাই বলে এর ভুল হওয়ার সম্ভাবনা আছে; বিশেষত $p=0.47$ বা $p=0.53$-এর মতো সিদ্ধান্তের বাউন্ডারির (থ্রেশহোল্ড $0.05$) কাছাকাছি প্রকৃত অনুপাতে, একটি ছোট স্যাম্পল ($N$-এর মাত্র ১০%) মাঝেমধ্যে ভুল দিকে ঝুঁকে যেতে পারে বাইনোমিয়াল ভেরিয়েন্সের কারণে। এই এরর রেটটি প্রতিটি রানে সত্যিকারের গণনা থেকে এসেছে, কোনো অনুমান নয় — এবং $p$-এর মান বাউন্ডারি থেকে যত দূরে থাকবে (যেমন $p=0.25$ বা $p=0.75$), ততই এরর রেট কমবে (এটি নিজে চালিয়ে p_values বদলে যাচাই করা যায়)।
মূল কথা · Key takeaway

দুটো ক্লাসের মধ্যে পছন্দ নির্ভর করে সমস্যার প্রয়োজনীয়তার উপর: সঠিকতা আপোসহীন হলে (যেমন একটি সাজানো লিস্ট ফেরত দেওয়া) লাস ভেগাস ভালো — রানটাইমের সামান্য তারতম্য সহনীয়। কিন্তু কঠোর সময়সীমা থাকলে (যেমন রিয়েল-টাইম সিস্টেম) এবং সামান্য এরর সহনীয় হলে, মন্টি কার্লো একটি নির্ভরযোগ্য, বাউন্ডেড-টাইম বিকল্প দেয় — প্রায়ই বহুবার চালিয়ে ও ভোট নিয়ে (majority vote) এরর সম্ভাবনা আরও কমানো যায় (একটি সাধারণ কৌশল যা এই পাঠের সরাসরি সুযোগের বাইরে, কিন্তু একই মূলনীতির উপর দাঁড়িয়ে)। L54-এ আমরা একটি লাস ভেগাস অ্যালগরিদমের (র‍্যান্ডোমাইজড কুইকসর্ট) এক্সপেক্টেড-টাইম বিশ্লেষণে গভীরে যাব।

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

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

প্র ০১ একটি অ্যালগরিদম কি একই সাথে লাস ভেগাস এবং মন্টি কার্লো দুটোই হতে পারে?

সাধারণভাবে না — দুটো শ্রেণি পরস্পরবিরোধী সংজ্ঞার উপর দাঁড়িয়ে (একটিতে সঠিকতা স্থির, অন্যটিতে রানটাইম স্থির)। তবে একটি লাস ভেগাস অ্যালগরিদমকে সবসময় একটি মন্টি কার্লো অ্যালগরিদমে "রূপান্তর" করা যায়: একটি সময়সীমা বেঁধে দাও, সময় শেষ হয়ে গেলে অ্যালগরিদম থামিয়ে একটি ডিফল্ট (হয়তো ভুল) উত্তর দাও। এভাবে রানটাইম বাউন্ডেড হয়ে যায়, কিন্তু বিনিময়ে সঠিকতার নিশ্চয়তা হারায় — precisely মন্টি কার্লোর সংজ্ঞা।

প্র ০২ উপরের মন্টি কার্লো কোডে sample_size ৩০০ থেকে বাড়িয়ে ৮০০ করলে এরর রেটের কী হবে বলে মনে হয়?

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

প্র ০৩ র‍্যান্ডোমাইজড কুইকসিলেক্ট কোডে, যদি pivot_idx = rng.randint(lo, hi)-এর বদলে সবসময় pivot_idx = hi (ফিক্সড পিভট) ব্যবহার করা হতো, তাহলে কি এটি এখনো লাস ভেগাস থাকত?

সঠিকতার দিক থেকে হ্যাঁ (পার্টিশনিং লজিক পিভট নির্বাচনের উপর নির্ভর করে না, তাই আউটপুট তখনো সবসময় সঠিক থাকত), কিন্তু এটি আর একটি অর্থবহ "র‍্যান্ডোমাইজড" অ্যালগরিদম থাকত না — কোনো র‍্যান্ডমনেসই অবশিষ্ট নেই। আরও গুরুত্বপূর্ণ, L16-এ দেখানো হয়েছে (এবং L54-এ বিস্তারিত) — একটি ফিক্সড পিভট নির্দিষ্ট adversarial ইনপুটে (যেমন সাজানো অ্যারে) বারবার সবচেয়ে খারাপ বিভাজন দিতে পারে, রানটাইমকে র‍্যান্ডমের বদলে নিশ্চিতভাবে খারাপ করে তোলে — এটাই মূলত এই লেসনের লাস ভেগাস গ্যারান্টি হারানোর একটি বাস্তব উদাহরণ।

অনুশীলন

  1. চিন্তা করুন: মন্টি কার্লো কোডে p_values-এ যদি শুধু [0.1, 0.9] রাখা হতো (দুটোই থ্রেশহোল্ড থেকে অনেক দূরে), তাহলে এরর রেট কেমন হবে বলে মনে হয়?

    এরর রেট প্রায় শূন্যের কাছাকাছি হবে। $p=0.1$ বা $p=0.9$-এ, এমনকি একটি ছোট স্যাম্পলও প্রায় নিশ্চিতভাবে সত্যিকারের অনুপাতের ($0.1$ বা $0.9$) কাছাকাছি একটি মান দেবে, যা $0.05$ থ্রেশহোল্ড থেকে অনেক দূরে — তাই স্যাম্পলিং ভেরিয়েন্সের কারণে ভুল দিকে সিদ্ধান্ত পাল্টানোর সম্ভাবনা প্রায় নেই। এটাই দেখায় মন্টি কার্লোর এরর রেট ইনপুটের উপর নির্ভরশীল — সব ইনপুটে সমান নয়, বরং থ্রেশহোল্ডের কাছাকাছি ইনপুটেই বেশি।

  2. পরীক্ষা করুন: উপরের মন্টি কার্লো কোড সেলে p_values-কে [0.1, 0.9]-এ পরিবর্তন করে Run চেপে আপনার অনুমান যাচাই করুন, তারপর p_values = [0.49, 0.50, 0.51] (থ্রেশহোল্ডের একদম কাছে) দিয়ে আবার চালিয়ে পার্থক্য লক্ষ্য করুন।

    [0.1, 0.9]-এ এরর রেট প্রায় $0$ হবে (উপরের অনুমান নিশ্চিত হবে)। কিন্তু [0.49, 0.50, 0.51]-এ এরর রেট অনেক বেশি হবে, কারণ এই তিনটি $p$-ই সত্যিকারের ব্যালেন্স-থ্রেশহোল্ডের ভেতরে বা খুব কাছাকাছি — একটি ছোট স্যাম্পলের র‍্যান্ডম ওঠানামাই সহজে সিদ্ধান্ত উল্টে দিতে পারে। এটি হাতে-কলমে দেখায় কেন মন্টি কার্লো অ্যালগরিদমের এরর রেট নিয়ে কথা বলার সময় ইনপুট ডিস্ট্রিবিউশনটাও উল্লেখ করা জরুরি, শুধু একটি একক সংখ্যা যথেষ্ট নয়।

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

আগের পাঠ
মেট্রিক TSP অ্যাপ্রক্সিমেশন