পাঠ ২৮ · ৫৭-এর মধ্যে · মডিউল ৬
Home / Courses / Design and Analysis of Algorithms / ডাইনামিক প্রোগ্রামিং

ম্যাট্রিক্স চেইন মাল্টিপ্লিকেশন

Matrix chain multiplication
৮ মিনিট পড়া উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • ম্যাট্রিক্স চেইন মাল্টিপ্লিকেশনের ইন্টারভাল DP রাজ্য ও রিকারেন্স
  • কেন এই সমস্যায় "স্প্লিট পয়েন্ট" নিয়ে চিন্তা করা প্রাকৃতিক, এবং এর অপটিমাল সাবস্ট্রাকচার প্রমাণ
  • নেইভ রিকার্সিভ ব্রুট-ফোর্স (ক্যাটালান-সংখ্যক প্যারেন্থেসাইজেশন) বনাম DP-এর বাস্তব যাচাই
  • ইন্টারভাল DP-তে গণনার ক্রম কেন "দৈর্ঘ্য অনুযায়ী ছোট থেকে বড়" হতে হয়

১ · সমস্যার সংজ্ঞা

$n$টি ম্যাট্রিক্স $A_1, A_2, \ldots, A_n$-এর একটি চেইন গুণ করতে হবে, যেখানে $A_i$-এর মাত্রা $p_{i-1} \times p_i$ (একটি অ্যারে $p[0 \ldots n]$ দিয়ে বর্ণিত, যেন পাশাপাশি ম্যাট্রিক্সগুলো গুণনযোগ্য হয়)। ম্যাট্রিক্স গুণন সহযোগী (associative) — $(A_1 A_2) A_3 = A_1 (A_2 A_3)$ — তাই চূড়ান্ত ফলাফল একই থাকে যেভাবেই প্যারেন্থেসাইজ করা হোক না কেন। কিন্তু দুটি ম্যাট্রিক্স ($p \times q$ এবং $q \times r$) গুণ করতে ঠিক $p \cdot q \cdot r$টি স্কেলার গুণন লাগে, এবং প্যারেন্থেসাইজেশনের উপর নির্ভর করে মোট স্কেলার গুণনের সংখ্যা অনেক ভিন্ন হতে পারে। লক্ষ্য: সর্বনিম্ন মোট স্কেলার গুণন প্রয়োজন এমন প্যারেন্থেসাইজেশন খুঁজে বের করা (আমাদের এখানে শুধু সেই সর্বনিম্ন সংখ্যাটিই বের করতে হবে, প্রকৃত গুণন নয়)।

২ · ইন্টারভাল DP রিকারেন্স

$dp[i][j]$ সংজ্ঞায়িত করি $A_i, A_{i+1}, \ldots, A_j$ ($i \le j$) গুণ করার সর্বনিম্ন স্কেলার-গুণন খরচ হিসেবে। বেস কেস $dp[i][i] = 0$ (একটি একক ম্যাট্রিক্স গুণ করার কিছু নেই)।

$$ dp[i][j] = \min_{i \le k < j} \Big(\, dp[i][k] + dp[k+1][j] + p_{i-1} \cdot p_k \cdot p_j \,\Big), \qquad i < j $$

এখানে $k$ হলো "স্প্লিট পয়েন্ট" — চেইনটি $(A_i \cdots A_k)$ এবং $(A_{k+1} \cdots A_j)$ এই দুই ভাগে ভাগ করা হয়, প্রতিটি ভাগ আলাদাভাবে (রিকার্সিভভাবে) সবচেয়ে সস্তায় গুণ করা হয়, এবং শেষে এই দুই ভাগের ফলাফল দুটি ম্যাট্রিক্স (মাত্রা $p_{i-1} \times p_k$ এবং $p_k \times p_j$) হিসেবে গুণ করতে $p_{i-1} \cdot p_k \cdot p_j$ অতিরিক্ত স্কেলার-গুণন লাগে। সব সম্ভাব্য $k$ ($i$ থেকে $j-1$ পর্যন্ত) পরীক্ষা করে সবচেয়ে সস্তাটি বেছে নেওয়া হয়।

