পাঠ ৩৫ · ৫৭-এর মধ্যে · মডিউল ৭
Home / Courses / Design and Analysis of Algorithms / ম্যাক্স ফ্লো

ম্যাক্স ফ্লো — ফোর্ড-ফুলকারসন ও ম্যাক্স-ফ্লো মিন-কাট

Maximum flow — Ford-Fulkerson and the max-flow min-cut theorem
১৩ মিনিট পড়া কঠিন · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

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

  • ফ্লো নেটওয়ার্ক, রেসিডুয়াল গ্রাফ ও অগমেন্টিং পাথের আনুষ্ঠানিক ধারণা
  • BFS-ভিত্তিক অগমেন্টিং পাথ খোঁজা (এডমন্ডস-কার্প) সম্পূর্ণ ইমপ্লিমেন্টেশন
  • ম্যাক্স-ফ্লো মিন-কাট থিওরেমের বিবৃতি ও এর পেছনের যুক্তি
  • ফাইনাল রেসিডুয়াল গ্রাফ থেকে সরাসরি মিন কাট গণনা করে থিওরেমটি কোডে যাচাই করার কৌশল

১ · ফ্লো নেটওয়ার্ক, রেসিডুয়াল গ্রাফ ও অগমেন্টিং পাথ

একটি ফ্লো নেটওয়ার্ক হলো একটি ডাইরেক্টেড গ্রাফ $G=(V,E)$ যেখানে প্রতিটি এজ $(u,v)$-এর একটি ক্যাপাসিটি $c(u,v) \ge 0$ আছে, একটি সোর্স $s$ ও একটি সিংক $t$ চিহ্নিত। একটি ফ্লো $f(u,v)$ হলো প্রতিটি এজে প্রকৃতপক্ষে পাঠানো প্রবাহ, যা দুটো শর্ত মানে: (ক) $0 \le f(u,v) \le c(u,v)$ (ক্যাপাসিটি লঙ্ঘন নয়) এবং (খ) $s,t$ ছাড়া প্রতিটি ভার্টেক্সে ইনফ্লো = আউটফ্লো (ফ্লো কনজার্ভেশন)। লক্ষ্য: মোট ফ্লো ভ্যালু $|f| = \sum_v f(s,v)$ সর্বোচ্চ করা।

মূল কৌশল হলো রেসিডুয়াল গ্রাফResidual Graphএকটি সহায়ক গ্রাফ যা দেখায় কোন এজে আরও কত ফ্লো পাঠানো সম্ভব (অবশিষ্ট ক্যাপাসিটি), এবং প্রতিটি ফরওয়ার্ড এজের জন্য একটি "আনডু" রিভার্স এজও রাখে। — প্রতিটি এজ $(u,v)$-এর জন্য একটি রেসিডুয়াল ক্যাপাসিটি $c_f(u,v) = c(u,v) - f(u,v)$ (আরও কতটা পাঠানো যায়) এবং একটি রিভার্স রেসিডুয়াল ক্যাপাসিটি $c_f(v,u) = f(u,v)$ (ইতিমধ্যে পাঠানো ফ্লো "ফেরত নেওয়ার" সুযোগ, যা অ্যালগরিদমকে ভুল প্রাথমিক সিদ্ধান্ত সংশোধন করার সুযোগ দেয়)। একটি অগমেন্টিং পাথ হলো রেসিডুয়াল গ্রাফে $s$ থেকে $t$-এ যাওয়ার এমন একটি পাথ যার প্রতিটি এজে এখনও পজিটিভ রেসিডুয়াল ক্যাপাসিটি আছে।

ফোর্ড-ফুলকারসন পদ্ধতি: যতক্ষণ না একটি অগমেন্টিং পাথ পাওয়া যায়, সেই পাথের সবচেয়ে ছোট রেসিডুয়াল ক্যাপাসিটি (বটলনেক) পরিমাণ ফ্লো পাঠাও এবং রেসিডুয়াল ক্যাপাসিটিগুলো আপডেট করো। যখন আর কোনো অগমেন্টিং পাথ পাওয়া যায় না, বর্তমান ফ্লো সর্বোচ্চ। এডমন্ডস-কার্প শুধু এই সাধারণ পদ্ধতির একটি নির্দিষ্ট বাস্তবায়ন — অগমেন্টিং পাথ খোঁজার জন্য সবসময় BFS ব্যবহার করে (তাই সবসময় সবচেয়ে কম এজবিশিষ্ট অগমেন্টিং পাথ বেছে নেয়), যা $O(VE^2)$ একটি টাইট আপার বাউন্ড গ্যারান্টি দেয় (নির্বিচারে DFS-ভিত্তিক পাথ বেছে নিলে তাত্ত্বিকভাবে অনেক বেশি ইটারেশন লাগতে পারে)।