A_i ... A_j (পুরো রেঞ্জ) A_i ... A_k খরচ dp[i][k] A_(k+1) ... A_j খরচ dp[k+1][j] + p[i-1]·p[k]·p[j] দুই ফলাফল গুণ করতে
প্রতিটি সম্ভাব্য স্প্লিট পয়েন্ট $k$ পরীক্ষা করে সবচেয়ে সস্তাটি বেছে নেওয়া হয় — এটাই $dp[i][j]$।

অপটিমাল সাবস্ট্রাকচার প্রমাণ: ধরা যাক $A_i \cdots A_j$-এর একটি অপটিমাল প্যারেন্থেসাইজেশনের সবচেয়ে বাইরের গুণনটি ঠিক $k^*$ অবস্থানে ভাগ করে (অর্থাৎ $(A_i \cdots A_{k^*})(A_{k^*+1} \cdots A_j)$)। তাহলে $(A_i \cdots A_{k^*})$-এর নিজস্ব প্যারেন্থেসাইজেশনও অবশ্যই $A_i \cdots A_{k^*}$-এর জন্য অপটিমাল হতে হবে — কারণ যদি এর চেয়ে সস্তা কোনো প্যারেন্থেসাইজেশন থাকত, সেটি বসিয়ে দিলে (বাকি সব অপরিবর্তিত রেখে) পুরো চেইনের মোট খরচ আরও কমে যেত, যা মূল সমাধানের অপটিমাল হওয়ার সাথে সাংঘর্ষিক। একই যুক্তি $(A_{k^*+1} \cdots A_j)$-এর জন্যও প্রযোজ্য। তাই অপটিমাল সমাধান তার দুই সাবচেইনের অপটিমাল সমাধান থেকে গঠিত — অপটিমাল সাবস্ট্রাকচার। আর $dp[i][k]$-এর মতো একই সাবচেইন বহু ভিন্ন বাইরের স্প্লিট পয়েন্টের হিসাবে পুনরায় প্রয়োজন হয় — ওভারল্যাপিং সাবপ্রবলেম।

৩ · যাচাই — নেইভ রিকার্সিভ ব্রুট-ফোর্স বনাম DP

ব্রুট-ফোর্স ভার্সনটি ঠিক উপরের রিকারেন্সের মতোই সব সম্ভাব্য স্প্লিট পয়েন্ট $k$ চেষ্টা করে, কিন্তু কোনো cache ছাড়া — অর্থাৎ এটি প্রকৃতপক্ষে সব সম্ভাব্য প্যারেন্থেসাইজেশন রিকার্সিভভাবে পরীক্ষা করে (মোট সংখ্যা ক্যাটালান সংখ্যা অনুসারে বাড়ে, যা এক্সপোনেনশিয়াল)। ছোট চেইনে ($n \le 6$) এটি এখনও দ্রুত চলে।

Python
import random

def mcm_brute(p, i, j):
    # নেইভ রিকার্সন -- সব সম্ভাব্য স্প্লিট পয়েন্ট, কোনো cache নেই
    if i == j:
        return 0
    best = None
    for k in range(i, j):
        cost = mcm_brute(p, i, k) + mcm_brute(p, k + 1, j) + p[i - 1] * p[k] * p[j]
        if best is None or cost < best:
            best = cost
    return best

def mcm_dp(p):
    # ইন্টারভাল DP -- দৈর্ঘ্য অনুযায়ী ছোট থেকে বড় রেঞ্জ পূরণ
    n = len(p) - 1  # ম্যাট্রিক্স সংখ্যা
    dp = [[0] * (n + 1) for _ in range(n + 1)]
    for length in range(2, n + 1):          # সাবচেইনের দৈর্ঘ্য (কতগুলো ম্যাট্রিক্স)
        for i in range(1, n - length + 2):
            j = i + length - 1
            dp[i][j] = min(
                dp[i][k] + dp[k + 1][j] + p[i - 1] * p[k] * p[j]
                for k in range(i, j)
            )
    return dp[1][n]