s a b c t s-t কাট
একটি কাট $V$-কে দুই ভাগে ভাগ করে — একটি ভাগে $s$, অন্যটিতে $t$। কাটের ক্যাপাসিটি হলো $s$-এর ভাগ থেকে $t$-এর ভাগে যাওয়া এজগুলোর ক্যাপাসিটির যোগফল। প্রতিটি $s$-$t$ কাট ম্যাক্স ফ্লোর একটি আপার বাউন্ড দেয় — কোনো ফ্লো একটি কাট অতিক্রম না করে $s$ থেকে $t$-এ পৌঁছাতে পারে না।
ম্যাক্স-ফ্লো মিন-কাট থিওরেম

যেকোনো ফ্লো নেটওয়ার্কে, সর্বোচ্চ ফ্লোর মান = সবচেয়ে ছোট $s$-$t$ কাটের ক্যাপাসিটি। সংক্ষিপ্ত যুক্তি: যেকোনো ফ্লো যেকোনো কাটের ক্যাপাসিটির বেশি হতে পারে না (তাই max-flow $\le$ min-cut, "উইক ডুয়ালিটি"), এবং ফোর্ড-ফুলকারসন যখন থামে (আর কোনো অগমেন্টিং পাথ নেই), রেসিডুয়াল গ্রাফে $s$ থেকে রিচেবল ভার্টেক্সদের সেট $S$ (এবং বাকি $T=V\setminus S$) একটি কাট গঠন করে যার ক্যাপাসিটি ঠিক বর্তমান ফ্লোর সমান — কারণ $S$ থেকে $T$-এ যাওয়া প্রতিটি এজ তার পূর্ণ ক্যাপাসিটিতে স্যাচুরেটেড থাকতে বাধ্য (নাহলে রেসিডুয়াল ক্যাপাসিটি থাকত এবং $T$-এর সেই ভার্টেক্স $S$-এ থাকত), এবং $T$ থেকে $S$-এ যাওয়া প্রতিটি এজে ফ্লো অবশ্যই ০ (নাহলে রিভার্স রেসিডুয়াল এজ দিয়ে সেই ভার্টেক্স $S$-এ পৌঁছানো যেত)।

২ · ইমপ্লিমেন্টেশন ও থিওরেমের কম্পিউটেড যাচাই

নিচে আমরা এডমন্ডস-কার্প সম্পূর্ণভাবে ইমপ্লিমেন্ট করব, এবং অ্যালগরিদম থামার পর ফাইনাল রেসিডুয়াল গ্রাফ থেকে $s$-রিচেবল সেট বের করে সেটিকে একটি মিন কাট হিসেবে দাবি করব — তারপর মূল (রেসিডুয়াল নয়) ক্যাপাসিটি ডেটা থেকে সেই কাট অতিক্রমকারী এজগুলোর ক্যাপাসিটি সরাসরি যোগ করে দেখব এটি ঠিক গণনা করা ম্যাক্স ফ্লোর সমান কি না। এটি প্রমাণের উপর নির্ভর না করে থিওরেমটি সরাসরি সংখ্যা দিয়ে যাচাই করে।

Python
from collections import deque, defaultdict

def edmonds_karp(capacity, source, sink):
    # residual[u][v] = u থেকে v-তে বর্তমানে আরও কতটা ফ্লো পাঠানো সম্ভব
    residual = defaultdict(lambda: defaultdict(int))
    for (u, v), c in capacity.items():
        residual[u][v] += c
        residual[v][u] += 0  # রিভার্স রেসিডুয়াল এজ অস্তিত্বে আনা (শুরুতে ০)

    def bfs_find_path():
        parent = {source: None}
        queue = deque([source])
        while queue:
            u = queue.popleft()
            if u == sink:
                break
            for v, cap in residual[u].items():
                if cap > 0 and v not in parent:
                    parent[v] = u
                    queue.append(v)
        if sink not in parent:
            return None
        path = []
        node = sink
        while node is not None:
            path.append(node)
            node = parent[node]
        path.reverse()
        return path

    max_flow = 0
    while True:
        path = bfs_find_path()
        if path is None:
            break
        bottleneck = min(residual[path[i]][path[i + 1]] for i in range(len(path) - 1))
        for i in range(len(path) - 1):
            u, v = path[i], path[i + 1]
            residual[u][v] -= bottleneck
            residual[v][u] += bottleneck
        max_flow += bottleneck

    return max_flow, residual