rng = random.Random(3)
print(f"{'dims p':>25} | {'brute':>6} | {'dp':>6}")
for trial in range(8):
    n = rng.randint(2, 6)
    p = [rng.randint(2, 20) for _ in range(n + 1)]
    b = mcm_brute(p, 1, n)
    d = mcm_dp(p)
    assert b == d, "মিসম্যাচ!"
    print(f"{str(p):>25} | {b:>6} | {d:>6}")

print("\nক্লাসিক CLRS উদাহরণ (৬টি ম্যাট্রিক্স):")
p = [30, 35, 15, 5, 10, 20, 25]
print("p =", p, "-> সর্বনিম্ন স্কেলার-গুণন:", mcm_dp(p), "(brute:", mcm_brute(p, 1, len(p) - 1), ")")

    
বাস্তবে চালালে যেমন p = [14, 15, 14, 20, 16, 6] (৫টি ম্যাট্রিক্সের চেইন)-এ উভয় পদ্ধতিই ৬,১২০ স্কেলার-গুণন দেয়। CLRS-এর ক্লাসিক ৬-ম্যাট্রিক্সের উদাহরণে ($p = [30, 35, 15, 5, 10, 20, 25]$) উভয় পদ্ধতিই ১৫,১২৫ দেয় — যা টেক্সটবুকের পরিচিত ফলাফলের সাথে সরাসরি মিলে যায়। প্রতিটি টেস্ট কেসেই brute ও dp কলাম অভিন্ন, কারণ দুটোই একই রিকারেন্স সমাধান করছে।
মূল কথা · Key takeaway

ম্যাট্রিক্স চেইন মাল্টিপ্লিকেশন দেখায় DP-এর রাজ্য সবসময় "প্রিফিক্স" হতে হয় না — এখানে রাজ্য একটি ইন্টারভাল $[i, j]$। এই কাঠামোর জন্য গণনার ক্রমও ভিন্ন হতে হয়: ছোট ইন্টারভাল (দৈর্ঘ্য ১, তারপর ২, তারপর ৩...) আগে পূরণ করতে হয়, কারণ প্রতিটি $dp[i][j]$ ছোট ইন্টারভাল $dp[i][k]$ ও $dp[k+1][j]$-এর উপর নির্ভর করে। এই "দৈর্ঘ্য অনুযায়ী ইটারেট করা" প্যাটার্নটি পরবর্তী ইন্টারভাল-ভিত্তিক DP সমস্যাগুলোতেও (এই কোর্সের বাইরে, যেমন নির্দিষ্ট গ্রাফ ও স্ট্রিং সমস্যায়) কাজে লাগবে।

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

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

প্র ০১ ট্যাবুলেশন কোডে বাইরের লুপ length (রেঞ্জের দৈর্ঘ্য) দিয়ে কেন চলে, i বা j সরাসরি দিয়ে নয়?

কারণ $dp[i][j]$-এর নির্ভরতা $dp[i][k]$ ও $dp[k+1][j]$-এর উপর, যেগুলো উভয়েই $[i,j]$-এর চেয়ে ছোট ইন্টারভাল। তাই নিশ্চিত করতে হবে সব ছোট ইন্টারভাল আগে গণনা হয়ে গেছে। "দৈর্ঘ্য অনুযায়ী ছোট থেকে বড়" ইটারেট করলে এই শর্ত স্বয়ংক্রিয়ভাবে পূরণ হয় — দৈর্ঘ্য $L$-এর যেকোনো ইন্টারভালের সাবইন্টারভাল সবসময় দৈর্ঘ্য $L$-এর চেয়ে ছোট, যা ইতিমধ্যে গণনা হয়ে গেছে।

প্র ০২ একটি চেইনে $n$টি ম্যাট্রিক্স থাকলে সম্ভাব্য প্যারেন্থেসাইজেশনের সংখ্যা ক্যাটালান সংখ্যা অনুসারে বাড়ে — এটি কি বহুপদী (polynomial) না এক্সপোনেনশিয়াল?