def reachable_in_residual(residual, source):
    visited = {source}
    queue = deque([source])
    while queue:
        u = queue.popleft()
        for v, cap in residual[u].items():
            if cap > 0 and v not in visited:
                visited.add(v)
                queue.append(v)
    return visited

# ক্লাসিক টেক্সটবুক উদাহরণ: s=0, v1=1, v2=2, v3=3, v4=4, t=5
capacity = {
    (0, 1): 16, (0, 2): 13,
    (1, 3): 12,
    (2, 1): 4, (2, 4): 14,
    (3, 2): 9, (3, 5): 20,
    (4, 3): 7, (4, 5): 4,
}
source, sink = 0, 5

max_flow, final_residual = edmonds_karp(capacity, source, sink)
print(f"এডমন্ডস-কার্প দিয়ে গণনা করা ম্যাক্স ফ্লো: {max_flow}")

# ফাইনাল রেসিডুয়াল গ্রাফ থেকে মিন কাট বের করা
reachable = reachable_in_residual(final_residual, source)
print(f"রেসিডুয়াল গ্রাফে s থেকে রিচেবল সেট S: {sorted(reachable)}")

cut_edges = []
cut_capacity = 0
for (u, v), c in capacity.items():
    if u in reachable and v not in reachable:
        cut_edges.append((u, v, c))
        cut_capacity += c

print(f"মিন কাট অতিক্রমকারী এজসমূহ (S থেকে T): {cut_edges}")
print(f"মিন কাটের মোট ক্যাপাসিটি: {cut_capacity}")
print(f"ম্যাক্স ফ্লো == মিন কাট ক্যাপাসিটি? {max_flow == cut_capacity}")

assert max_flow == cut_capacity, "ম্যাক্স-ফ্লো মিন-কাট থিওরেম লঙ্ঘিত -- ইমপ্লিমেন্টেশনে বাগ আছে!"
print("\nম্যাক্স-ফ্লো মিন-কাট থিওরেম কম্পিউটেশনালি যাচাই সম্পন্ন।")

    
লক্ষ্য করুন cut_capacity গণনায় আমরা মূল capacity ডিকশনারি ব্যবহার করেছি, final_residual নয় — কারণ কাটের সংজ্ঞা মূল নেটওয়ার্কের এজ ক্যাপাসিটির উপর ভিত্তি করে, রেসিডুয়াল মানের উপর নয় (যা ইতিমধ্যে ব্যবহৃত ফ্লো বিয়োগ করে দেওয়া হয়েছে)। শুধু reachable সেটটি (কারা রিচেবল, কারা নয়) ফাইনাল রেসিডুয়াল গ্রাফ থেকে আসে — এটিই থিওরেমের মূল দাবি: অ্যালগরিদম যেখানে থামে সেই বিভাজনটিই একটি সর্বনিম্ন কাট।
মূল কথা · Key takeaway

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

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

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

প্র ০১ রিভার্স রেসিডুয়াল এজ (residual[v][u] += 0 দিয়ে শুরু করা) না থাকলে অ্যালগরিদম কী ভুল করতে পারত?

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

প্র ০২ এডমন্ডস-কার্প কেন সবসময় DFS-ভিত্তিক (নির্বিচারে যেকোনো অগমেন্টিং পাথ বেছে নেওয়া) ফোর্ড-ফুলকারসনের চেয়ে ভালো তাত্ত্বিক গ্যারান্টি দেয়?

BFS সবসময় সবচেয়ে কম এজবিশিষ্ট অগমেন্টিং পাথ বেছে নেয়, এবং প্রমাণ করা যায় যে প্রতিটি এজ সর্বোচ্চ $O(V)$ বার "বটলনেক" (সীমাবদ্ধকারী এজ) হতে পারে এই কৌশলে — যা মোট ইটারেশন সংখ্যাকে $O(VE)$-এ সীমাবদ্ধ করে, প্রতিটি ইটারেশন $O(E)$ সময় নেয় বলে মোট $O(VE^2)$। নির্বিচারে পাথ বেছে নিলে (যেমন প্লেইন DFS), বিশেষভাবে ডিজাইন করা গ্রাফে অ্যালগরিদম তাত্ত্বিকভাবে অনেক বেশি (এমনকি ক্যাপাসিটির মানের উপর নির্ভরশীল, ইনপুট সাইজের উপর নয়) ইটারেশন নিতে পারে।

প্র ০৩ মিন কাট কি সবসময় ইউনিক (একমাত্র)? উপরের উদাহরণে যদি একাধিক মিন কাট থাকত, আমাদের কোড কোনটি খুঁজে পেত?

না, একই গ্রাফে একাধিক ভিন্ন কাট একই (সর্বনিম্ন) ক্যাপাসিটি ধারণ করতে পারে। আমাদের কোড নির্দিষ্টভাবে একটি মিন কাট খুঁজে পায় — যেটি ফাইনাল রেসিডুয়াল গ্রাফে $s$ থেকে রিচেবল ভার্টেক্সদের সেট দিয়ে সংজ্ঞায়িত। এটি সবসময় একটি বৈধ মিন কাট (থিওরেম অনুযায়ী), কিন্তু যদি একাধিক মিন কাট থাকে, অন্য কোনো বৈধ মিন কাটও একই ক্যাপাসিটি দিত — থিওরেমটি "একটি নির্দিষ্ট মিন কাট" নয়, "সর্বনিম্ন কাটের মান" সম্পর্কে দাবি করে।

অনুশীলন

  1. চিন্তা করুন: উপরের গ্রাফে যদি এজ (4, 5): 4-এর ক্যাপাসিটি বাড়িয়ে 40 করা হয়, তাহলে ম্যাক্স ফ্লো কি বাড়বে, এবং কতটুকু বাড়তে পারে বলে আপনার ধারণা (হিন্ট: অন্য এজগুলোর ক্যাপাসিটিও একটি সীমা তৈরি করে)?

    ম্যাক্স ফ্লো বাড়তে পারে, কিন্তু সীমাহীন নয় — $t$-এ ঢোকার মোট ক্যাপাসিটি এখনও $(3,5){:}20$ ও $(4,5){:}40$ এজের যোগফল দ্বারা সীমাবদ্ধ, এবং $s$ থেকে বের হওয়ার মোট ক্যাপাসিটিও $(0,1){:}16 + (0,2){:}13 = 29$ দ্বারা সীমাবদ্ধ। তাই ম্যাক্স ফ্লো কখনো ২৯-এর বেশি হতে পারবে না, তা $(4,5)$-এর ক্যাপাসিটি যতই বাড়ানো হোক না কেন — একটি নির্দিষ্ট মিন কাট (হয়তো $s$-এর আশেপাশের এজগুলো নিয়ে গঠিত) এখন "বটলনেক" হয়ে যাবে।

  2. পরীক্ষা করুন: কোড সেলে capacity[(4, 5)] = 40 সেট করে Run চেপে নতুন ম্যাক্স ফ্লো এবং নতুন মিন কাট এজগুলো দেখুন — নিশ্চিত করুন assert এখনও পাস করছে।

    নতুন ম্যাক্স ফ্লো বেড়ে যাবে (মূল ২৩ থেকে বেশি, $s$-এর আউটগোয়িং ক্যাপাসিটি $29$-এর কাছাকাছি বা সমান পর্যন্ত পৌঁছাতে পারে, নির্ভর করে ভেতরের গ্রাফের গঠনের উপর), এবং মিন কাট এজগুলো বদলে যাবে (হয়তো এখন $(0,1)$ ও $(0,2)$ কাটটি নির্ধারণ করবে) — কিন্তু max_flow == cut_capacity সবসময় সত্য থাকবে।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ ব্যাকট্র্যাকিং, ব্রাঞ্চ-অ্যান্ড-বাউন্ড, স্ট্রিং অ্যালগরিদম, অ্যামর্টাইজড অ্যানালাইসিস, NP-কমপ্লিটনেস ও অ্যাপ্রক্সিমেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • 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 — সব এক জায়গায়।
পূর্ববর্তী পাঠ
ফ্লয়েড-ওয়ারশাল অল-পেয়ার্স শর্টেস্ট পাথ