এক্সপোনেনশিয়াল — $(n{-}1)$-তম ক্যাটালান সংখ্যা $C_{n-1} = \binom{2(n-1)}{n-1}/n$ আকারে বাড়ে, যা $\Theta(4^n / n^{1.5})$-এর সমতুল্য। এই কারণেই নেইভ ব্রুট-ফোর্স $n$ একটু বড় হলেই (যেমন $n > 15$-২০) ধীর হয়ে যায়, অথচ DP মাত্র $O(n^3)$ সময়ে (এবং $O(n^2)$ স্পেসে) একই উত্তর দেয় — ইন্টারভাল DP-এর ক্লাসিক সাফল্যের একটি উদাহরণ।

প্র ০৩ যদি সব ম্যাট্রিক্স বর্গাকার (square, $p_0 = p_1 = \cdots = p_n$) হয়, তাহলে কি সব প্যারেন্থেসাইজেশনের খরচ একই হবে?

হ্যাঁ — যদি সব $p_i$ সমান (ধরুন $c$) হয়, তাহলে $p_{i-1} \cdot p_k \cdot p_j = c^3$ সবসময়, এবং প্রতিটি প্যারেন্থেসাইজেশনে ঠিক $n-1$টি দুই-ম্যাট্রিক্স গুণন হয় (যেকোনো বাইনারি ট্রি-স্ট্রাকচারে $n$টি পাতার জন্য $n-1$টি অভ্যন্তরীণ নোড থাকে) — তাই মোট খরচ সবসময় $(n-1) \cdot c^3$, প্যারেন্থেসাইজেশন নির্বিশেষে। এই বিশেষ কেসে DP-এর সুবিধা হারিয়ে যায় (যেকোনো ক্রম সমান ভালো), যা দেখায় কেন সাধারণভাবে মাত্রাগুলো ভিন্ন হলেই সমস্যাটি আকর্ষণীয় হয়ে ওঠে।

অনুশীলন

  1. চিন্তা করুন: $p = [10, 20, 30]$ (দুটি ম্যাট্রিক্স, $A_1$: $10{\times}20$, $A_2$: $20{\times}30$) — এখানে শুধু একটিই প্যারেন্থেসাইজেশন সম্ভব। খরচ কত হবে হাতে গণনা করুন।

    খরচ $= p_0 \cdot p_1 \cdot p_2 = 10 \times 20 \times 30 = 6{,}000$। মাত্র দুটি ম্যাট্রিক্সের চেইনে কোনো স্প্লিট-পয়েন্ট বাছাইয়ের সুযোগ নেই ($k$ শুধু $i$ হতে পারে), তাই DP ও ব্রুট-ফোর্স উভয়েই সরাসরি একই একমাত্র উত্তরে পৌঁছাবে।

  2. পরীক্ষা করুন: উপরের কোড সেলে p = [10, 20, 30] বসিয়ে mcm_dp(p) ও mcm_brute(p, 1, 2) কল করে Run চাপুন, ফলাফল আপনার হাতে-গোনা ৬,০০০-এর সাথে মিলছে কি না দেখুন।

    দুটো ফাংশনই ৬০০০ ফেরত দেবে — উপরের হাতে-গোনা উত্তরের সাথে হুবহু মিলে যায়, যা এই তুচ্ছ (কোনো প্রকৃত সিদ্ধান্ত ছাড়া) কেসেও রিকারেন্সের সঠিকতা নিশ্চিত করে।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ পরবর্তী পাঠে (L29) আমরা এডিট ডিস্ট্যান্স কভার করব — M6-এর শেষ পাঠ, যেখানে আমরা L24 থেকে এখানকার সব DP সমস্যার অপটিমাল সাবস্ট্রাকচার ও ওভারল্যাপিং সাবপ্রবলেম ধারণা একত্র করে সাধারণীকরণ করব।
  • Data Structures & Algorithms কোর্স সহোদর কোর্স ম্যাট্রিক্স চেইন মাল্টিপ্লিকেশনের ইমপ্লিমেন্টেশন ও প্রকৃত অপটিমাল প্যারেন্থেসাইজেশন পুনর্গঠন সেই কোর্সে দেখানো হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks, Mobile App Development, Ethics in Computing & AI Safety, Software Testing & Quality Assurance ও Design and Analysis of Algorithms — সব এক জায়গায়।
আগের পাঠ
লংগেস্ট ইনক্রিজিং সাবসিকোয়েন্স