এই পর্বে যা শিখবেন
- Closure-এর নির্ভুল সংজ্ঞা, কোন ধর্মের closure থাকে আর কেন থাকে, তার সাধারণ প্রমাণ
- Path, Rn আর connectivity relation R*, আর transitive closure = R*-এর প্রমাণ
- n উপাদানের সেটে n ধাপই যথেষ্ট কেন (pigeonhole), আর Boolean power পদ্ধতির খরচ
- Warshall-এর algorithm হাতে চালানো, তার invariant আর সম্পূর্ণ সঠিকতার প্রমাণ
- Closure-গুলোর ক্রম, equivalence closure, আর delivery route ও dependency-তে প্রয়োগ, সব Python-এ যাচাই করে
১ · Closure কী — যত কম যোগ করে ধর্মটি আনা
পর্ব ৫২ “Relation আর তার ধর্ম — reflexive, symmetric, transitive”-এ একটি relation-কে দেখা হয়েছে A × A-এর একটি subset হিসেবে (পর্ব ১৪-এর Cartesian product), আর তার তিনটি প্রধান ধর্ম পরীক্ষা করা হয়েছে। এই পর্বের প্রশ্ন উল্টো দিক থেকে: একটি relation-এ কোনো ধর্ম নেই; তাকে সবচেয়ে কম বদলে ধর্মটি কীভাবে আনা যায়?
বাজারঘরের ডেলিভারি বিভাগে একটি তালিকা আছে: কোন গুদাম থেকে কোন গুদামে সরাসরি ট্রাক যায়। ঢাকা থেকে গাজীপুরে ট্রাক যায়, গাজীপুর থেকে ময়মনসিংহে যায়, কিন্তু ঢাকা থেকে ময়মনসিংহে সরাসরি কোনো ট্রাক নেই। গ্রাহক সেবার কর্মী জানতে চান: “ঢাকার গুদাম থেকে ময়মনসিংহে মাল পাঠানো সম্ভব কি?” উত্তর হ্যাঁ, গাজীপুর হয়ে। এই “পাঠানো সম্ভব” তালিকাটি সরাসরি ট্রাকের তালিকার চেয়ে বড়, কিন্তু অকারণে বড় নয়: শুধু সেই জোড়াগুলো যোগ হয়, যা কোনো না কোনো পথে সত্যিই পৌঁছায়। এটিই transitive closure.
পর্ব ৫২-এর তিনটি ধর্ম এক লাইনে মনে করি। A-এর ওপর R হলো reflexive, যদি প্রতিটি a ∈ A-এর জন্য (a, a) ∈ R; symmetric, যদি (a, b) ∈ R হলে (b, a) ∈ R; আর transitive, যদি (a, b) ∈ R আর (b, c) ∈ R হলে (a, c) ∈ R. চতুর্থ একটি ধর্ম, antisymmetric: (a, b) আর (b, a) দুটিই R-এ থাকলে a = b.
P একটি ধর্ম, আর R সেট A-এর ওপর একটি relation. R-এর P-closure হলো এমন একটি relation S, যার
- (ক) ধর্ম P আছে,
- (খ) R ⊆ S, অর্থাৎ R-এর কোনো জোড়া বাদ যায়নি,
- (গ) P ধর্মওয়ালা যেকোনো T ⊇ R-এর জন্য S ⊆ T, অর্থাৎ S হলো এমন সবগুলোর মধ্যে সবচেয়ে ছোট।
Reflexive, symmetric আর transitive closure-কে লেখা হয় r(R), s(R) আর t(R).
শর্ত (গ) খুব কড়া: “সবচেয়ে ছোট” মানে শুধু “এর চেয়ে ছোট আর কিছু নেই” নয়, বরং “বাকি সবার ভেতরে ঢুকে যায়”। তাই closure থাকলে তা একটিই (দুটি থাকলে প্রত্যেকে অন্যটির subset, তাই সমান)। কিন্তু থাকবেই, এমন নিশ্চয়তা নেই। দুটি ব্যর্থতার উদাহরণ দেখি।
- কোনো উপযুক্ত বাড়তি নেই: A = {1, 2} আর R = {(1, 2), (2, 1)}. যেকোনো T ⊇ R-এ (1, 2) আর (2, 1) দুটিই থাকে, অথচ 1 ≠ 2; তাই কোনো superset antisymmetric নয়। জোড়া যোগ করে antisymmetric হওয়া যায় না, কারণ সমস্যাটি একটি জোড়া থাকার জন্য, না থাকার জন্য নয়। তাই সাধারণভাবে “antisymmetric closure” বলে কিছু নেই।
- বাড়তি আছে, কিন্তু সবচেয়ে ছোটটি নেই: ধর্ম P = “অন্তত একটি (a, a) জোড়া আছে”, A = {1, 2}, R = ∅. {(1, 1)} আর {(2, 2)} দুটিই P মানে আর কোনোটি থেকে কিছু বাদ দেওয়া যায় না, কিন্তু কোনোটি অন্যটির subset নয়। তাদের intersection ∅, যাতে P নেই। এখানে closure নেই।
তাহলে কোন ধর্মে closure নিশ্চিত? উত্তর একটি সাধারণ উপপাদ্য, যার প্রমাণ শুধু সেটের intersection (পর্ব ১৫) ব্যবহার করে।
ধরুন ধর্ম P এমন যে (i) পূর্ণ relation A × A-এর P আছে, আর (ii) P ধর্মওয়ালা যেকোনো অশূন্য পরিবারের relation-গুলোর intersection-এরও P আছে। তাহলে A-এর ওপর প্রতিটি R-এর P-closure আছে, আর তা হলো
c(R) = ⋂ { T ⊆ A × A : R ⊆ T আর T-এর P আছে }
- পরিবার 𝓕 = {T : R ⊆ T, T-এর P আছে} অশূন্য, কারণ A × A ⊇ R আর (i) অনুযায়ী তার P আছে। — শর্ত (i)
- c(R) = ⋂𝓕-এর P আছে। — শর্ত (ii)
- 𝓕-এর প্রতিটি T-তে R আছে, তাই তাদের intersection-এও: R ⊆ c(R). — intersection-এর সংজ্ঞা
- P ধর্মওয়ালা যেকোনো T ⊇ R নিজেই 𝓕-এর সদস্য, তাই ⋂𝓕 ⊆ T. তিনটি শর্তই মিলল। ∎ — intersection প্রতিটি সদস্যের subset
এবার তিনটি ধর্ম মিলিয়ে দেখি। A × A reflexive, symmetric আর transitive তিনটিই, তাই (i) মেলে। (ii)-এর জন্য transitive-এর প্রমাণটি দেখাই, বাকি দুটি একই ধাঁচের: ধরুন প্রতিটি Ti transitive, আর (a, b), (b, c) ∈ ⋂Ti. তাহলে প্রতিটি i-তে (a, b), (b, c) ∈ Ti, তাই (a, c) ∈ Ti; অর্থাৎ (a, c) ∈ ⋂Ti. Antisymmetric ধর্ম (ii) মানে, কিন্তু (i) মানে না (|A| ≥ 2 হলে A × A antisymmetric নয়), আর ঠিক সেই ফাঁকেই ওপরের প্রথম উদাহরণ পড়েছে। দ্বিতীয় উদাহরণের ধর্মে (i) আছে, কিন্তু (ii) ভেঙেছে।
উপপাদ্যটি অস্তিত্ব দেয়, কিন্তু হিসাবের উপায় দেয় না: সব superset-এর তালিকা করে intersection নেওয়া অবাস্তব। পরের তিনটি অংশে প্রতিটি closure-এর একটি সরাসরি সূত্র বের করব, আর §৩-এর শেষে Python দিয়ে মিলিয়ে দেখব যে সূত্র আর এই সংজ্ঞা সত্যিই একই জিনিস দেয়। দুটি ছোট ফলও সংজ্ঞা থেকে সরাসরি আসে: R-এ আগে থেকেই P থাকলে c(R) = R (R নিজেই সবচেয়ে ছোট), আর তাই c(c(R)) = c(R); দ্বিতীয়বার closure নিলে কিছু বদলায় না।
২ · Reflexive আর symmetric closure — এক লাইনের সূত্র
সেট A-এর diagonal relation Δ = {(a, a) : a ∈ A}, অর্থাৎ “প্রতিটি উপাদান নিজের সঙ্গে”। আর R-এর inverse R−1 = {(b, a) : (a, b) ∈ R}, অর্থাৎ প্রতিটি তীর উল্টো দিকে (পর্ব ১৬-এর inverse function-এর relation রূপ, যা সবসময় থাকে)।
Reflexive closure r(R) = R ∪ Δ, আর symmetric closure s(R) = R ∪ R−1.
প্রমাণ (reflexive):
- R ∪ Δ-তে প্রতিটি (a, a) আছে, তাই এটি reflexive; আর R ⊆ R ∪ Δ. — union-এর সংজ্ঞা
- যেকোনো reflexive T ⊇ R-এ সব (a, a) আছে, অর্থাৎ Δ ⊆ T, আর R ⊆ T; তাই R ∪ Δ ⊆ T. ∎ — দুটি subset-এর union-ও subset
প্রমাণ (symmetric):
- (x, y) ∈ R ∪ R−1 হলে হয় (x, y) ∈ R, তখন (y, x) ∈ R−1; নয়তো (x, y) ∈ R−1, অর্থাৎ (y, x) ∈ R. দুই ক্ষেত্রেই (y, x) ∈ R ∪ R−1, তাই এটি symmetric. — inverse-এর সংজ্ঞা
- যেকোনো symmetric T ⊇ R নিন। (a, b) ∈ R হলে (a, b) ∈ T, আর symmetric বলে (b, a) ∈ T; তাই R−1 ⊆ T, আর R ∪ R−1 ⊆ T. ∎ — T-এর symmetry
0-1 matrix-এর ভাষায় (পর্ব ১৯) দুটিই এক নজরে: Mr(R) = MR ∨ I (কর্ণে সব 1 বসানো), আর Ms(R) = MR ∨ MRT (matrix আর তার transpose-এর ঘরে ঘরে OR). Digraph ছবিতে reflexive closure প্রতিটি বিন্দুতে একটি loop যোগ করে, আর symmetric closure প্রতিটি একমুখী তীরের পাশে উল্টো তীর।
উদাহরণ। A = {1, 2, 3, 4}, R = {(1, 2), (2, 2), (3, 4), (4, 1)}. r(R) = R ∪ {(1, 1), (3, 3), (4, 4)}: (2, 2) আগেই ছিল, তাই নতুন তিনটি, মোট ৭টি জোড়া। s(R) = R ∪ {(2, 1), (4, 3), (1, 4)}: (2, 2)-এর উল্টো সে নিজেই, তাই নতুন তিনটি, মোট ৭টি। দুটিই scratch-এ Python দিয়ে মেলানো। সাধারণভাবে |r(R)| = |R| + (কর্ণের ফাঁকা ঘর), আর |s(R)| = |R| + (যেসব জোড়ার উল্টোটি নেই, তাদের সংখ্যা)।
এই দুটি closure সহজ, কারণ নতুন জোড়া যোগ করলে নতুন কোনো দাবি তৈরি হয় না: (a, a) যোগ করলে আর কিছু যোগ করতে হয় না, (b, a) যোগ করলে তার উল্টো (a, b) তো আগেই আছে। Transitive closure-এ তা সত্য নয়। (1, 3) যোগ করলে, আর (3, 4) থাকলে, এখন (1, 4)-ও চাই; একটি যোগ আরেকটি যোগ ডেকে আনে। তাই transitive closure-এর জন্য একটি নতুন ধারণা লাগে: পথ।
৩ · Path আর connectivity relation — transitive closure = R*
A-এর ওপর R-কে একটি directed graph (digraph) হিসেবে আঁকা যায়: A-এর প্রতিটি উপাদান একটি বিন্দু, আর (a, b) ∈ R হলে a থেকে b-তে একটি তীর। বাজারঘরের চারটি গুদাম নিন: 1 = ঢাকা, 2 = গাজীপুর, 3 = ময়মনসিংহ, 4 = নেত্রকোনা। সরাসরি ট্রাকের relation
R = {(1, 2), (2, 1), (2, 3), (3, 4), (4, 3)}
অর্থাৎ ঢাকা ⇄ গাজীপুর, গাজীপুর → ময়মনসিংহ, আর ময়মনসিংহ ⇄ নেত্রকোনা। ময়মনসিংহ থেকে গাজীপুরে ফেরার কোনো ট্রাক নেই। এই পর্বের বেশিরভাগ হিসাব এই relation-এ।
বাজারঘরের ৪টি গুদাম: সরাসরি ট্রাক (R) আর তার transitive closure
নীল টানা তীর = R-এর ৫টি জোড়া। কমলা ড্যাশ = transitive closure-এ নতুন যোগ হওয়া ৭টি জোড়া: তিনটি তীর আর চারটি loop (নিজের কাছে ফেরার পথ)। মোট ১২টি। (ময়মন. = ময়মনসিংহ, নেত্র. = নেত্রকোনা)
R-এ a থেকে b-তে দৈর্ঘ্য n ≥ 1-এর একটি path (পথ) হলো উপাদানের একটি ক্রম x0 = a, x1, …, xn = b, যেখানে প্রতিটি (xi−1, xi) ∈ R. উপাদান পুনরাবৃত্ত হতে পারে (পর্ব ১৯-এ একে walk বলা হয়েছে); a = b হলে পথটিকে cycle বলে। R-এর connectivity relation
R* = R ∪ R2 ∪ R3 ∪ ⋯ = ⋃n≥1 Rn
যেখানে R1 = R আর Rn+1 = Rn ∘ R = {(a, c) : কোনো b-এর জন্য (a, b) ∈ R আর (b, c) ∈ Rn} (পর্ব ৫২-এর composition).
চিহ্নের সতর্কতা: এই পর্বে (আর অনেক discrete math পাঠ্যবইয়ে) R* মানে ⋃n≥1 Rn, যা transitive closure. কিন্তু automata, regular expression আর programming language-এর বইয়ে প্রায়ই R+ লেখা হয় transitive closure-এর জন্য, আর R* মানে reflexive-transitive closure (n = 0 সহ, অর্থাৎ Δ যোগ করে)। কোনো বই বা AI-এর উত্তর পড়ার সময় আগে দেখে নিন R* কোন অর্থে।
প্রতিটি n ≥ 1-এর জন্য (a, b) ∈ Rn ⇔ R-এ a থেকে b-তে দৈর্ঘ্য n-এর একটি path আছে।
- Basis n = 1: দৈর্ঘ্য ১-এর path মানে একটি তীর (a, b) ∈ R = R1. — সংজ্ঞা
- Inductive step: n-এর জন্য সত্য ধরি। (a, c) ∈ Rn+1 ⇔ কোনো b-তে (a, b) ∈ R আর (b, c) ∈ Rn ⇔ একটি তীর a → b, তারপর b থেকে c-তে দৈর্ঘ্য n-এর path (hypothesis) ⇔ a থেকে c-তে দৈর্ঘ্য n + 1-এর path (প্রথম তীরটি আলাদা করে)। ∎ — পর্ব ১০-এর induction
এটি পর্ব ১৯-এর উপপাদ্য ৪.২-এর relation রূপ, যেখানে একই কথা Boolean power MR[n] দিয়ে বলা হয়েছিল। তাই R* মানে “কোনো দৈর্ঘ্যের path দিয়ে পৌঁছানো যায়”, অর্থাৎ reachability.
A-এর ওপর যেকোনো R-এর জন্য transitive closure t(R) = R*.
- R = R1 ⊆ R*. — union-এর সংজ্ঞা
- R* transitive: (a, b) ∈ Rm আর (b, c) ∈ Rk হলে lemma ৩.২ অনুযায়ী a থেকে b-তে দৈর্ঘ্য m-এর আর b থেকে c-তে দৈর্ঘ্য k-এর path আছে; জোড়া দিলে a থেকে c-তে দৈর্ঘ্য m + k-এর path, তাই (a, c) ∈ Rm+k ⊆ R*. — path জোড়া দেওয়া
- যেকোনো transitive S ⊇ R নিন। দাবি: প্রতিটি n ≥ 1-এ Rn ⊆ S. n = 1-এ এটি R ⊆ S. n-এ সত্য ধরলে, (a, c) ∈ Rn+1 মানে কোনো b-তে (a, b) ∈ R ⊆ S আর (b, c) ∈ Rn ⊆ S, আর S transitive বলে (a, c) ∈ S. — n-এর ওপর induction
- তাই R* = ⋃Rn ⊆ S. ধাপ ১–৩ মিলিয়ে R* হলো R ধারণকারী সবচেয়ে ছোট transitive relation, অর্থাৎ t(R) = R*. ∎ — সংজ্ঞা ১.১
ধাপ ৩-এর একটি বিশেষ রূপ পর্ব ৫২-এর উপপাদ্য: R নিজে transitive হলে (S = R নিয়ে) সব Rn ⊆ R, তাই R* = R. ছবিতে দেখুন: ঢাকা থেকে ঢাকা (loop) কেন closure-এ এল? ঢাকা → গাজীপুর → ঢাকা, দৈর্ঘ্য ২-এর একটি cycle. আর ময়মনসিংহ থেকে গাজীপুর কেন এল না? কারণ ময়মনসিংহ থেকে যে দুটি গুদামে যাওয়া যায় (নেত্রকোনা আর নিজে), তাদের কোনোটি থেকেই ওপরের সারিতে তীর নেই।
উপপাদ্য ১.২ আর ৩.৩ দুটি সম্পূর্ণ ভিন্ন পথে একই t(R)-কে বর্ণনা করে: একটি “সব transitive superset-এর intersection”, অন্যটি “সব দৈর্ঘ্যের path-এর union”. ছোট সেটে দুটিকে সরাসরি মেলানো যায়। নিচের ব্লক A = {1, 2, 3}-এর ওপর সব 29 = 512টি relation নিয়ে প্রতিটির জন্য দুটি পথে হিসাব করে, আর antisymmetric ধর্মের ব্যর্থতাও গোনে। প্রথমবার চালালে ব্রাউজারে Python (Pyodide) নামতে কয়েক সেকেন্ড লাগে; পরের ব্লকগুলো দ্রুত চলে।
from itertools import product, combinations
A = (1, 2, 3)
pairs = list(product(A, A)) # 9 pairs, 2^9 relations
rels = [frozenset(c) for k in range(10) for c in combinations(pairs, k)]
def compose(R, S): return {(a, d) for a, b in R for c, d in S if b == c}
def transitive(R): return compose(R, R) <= R
def antisym(R): return all(a == b or (b, a) not in R for a, b in R)
TRANS = [T for T in rels if transitive(T)]
print("relations:", len(rels), "| transitive ones:", len(TRANS))
ok = 0
for R in rels:
smallest = frozenset(pairs) # A x A is transitive
for T in TRANS:
if R <= T:
smallest &= T # intersect all supersets
R2 = compose(R, R); R3 = compose(R2, R)
ok += smallest == R | R2 | R3 and transitive(smallest)
print("intersection == R U R^2 U R^3 (and transitive):", ok, "of", len(rels))
ANTI = [S for S in rels if antisym(S)]
none = sum(1 for R in rels if not any(R <= S for S in ANTI))
print("antisymmetric relations:", len(ANTI),
"| relations with NO antisymmetric superset:", none)
R = frozenset({(1, 2), (2, 1)})
print("example", sorted(R), "antisymmetric supersets:",
sum(R <= S for S in ANTI))আসল আউটপুট (ব্রাউজারের Python):
relations: 512 | transitive ones: 171
intersection == R U R^2 U R^3 (and transitive): 512 of 512
antisymmetric relations: 216 | relations with NO antisymmetric superset: 296
example [(1, 2), (2, 1)] antisymmetric supersets: 0
৯টি জোড়ার প্রতিটি থাকতে বা না থাকতে পারে, তাই ৫১২টি relation; তাদের ১৭১টি transitive (transitive relation গোনার কোনো সরল সূত্র নেই, তাই এই সংখ্যা brute force-এ আসে)। দ্বিতীয় লাইন এই অংশের মূল যাচাই: প্রতিটি R-এর জন্য “সব transitive superset-এর intersection” (উপপাদ্য ১.২) আর R ∪ R2 ∪ R3 (উপপাদ্য ৩.৩ আর §৪-এর উপপাদ্য) হুবহু এক, আর সেটি নিজে transitive; ৫১২টির ৫১২টিতেই। Intersection শুরু হয়েছে A × A থেকে, কারণ শর্ত (i) অনুযায়ী সেটি পরিবারে সবসময় আছে। তৃতীয় লাইন: antisymmetric relation ২১৬টি (= 23 · 33, পর্ব ৫২-এর সূত্র), আর বাকি ৫১২ − ২১৬ = ২৯৬টির কোনো antisymmetric superset-ই নেই; অর্থাৎ যে R নিজে antisymmetric নয়, তার “antisymmetric closure” কখনোই থাকে না। শেষ লাইনে {(1, 2), (2, 1)}-এর ক্ষেত্রে এমন superset শূন্যটি।
৪ · সসীম সেটে n ধাপই যথেষ্ট — Boolean power পদ্ধতি
R* একটি অসীম union, কিন্তু সসীম সেটে অসীম পর্যন্ত যেতে হয় না। কারণটি pigeonhole principle (পর্ব ৩২): দীর্ঘ পথে কোনো গুদাম দুবার আসতে বাধ্য, আর মাঝের ঘোরাটুকু কেটে দিলে পথ ছোট হয়।
|A| = n হলে R* = R ∪ R2 ∪ ⋯ ∪ Rn. আরও সূক্ষ্মভাবে: a ≠ b আর (a, b) ∈ R* হলে দৈর্ঘ্য বড়জোর n − 1-এর একটি path আছে; (a, a) ∈ R* হলে দৈর্ঘ্য বড়জোর n-এর একটি cycle আছে।
- (a, b) ∈ R* হলে path আছে; তাদের মধ্যে সবচেয়ে ছোটটি নিন, দৈর্ঘ্য m, ক্রম x0, …, xm. — well-ordering, পর্ব ১১
- a ≠ b ধরুন আর ধরুন m ≥ n. তাহলে x0, …, xm-এ m + 1 ≥ n + 1টি অবস্থান, কিন্তু উপাদান মাত্র n-টি; তাই কোনো p < q-তে xp = xq. — pigeonhole
- xp+1, …, xq অংশটি কেটে দিলে x0, …, xp, xq+1, …, xm একটি path, দৈর্ঘ্য m − (q − p) < m; শুরু a আর শেষ b ভিন্ন বলে দৈর্ঘ্য ≥ 1. এটি “সবচেয়ে ছোট”-এর বিরোধী, তাই m ≤ n − 1. — proof by contradiction
- a = b হলে শুধু x0, …, xm−1 (m-টি অবস্থান) দেখুন। m > n হলে এদের মধ্যে xp = xq, 0 ≤ p < q ≤ m − 1; একই কাটায় দৈর্ঘ্য m − (q − p) ≥ 1 থাকে (কারণ q − p ≤ m − 1), আবার বিরোধ। তাই m ≤ n. — একই যুক্তি
- দুই ক্ষেত্রেই m ≤ n, আর lemma ৩.২ অনুযায়ী (a, b) ∈ Rm ⊆ R ∪ ⋯ ∪ Rn. উল্টো অন্তর্ভুক্তি সংজ্ঞা থেকেই। ∎
সীমা n আর কমানো যায় না: n উপাদানের একটি চক্র 1 → 2 → ⋯ → n → 1-এ (1, 1) প্রথম আসে ঠিক Rn-এ। নিচের stepper n = 3-এ ঠিক এটিই দেখায়, 0-1 matrix আর Boolean product দিয়ে: MRk+1 = MRk ⊙ MR, আর union মানে ঘরে ঘরে OR.
1R: 1→2, 2→3, 3→12R² = R ∘ R, সারি ধরে3R³ = R² ∘ R4R ∨ R² ∨ R³5R⁴ = R: নতুন কিছু নেই6সীমা n কেন দরকার
| matrix | |
| সারি 1 | |
| সারি 2 | |
| সারি 3 | |
| জোড়া |
- শুরু। তিন গুদামে একমুখী চক্র: 1 → 2 → 3 → 1. MR-এর প্রতিটি সারিতে একটি 1. কোনো কর্ণের ঘর 1 নয়, আর R transitive নয়: (1, 2), (2, 3) আছে কিন্তু (1, 3) নেই।
- R2. (a, c) ∈ R2 ⇔ কোনো b-তে a → b → c. 1 → 2 → 3, তাই (1, 3); 2 → 3 → 1, তাই (2, 1); 3 → 1 → 2, তাই (3, 2). Matrix-এ: সারি a-এর 1-গুলো যে সারিগুলো দেখায়, সেই সারিগুলোর OR.
- R3. তিন ধাপে প্রত্যেকে চক্র ঘুরে নিজের কাছে ফেরে: R3 = Δ, অর্থাৎ identity matrix. এই প্রথম কর্ণে 1 এল।
- Union. R ∨ R2 ∨ R3: তিনটি matrix-এর ১-গুলো কোনো ঘরে মেলে না, তাই মিলে পুরো 3 × 3, ৯টি জোড়া। প্রতিটি গুদাম থেকে প্রতিটিতে (নিজেসহ) যাওয়া যায়।
- আর দরকার নেই। R4 = R3 ∘ R = Δ ∘ R = R, R5 = R2, …: power-গুলো একই তিনটি matrix-এ ঘুরতে থাকে। উপপাদ্য ৪.১ আগেই বলেছিল n = 3-এ থামা যথেষ্ট।
- সীমা টাইট। R ∪ R2-এ থামলে কর্ণের তিনটি জোড়াই বাদ যেত। a ≠ b জোড়াগুলো অবশ্য R ∪ R2-এই এসে গেছে, ঠিক উপপাদ্যের “n − 1” অংশের মতো। (হিসাবটি scratch-এ Python দিয়ে মেলানো।)
খরচ। উপপাদ্য ৪.১ থেকে সরাসরি একটি algorithm: MR* = MR ∨ MR[2] ∨ ⋯ ∨ MR[n]. প্রতিটি Boolean product-এ n2টি ঘর, প্রতিটি ঘরে n-টি AND আর n − 1টি OR, অর্থাৎ n2(2n − 1)টি bit-অপারেশন। এমন product n − 1টি, আর join n − 1টি (প্রতিটি n2টি OR). মোট
(n − 1) · n2(2n − 1) + (n − 1) · n2 = 2n3(n − 1)
যা O(n4). n = 4-এ 384, n = 30-এ ১৫ লক্ষের বেশি। পর্ব ১৯-এর ভাবনার প্রশ্নে বারবার বর্গ করে n3 log n-এ নামানোর কৌশল আছে; কিন্তু আরও সরল আর দ্রুত একটি উপায় আছে, যা ধাপ গোনে না, মাঝের স্টপ গোনে।
৫ · Warshall-এর algorithm — মাঝের স্টপ একটি একটি করে খোলা
একটি path x0, x1, …, xm-এর interior vertex (মাঝের বিন্দু) হলো x1, …, xm−1: শুরু আর শেষ বাদে যেসব গুদাম হয়ে মাল যায়। দৈর্ঘ্য ১-এর path-এ (একটি সরাসরি তীর) কোনো মাঝের বিন্দু নেই।
বাজারঘরের ম্যানেজার একটি নতুন নিয়ম ভাবছেন: প্রথমে কোনো গুদামকে “ট্রানজিট হাব” হিসেবে ব্যবহার করা যাবে না, শুধু সরাসরি ট্রাক। তারপর ঢাকাকে হাব হিসেবে খুলে দেওয়া হলো: এখন কোন নতুন জোড়ায় মাল পাঠানো যায়? তারপর গাজীপুরকেও। এভাবে একটি একটি করে সব গুদাম হাব হলে শেষে যা পাওয়া যায়, তা-ই পূর্ণ reachability. প্রতিটি নতুন হাব খোলার সময় একটিই প্রশ্ন: “i থেকে নতুন হাব k-তে যাওয়া যায়, আর k থেকে j-তে যাওয়া যায়?” দুটিই হ্যাঁ হলে i থেকে j-তেও।
A = {1, …, n}, W0 = MR. k = 1, 2, …, n-এর জন্য, প্রতিটি i, j-তে
Wk[i][j] = Wk−1[i][j] ∨ (Wk−1[i][k] ∧ Wk−1[k][j])
ফল: Wn = MR*. সারির ভাষায়: সারি i-এর কলাম k-তে 1 থাকলে সারি i-এর সঙ্গে সারি k-কে OR করুন।
সূত্রটির অর্থ §৬-এ প্রমাণ হবে: Wk[i][j] = 1 ঠিক তখন, যখন i থেকে j-তে এমন একটি path আছে, যার সব মাঝের বিন্দু {1, …, k}-এর মধ্যে। আগে আমাদের গুদামে হাতে চালাই। কলাম-ক্রম ঢাকা, গাজীপুর, ময়মনসিংহ, নেত্রকোনা।
1W₀ = M_R (সরাসরি ট্রাক)2k = 1: ঢাকা হাব3k = 2: গাজীপুর হাব4k = 3: ময়মনসিংহ হাব5k = 4: নেত্রকোনা হাব6W₄ = M_R*
| ঢাকা | |
| গাজীপুর | |
| ময়মনসিংহ | |
| নেত্রকোনা | |
| মোট 1 |
- W0. কোনো হাব নেই, শুধু সরাসরি ট্রাক: MR, ৫টি 1. W0[i][j] = 1 মানে মাঝের বিন্দু ছাড়া path, অর্থাৎ একটি তীর।
- k = 1 (ঢাকা)। কলাম ১-এ 1 আছে শুধু গাজীপুরের সারিতে, তাই শুধু সেই সারি বদলাতে পারে: গাজীপুর OR ঢাকার সারি = 1010 ∨ 0100 = 1110. নতুন (গাজীপুর, গাজীপুর): গাজীপুর → ঢাকা → গাজীপুর।
- k = 2 (গাজীপুর)। কলাম ২-এ 1 আছে ঢাকা আর গাজীপুরের সারিতে। ঢাকা: 0100 ∨ 1110 = 1110, নতুন (ঢাকা, ঢাকা) আর (ঢাকা, ময়মনসিংহ)। গাজীপুরের নিজের সারি বদলায় না।
- k = 3 (ময়মনসিংহ)। কলাম ৩-এ 1 আছে ঢাকা, গাজীপুর আর নেত্রকোনার সারিতে; ময়মনসিংহের সারি 0001. তিনটি সারিতেই নেত্রকোনার ঘর 1 হয়: (ঢাকা, নেত্রকোনা), (গাজীপুর, নেত্রকোনা), আর (নেত্রকোনা, নেত্রকোনা), নেত্রকোনা → ময়মনসিংহ → নেত্রকোনা।
- k = 4 (নেত্রকোনা)। কলাম ৪-এ এখন চারটি সারিতেই 1; নেত্রকোনার সারি 0011. ঢাকা আর গাজীপুরে নতুন কিছু নেই, নেত্রকোনা নিজে বদলায় না, শুধু ময়মনসিংহ: 0001 ∨ 0011 = 0011, নতুন (ময়মনসিংহ, ময়মনসিংহ)।
- ফল। W4-এ ১২টি 1, ছবির ৫টি নীল আর ৭টি কমলা জোড়া। নিচের দুই সারির প্রথম দুই কলাম 0: ময়মনসিংহ বা নেত্রকোনা থেকে ঢাকা বা গাজীপুরে কোনো পথ নেই।
এবার একই হিসাব Python-এ, প্রতিটি Wk আর তাতে নতুন জোড়াসহ। শেষে ফলটিকে R ∪ R2 ∪ R3 ∪ R4-এর সঙ্গে মেলানো হয়েছে (DHK = ঢাকা, GZP = গাজীপুর, MYM = ময়মনসিংহ, NET = নেত্রকোনা)।
names = ["DHK", "GZP", "MYM", "NET"] # vertices 1, 2, 3, 4
R = {(1, 2), (2, 1), (2, 3), (3, 4), (4, 3)}
n = 4
W = [[1 if (i, j) in R else 0 for j in range(1, n + 1)]
for i in range(1, n + 1)]
def show(W, k, new):
print(f"W{k}:", " ".join("".join(map(str, row)) for row in W),
" new:", ", ".join(new) or "-")
show(W, 0, [])
for k in range(n): # allow vertex k+1 as interior
new = []
for i in range(n):
for j in range(n):
if not W[i][j] and W[i][k] and W[k][j]:
W[i][j] = 1 # in place is safe (see the proof)
new.append(f"{names[i]}->{names[j]}")
show(W, k + 1, new)
closure = {(i + 1, j + 1) for i in range(n) for j in range(n) if W[i][j]}
R2 = {(a, d) for (a, b) in R for (c, d) in R if b == c}
R3 = {(a, d) for (a, b) in R2 for (c, d) in R if b == c}
R4 = {(a, d) for (a, b) in R3 for (c, d) in R if b == c}
print("pairs:", len(R), "->", len(closure))
print("equals R U R2 U R3 U R4:", closure == R | R2 | R3 | R4)আসল আউটপুট (ব্রাউজারের Python):
W0: 0100 1010 0001 0010 new: -
W1: 0100 1110 0001 0010 new: GZP->GZP
W2: 1110 1110 0001 0010 new: DHK->DHK, DHK->MYM
W3: 1111 1111 0001 0011 new: DHK->NET, GZP->NET, NET->NET
W4: 1111 1111 0011 0011 new: MYM->MYM
pairs: 5 -> 12
equals R U R2 U R3 U R4: True
প্রতিটি লাইনে চারটি সারি (ঢাকা, গাজীপুর, ময়মনসিংহ, নেত্রকোনা), stepper-এর matrix-এর সঙ্গে হুবহু মেলে, আর “new” অংশে সেই ধাপে নতুন জোড়া। জোড়ার সংখ্যা ৫ থেকে ১২. শেষ লাইন বলে Warshall-এর ফল আর উপপাদ্য ৪.১-এর union (এখানে n = 4, তাই R4 পর্যন্ত) এক। কোডটি নতুন matrix না বানিয়ে একই W-তে লিখছে; কেন তাতে ভুল হয় না, তা §৬-এর শেষে প্রমাণ করা হয়েছে।
৬ · Warshall-এর সঠিকতার প্রমাণ আর খরচ
Algorithm-টির হৃদয় একটি loop invariant (পর্ব ১৩): বাইরের loop-এর প্রতিটি k-এর পরে একটি নির্দিষ্ট দাবি সত্য থাকে।
প্রতিটি 0 ≤ k ≤ n আর প্রতিটি i, j-তে: Wk[i][j] = 1 ⇔ R-এ i থেকে j-তে দৈর্ঘ্য ≥ 1-এর এমন একটি path আছে, যার সব interior vertex {1, …, k}-এ। বিশেষত Wn = MR*.
প্রমাণ (k-এর ওপর induction):
- Basis k = 0: {1, …, 0} = ∅, তাই শর্তটি মানে “কোনো interior vertex নেই”, অর্থাৎ path-টি একটি তীর (i, j) ∈ R, অর্থাৎ MR[i][j] = W0[i][j] = 1. — সংজ্ঞা
- Hypothesis: k − 1-এর জন্য দাবি সব i, j-তে সত্য। দুই দিক দেখাই। — অনুমান
- (⇐) Wk−1[i][j] = 1 হলে hypothesis অনুযায়ী interior {1, …, k − 1}-এ এমন path আছে, যা {1, …, k}-তেও পড়ে। আর Wk−1[i][k] = Wk−1[k][j] = 1 হলে i থেকে k আর k থেকে j-তে দুটি path, interior {1, …, k − 1}-এ; জোড়া দিলে i থেকে j-তে path, যার interior-এ শুধু ওগুলো আর k. — hypothesis, path জোড়া
- (⇒) i থেকে j-তে interior {1, …, k}-এ একটি path x0, …, xm নিন। k যদি কোনো interior অবস্থানে না আসে, interior {1, …, k − 1}-এ, আর hypothesis অনুযায়ী Wk−1[i][j] = 1. — ক্ষেত্র ১
- k যদি interior-এ আসে, ধরুন প্রথমবার অবস্থান p-তে আর শেষবার অবস্থান q-তে (1 ≤ p ≤ q ≤ m − 1). x0, …, xp হলো i থেকে k-তে দৈর্ঘ্য p ≥ 1-এর path, যার interior x1, …, xp−1-এ k নেই, তাই সেগুলো {1, …, k − 1}-এ; একইভাবে xq, …, xm হলো k থেকে j-তে। Hypothesis অনুযায়ী Wk−1[i][k] = Wk−1[k][j] = 1. — ক্ষেত্র ২: প্রথম আর শেষ k-তে কাটা
- দুই ক্ষেত্রেই সূত্রের ডান দিক 1, তাই Wk[i][j] = 1. Induction সম্পূর্ণ। k = n-এ শর্তটি কোনো বাধা নয়, তাই Wn[i][j] = 1 ⇔ কোনো path আছে ⇔ (i, j) ∈ R* (lemma ৩.২)। ∎ — উপপাদ্য ৩.৩
ধাপ ৫-এ “প্রথম আর শেষ” বাছাই জরুরি: path-এ k একাধিকবার আসতে পারে, আর তখন মাঝের যেকোনো দুই k-এর মধ্যে কাটলে বাঁ বা ডান টুকরোর ভেতরে k থেকে যেতে পারত। প্রথম k-এর আগের অংশে আর শেষ k-এর পরের অংশে k থাকা অসম্ভব। i = k হলেও যুক্তি চলে: তখন প্রথম টুকরোটি k থেকে k-তে একটি cycle.
ধাপ k-তে সারি k আর কলাম k বদলায় না: Wk[i][k] = Wk−1[i][k] আর Wk[k][j] = Wk−1[k][j].
- j = k বসালে সূত্রটি Wk−1[i][k] ∨ (Wk−1[i][k] ∧ Wk−1[k][k]). — প্রতিস্থাপন
- Absorption law x ∨ (x ∧ y) = x (পর্ব ০৩) অনুযায়ী এটি Wk−1[i][k]. সারির ক্ষেত্রে i = k বসিয়ে একই যুক্তি। ∎ — absorption
তাই ধাপ k-তে যে ঘরগুলো পড়া হয় (সারি k আর কলাম k), সেগুলো ওই ধাপে লেখা হয় না; একটি matrix-এই সব কাজ চলে, বাড়তি মেমরি লাগে না।
খরচ। n-টি ধাপ, প্রতিটিতে n2টি ঘর, প্রতিটি ঘরে একটি AND আর একটি OR: মোট 2n3, অর্থাৎ O(n3). n = 4-এ 128, যেখানে Boolean power পদ্ধতিতে 384; n বাড়লে অনুপাত n − 1 গুণ। প্রতিটি সারিকে একটি পূর্ণসংখ্যার bit হিসেবে রাখলে (পর্ব ১৯-এর আলোচনা) “সারি i OR সারি k” একটি অপারেশন হয়, যা নিচের ব্লকে ব্যবহার করা হয়েছে।
Reachability-র তৃতীয় একটি পথও আছে: প্রতিটি বিন্দু থেকে একটি breadth-first search (BFS) চালানো (DSA কোর্সের পর্ব ৪০)। নিচের ব্লক এলোমেলো digraph-এ (n ≤ 30, প্রতিটি সম্ভাব্য তীর 1.5/n সম্ভাবনায়, seed স্থির) তিনটি পদ্ধতির ফল মেলায়, আর পাঠ্যবইয়ের দুই matrix পদ্ধতির bit-অপারেশনের সংখ্যা সূত্র থেকে ও BFS-এর তীর-পরীক্ষা সরাসরি গুনে দেখায়।
import random
from functools import reduce
rng = random.Random(54)
def by_powers(rows, n): # R U R^2 U ... U R^n, rows are bitsets
P, acc = rows[:], rows[:]
for _ in range(n - 1): # P = R^m -> R^m o R = R^(m+1)
P = [reduce(int.__or__, (rows[t] for t in range(n) if p >> t & 1), 0)
for p in P]
acc = [a | p for a, p in zip(acc, P)]
return acc
def warshall(W, n): # W: a copy of the rows
for k in range(n):
for i in range(n): # i reaches k: add everything k reaches
if W[i] >> k & 1: W[i] |= W[k]
return W
def by_bfs(adj, n):
res, checks = [], 0
for s in range(n):
seen, q = 0, [s]
for u in q: # q grows while we walk it: BFS order
for v in adj[u]:
checks += 1
if not seen >> v & 1: seen |= 1 << v; q.append(v)
res.append(seen)
return res, checks
for n in (5, 10, 20, 30):
agree = checks = 0
for _ in range(25):
adj = [[v for v in range(n) if v != u and rng.random() < 1.5 / n]
for u in range(n)]
rows = [sum(1 << v for v in a) for a in adj]
b, c = by_bfs(adj, n); checks += c
agree += by_powers(rows, n) == warshall(rows[:], n) == b
print(f"n={n:2} agree {agree}/25 | powers {2*n**3*(n-1):>7}"
f" | warshall {2*n**3:>5} | bfs checks {checks // 25}")আসল আউটপুট (ব্রাউজারের Python):
n= 5 agree 25/25 | powers 1000 | warshall 250 | bfs checks 20
n=10 agree 25/25 | powers 18000 | warshall 2000 | bfs checks 75
n=20 agree 25/25 | powers 304000 | warshall 16000 | bfs checks 250
n=30 agree 25/25 | powers 1566000 | warshall 54000 | bfs checks 496
প্রতিটি n-এ ২৫টির ২৫টি digraph-এ তিন পদ্ধতির ফল হুবহু এক: তিনটি স্বাধীন পথ একই R* দিয়েছে। “powers” আর “warshall” কলাম সংজ্ঞা অনুযায়ী লেখা পাঠ্যবইয়ের বাস্তবায়নের bit-অপারেশন, 2n3(n − 1) আর 2n3; n = 30-এ ১৫,৬৬,০০০ বনাম ৫৪,০০০, ঠিক n − 1 = 29 গুণ। কোডের bitset রূপ এর চেয়ে কম কাজ করে, কারণ এক OR-এ পুরো সারি যায়, তবে অনুপাতের গল্প একই। BFS-এর সংখ্যা (প্রতিটি উৎস থেকে যতবার একটি তীর পরীক্ষা হলো, গড়ে) এখানে সবচেয়ে কম, কারণ এই graph-গুলো পাতলা: তীর মোটে প্রায় 1.5n. BFS-এর খরচ n(n + m)-এর মাপে, m = তীরের সংখ্যা; ঘন graph-এ (m প্রায় n2) তা n3-এ ওঠে, আর তখন Warshall-এর সরল তিনটি loop আর bitset সুবিধা জেতে।
৭ · Closure-এর ক্রম গুরুত্বপূর্ণ — equivalence closure
প্রায়ই একাধিক ধর্ম একসঙ্গে চাই। যেমন বাজারঘর জানতে চায়, কোন গ্রাহক-অ্যাকাউন্টগুলো “একই ব্যক্তির”: একই ফোন নম্বর বা একই ঠিকানা থাকলে দুটি অ্যাকাউন্ট যুক্ত, আর এই যোগাযোগ ধরে ধরে যতদূর যাওয়া যায়। এর জন্য symmetric আর transitive দুটিই চাই। প্রশ্ন: আগে কোনটি নেব? উত্তর: ক্রমে ফল বদলে যায়।
1R = {(1,2), (3,2)}2s(R)3t(s(R))4t(R)5s(t(R))6তুলনা
| relation | |
| সারি 1 | |
| সারি 2 | |
| সারি 3 | |
| জোড়া |
- শুরু। দুটি অ্যাকাউন্ট (1 আর 3) একই ঠিকানা-রেকর্ড 2-এর দিকে নির্দেশ করে: R = {(1, 2), (3, 2)}. R symmetric নয়, কিন্তু transitive (কোনো a → b → c জোড়া নেই, কারণ 2 থেকে কোনো তীর বের হয় না)।
- আগে symmetric. s(R) = R ∪ R−1 = {(1, 2), (2, 1), (3, 2), (2, 3)}: এখন 2 থেকে 1 আর 3-এ তীর আছে।
- তারপর transitive. 1 → 2 → 1 থেকে (1, 1), 1 → 2 → 3 থেকে (1, 3), 3 → 2 → 1 থেকে (3, 1), আর একইভাবে (2, 2), (3, 3). ফল পুরো A × A, ৯টি জোড়া, যা symmetric আর transitive দুটিই।
- উল্টো ক্রম: আগে transitive. R ইতিমধ্যে transitive, তাই t(R) = R; কিছুই যোগ হয় না।
- তারপর symmetric. s(t(R)) = {(1, 2), (2, 1), (3, 2), (2, 3)}, ৪টি জোড়া। কিন্তু এটি transitive নয়: (1, 2) আর (2, 1) আছে, অথচ (1, 1) নেই। Symmetric closure নতুন তীর যোগ করে নতুন path তৈরি করেছে, যা আর বন্ধ করা হয়নি।
- তুলনা। s(t(R)) ⊊ t(s(R)): ৪টি বনাম ৯টি। “Symmetric আর transitive দুটিই আছে এমন সবচেয়ে ছোট” relation হলো t(s(R)); s(t(R)) সেই প্রশ্নের উত্তরই নয়, কারণ তাতে transitive ধর্ম নেই। (হিসাব নিচের Python ব্লকে মেলানো।)
উদাহরণটি কাকতালীয় নয়। তিনটি সাধারণ উপপাদ্য পুরো ছবিটি দেয়। প্রথমে একটি সহায়ক তথ্য: closure monotone, অর্থাৎ R ⊆ S হলে c(R) ⊆ c(S); কারণ c(S) একটি P ধর্মওয়ালা relation, যাতে R আছে, তাই সংজ্ঞা ১.১(গ) অনুযায়ী c(R) ⊆ c(S).
(ক) R symmetric হলে t(R)-ও symmetric. (খ) যেকোনো R-এ s(t(R)) ⊆ t(s(R)), আর সমতা সবসময় হয় না। (গ) r(t(R)) = t(r(R)), অর্থাৎ reflexive আর transitive closure-এর ক্রম বদলালে কিছু হয় না।
- (ক) (a, b) ∈ t(R) = R* হলে a থেকে b-তে একটি path x0, …, xm আছে। R symmetric বলে প্রতিটি (xi, xi−1) ∈ R, তাই উল্টো ক্রম xm, …, x0 হলো b থেকে a-তে path, আর (b, a) ∈ R*. — উপপাদ্য ৩.৩, path উল্টানো
- (খ) T = t(s(R)) transitive, আর R ⊆ s(R) ⊆ T; তাই t(R) ⊆ T. (ক) অনুযায়ী T symmetric (কারণ s(R) symmetric), আর তাতে t(R) আছে, তাই s(t(R)) ⊆ T. Stepper-এ সমতা ভেঙেছে। — closure-এর minimality
- (গ) প্রথমে, t(R) ∪ Δ transitive: (a, b), (b, c) তাতে থাকলে কোনোটি Δ-তে হলে (a, c) অন্যটিরই সমান, আর দুটিই t(R)-এ হলে (a, c) ∈ t(R). এতে R ∪ Δ আছে, তাই t(r(R)) ⊆ t(R) ∪ Δ = r(t(R)). — ক্ষেত্র ভাগ, minimality
- উল্টো দিকে Δ ⊆ R ∪ Δ ⊆ t(R ∪ Δ), আর monotone বলে t(R) ⊆ t(R ∪ Δ); তাই r(t(R)) = t(R) ∪ Δ ⊆ t(r(R)). ∎ — monotonicity
R ধারণকারী সবচেয়ে ছোট equivalence relation হলো t(s(r(R))): আগে reflexive, তারপর symmetric, শেষে transitive.
- r(R) reflexive, s(r(R)) reflexive আর symmetric (জোড়া যোগে reflexivity হারায় না), আর t(s(r(R))) তিনটিই: reflexive কারণ superset, symmetric উপপাদ্য ৭.১(ক) অনুযায়ী, transitive সংজ্ঞায়। — ৭.১(ক)
- যেকোনো equivalence E ⊇ R reflexive, তাই E ⊇ r(R); symmetric, তাই E ⊇ s(r(R)); transitive, তাই E ⊇ t(s(r(R))). ∎ — প্রতিটি ধাপে minimality
উপপাদ্য ৭.১(গ) বলে r-কে যেকোনো জায়গায় সরানো যায়, কিন্তু t-কে s-এর আগে নিলে ভুল হতে পারে। Equivalence relation আর তার শ্রেণি (partition) নিয়ে পুরো আলোচনা পরের পর্ব ৫৫ “Equivalence relation আর partition”-এ; সেখানে এই closure দিয়েই শ্রেণি বানানো হবে, আর পর্ব ৫১-এর orbit-গুলোও এমন শ্রেণি। নিচের ব্লক উপপাদ্যগুলো এলোমেলো ২০০০টি relation-এ পরীক্ষা করে।
import random
from itertools import product
A = range(1, 5)
def r(R): return R | {(a, a) for a in A} # reflexive closure
def s(R): return R | {(b, a) for (a, b) in R} # symmetric closure
def t(R): # transitive (Warshall)
T = set(R)
for k in A:
T |= {(i, j) for i, j in product(A, A) if (i, k) in T and (k, j) in T}
return T
def sym(R): return R == s(R)
def trans(R): return all((a, d) in R for a, b in R for c, d in R if b == c)
R = {(1, 2), (3, 2)}
print("s(t(R)):", sorted(s(t(R))), "transitive:", trans(s(t(R))))
print("t(s(R)) has", len(t(s(R))), "pairs, symmetric:", sym(t(s(R))))
rng = random.Random(2026)
pairs = list(product(A, A))
differ = subset = rt_tr = rs_sr = ts_sym = 0
for _ in range(2000):
R = {p for p in pairs if rng.random() < 0.2}
differ += s(t(R)) != t(s(R))
subset += s(t(R)) <= t(s(R))
rt_tr += r(t(R)) == t(r(R))
rs_sr += r(s(R)) == s(r(R))
ts_sym += sym(t(s(R)))
print("of 2000 random R on {1,2,3,4}:")
print(" st != ts:", differ, "| st <= ts:", subset, "| ts symmetric:", ts_sym)
print(" rt == tr:", rt_tr, "| rs == sr:", rs_sr)আসল আউটপুট (ব্রাউজারের Python):
s(t(R)): [(1, 2), (2, 1), (2, 3), (3, 2)] transitive: False
t(s(R)) has 9 pairs, symmetric: True
of 2000 random R on {1,2,3,4}:
st != ts: 1667 | st <= ts: 2000 | ts symmetric: 2000
rt == tr: 2000 | rs == sr: 2000
প্রথম দুই লাইন stepper-এর উদাহরণ (এখানে A = {1, 2, 3, 4}, কিন্তু 4 কোনো জোড়ায় নেই, তাই সংখ্যা একই): s(t(R))-এ ৪টি জোড়া আর তা transitive নয়; t(s(R))-এ ৯টি, আর তা symmetric. পরের লাইনগুলো {1, 2, 3, 4}-এর ওপর ২০০০টি এলোমেলো relation-এ (প্রতিটি জোড়া 0.2 সম্ভাবনায়): ১৬৬৭টিতে দুই ক্রমের ফল আলাদা, অথচ s(t(R)) ⊆ t(s(R)) ২০০০টিতেই, আর t(s(R)) সবসময় symmetric, ঠিক উপপাদ্য ৭.১(ক) আর (খ)। শেষ লাইন: reflexive closure transitive বা symmetric যেকোনোটির সঙ্গে ক্রম বদলালেও একই ফল দেয়, ২০০০টিতেই। Random পরীক্ষা প্রমাণ নয়, কিন্তু ভুল দাবি থাকলে সাধারণত দ্রুত ধরা পড়ে; প্রমাণ ওপরে।
৮ · প্রয়োগ, পরীক্ষার অঙ্ক আর একটি ভুল যুক্তি
প্রয়োগ ১: ডেলিভারির reachability আর ফেরার পথ
W4-এর সারি পড়লে প্রতিটি গুদাম থেকে কোথায় মাল পাঠানো যায়, আর কলাম পড়লে কোথা থেকে মাল আসতে পারে। কর্ণের ঘর (i, i) = 1 মানে i থেকে বেরিয়ে আবার i-তে ফেরার একটি পথ আছে, অর্থাৎ ফেরত পণ্য (return) একই রুটে ফেরানো সম্ভব। দুটি গুদাম i আর j পরস্পর পৌঁছানো যায় (R*-এ (i, j) আর (j, i) দুটিই) এমন দলগুলো হলো {ঢাকা, গাজীপুর} আর {ময়মনসিংহ, নেত্রকোনা}; graph তত্ত্বে এদের বলে strongly connected component (DSA কোর্সের পর্ব ৪৬), আর connectivity নিয়ে বিস্তারিত পর্ব ৫৯ “Connectivity — path, component, cut vertex আর Menger-এর ধারণা”-এ। দূরত্ব বা খরচসহ সবচেয়ে ছোট পথ চাইলে একই তিন-loop কাঠামোয় ∨, ∧-এর জায়গায় min আর + বসে: সেটিই Floyd–Warshall algorithm (DSA কোর্সের পর্ব ৪৪)।
প্রয়োগ ২: dependency closure আর cycle ধরা
বাজারঘরের অর্ডার-প্রসেসিংয়ে কাজগুলোর একটি dependency graph আছে: (x, y) মানে “x শুরুর আগে y শেষ হতে হবে”। যেমন invoice → payment, payment → stock-check, packing → stock-check, delivery → packing. কোনো কাজের আগে মোট কী কী শেষ হতে হবে, তা transitive closure-এর সারি: delivery-র আগে packing আর stock-check. নিজেকেসহ গুনতে চাইলে reflexive-transitive closure r(t(R)) = R* ∪ Δ, যাকে অনেক বইয়ে reflexive-transitive closure বলে। সফটওয়্যার build system আর package manager একই হিসাব করে: একটি package-এর “transitive dependency” মানে এই closure-এর সারি।
একটি সহজ কিন্তু কাজের উপপাদ্য: R-এ কোনো cycle আছে ⇔ t(R)-এর কর্ণে অন্তত একটি 1 আছে। প্রমাণ: (a, a) ∈ R* মানে lemma ৩.২ অনুযায়ী a থেকে a-তে দৈর্ঘ্য ≥ 1-এর path, যা সংজ্ঞায় একটি cycle; উল্টোটিও একই কারণে। Dependency-তে cycle মানে কাজগুলো কখনো শুরুই করা যাবে না (x-এর আগে y, y-এর আগে x), তাই build system এটিকে ত্রুটি হিসেবে জানায়। Cycle না থাকলে কাজগুলোকে একটি সারিতে সাজানো যায়, যা topological sort (DSA কোর্সের পর্ব ৪২, আর গাণিতিক দিক পর্ব ৫৬ “Partial order, Hasse diagram, topological sort আর lattice”-এ)।
অঙ্ক ১: Warshall হাতে
A = {1, 2, 3, 4}, R = {(1, 3), (2, 1), (3, 2), (3, 4), (4, 1)}. Warshall দিয়ে t(R) বের করুন।
- W0 = MR: সারিগুলো 0010, 1000, 0101, 1000.
- k = 1: কলাম ১-এ 1 আছে সারি 2 আর 4-এ; দুটির সঙ্গেই সারি 1 (0010) OR: সারি 2 = 1010, সারি 4 = 1010. W1: 0010, 1010, 0101, 1010.
- k = 2: কলাম ২-এ 1 শুধু সারি 3-এ; সারি 3 = 0101 ∨ 1010 = 1111. W2: 0010, 1010, 1111, 1010.
- k = 3: কলাম ৩-এ চারটি সারিতেই 1; প্রতিটির সঙ্গে সারি 3 = 1111 OR, তাই W3 সব 1. k = 4-এ আর কিছু বদলায় না।
- উত্তর: t(R) = A × A, ১৬টি জোড়া। কারণ 1 → 3 → 2 → 1 একটি cycle, যাতে 1, 2, 3 আছে, আর 3 ⇄ 4 পথ (3 → 4 → 1 → 3) 4-কেও চক্রে আনে। (scratch-এ Python দিয়ে মেলানো।)
অঙ্ক ২: অপারেশনের হিসাব
১০টি গুদামের relation-এর transitive closure Boolean power দিয়ে আর Warshall দিয়ে করতে কতগুলো bit-অপারেশন লাগে? Boolean power: 2n3(n − 1) = 2 · 1000 · 9 = 18000. Warshall: 2n3 = 2000. অনুপাত 9 = n − 1 (তুলনার ব্লকের n=10 সারির সঙ্গে মেলে)।
অঙ্ক ৩: closure-এর জোড়া গোনা
A = {1, 2, 3}, R = {(1, 2), (2, 3), (3, 1)}. |r(R)|, |s(R)|, |t(R)| কত? r(R)-এ R-এর ৩টি আর কর্ণের ৩টি, মোট ৬. s(R)-এ প্রতিটি তীরের উল্টো নতুন, মোট ৬. t(R): §৪-এর stepper অনুযায়ী R ∪ R2 ∪ R3 = A × A, মোট ৯.
যুক্তির ভুল ধরুন: “আগে transitive, তারপর symmetric”
একজন লিখলেন: “R-এর equivalence closure পেতে আগে reflexive, তারপর transitive, শেষে symmetric closure নিই: s(t(r(R))). তিনটি ধর্মই যোগ হলো, তাই এটি equivalence relation.” R = {(1, 2), (3, 2)}, A = {1, 2, 3}-এ দেখি।
ভুল কোথায়: “তিনটি ধর্মই যোগ হলো” কথাটি মিথ্যা, কারণ শেষের symmetric closure নতুন তীর যোগ করে transitive ধর্ম ভাঙতে পারে। এখানে r(R) = R ∪ Δ, t(r(R)) = R ∪ Δ (কোনো নতুন path নেই), আর s(t(r(R))) = Δ ∪ {(1, 2), (2, 1), (3, 2), (2, 3)}, ৭টি জোড়া। এতে (1, 2) আর (2, 3) আছে, কিন্তু (1, 3) নেই: transitive নয়। সঠিক ক্রম t(s(r(R))) দেয় পুরো A × A, ৯টি জোড়া (দুটিই scratch-এ Python দিয়ে মেলানো)। শিক্ষা: “প্রতিটি ধাপ একটি ধর্ম যোগ করে” যথেষ্ট নয়; দেখাতে হয় যে পরের ধাপ আগের ধর্ম নষ্ট করে না। r আর s জোড়া যোগ করে transitivity নষ্ট করতে পারে, কিন্তু t symmetry বা reflexivity নষ্ট করে না (উপপাদ্য ৭.১(ক))।
আরও গভীরে: Transitive closure বের করা মোটামুটি Boolean matrix গুণের মতোই কঠিন, এর বেশি নয়: ১৯৭০-এর দশকের শুরুর কাজে (Fischer–Meyer আর Munro-র নামে উল্লেখিত) দেখানো হয়েছে যে একটির দ্রুত algorithm অন্যটিকেও প্রায় একই গতিতে দেয়। গ্রাফ যখন সময়ের সঙ্গে বদলায় (তীর যোগ বা বাদ), তখন প্রতিবার নতুন করে না গুনে closure হালনাগাদ রাখার “dynamic transitive closure” এখনও সক্রিয় গবেষণার বিষয়। আর relational database-এর তত্ত্বে একটি পরিচিত ফল হলো সাধারণ relational algebra দিয়ে transitive closure প্রকাশ করা যায় না (Aho আর Ullman-এর ১৯৭৯ সালের কাজের সঙ্গে যুক্ত বলে উল্লেখ করা হয়), তাই SQL-এ recursive query আর Datalog-এর মতো ভাষা আলাদা করে এসেছে।
পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন প্রায়ই আসে:
- Reflexive আর symmetric closure-এর সূত্র? r(R) = R ∪ Δ, s(R) = R ∪ R−1.
- n উপাদানের সেটে transitive closure পেতে R-এর কোন power পর্যন্ত লাগে? Rn পর্যন্ত; a ≠ b জোড়ার জন্য Rn−1-ই যথেষ্ট।
- Warshall-এর algorithm-এর ধাপ k-এর সূত্র আর মোট খরচ? Wk[i][j] = Wk−1[i][j] ∨ (Wk−1[i][k] ∧ Wk−1[k][j]); 2n3, অর্থাৎ O(n3).
- Antisymmetric closure কেন সবসময় থাকে না? (a, b) আর (b, a) দুটিই থাকলে (a ≠ b) কোনো superset antisymmetric হয় না।
- Equivalence closure কীভাবে? t(s(r(R))); s(t(R)) transitive না-ও হতে পারে।
৯ · AI-এর যুগে — closure আর Warshall-এর উত্তর যাচাই
Closure-এর হিসাবে AI সহকারী সাধারণত সূত্র ঠিক বলে, কিন্তু হাতে হিসাবের মাঝপথে বা প্রমাণে ভুল করে, আর ভুলটি আত্মবিশ্বাসী ভাষায় আসে। এই বিষয়ের কয়েকটি নিয়মিত ভুল:
| AI-এর ভুল | কীভাবে ধরবেন |
|---|---|
| R ∪ R2 ∪ ⋯ ∪ Rn−1-এ থেমে যাওয়া, ফলে কর্ণের জোড়া বাদ | একটি n-চক্রে (1 → 2 → ⋯ → n → 1) উত্তরটি পরীক্ষা করুন |
| Warshall-এর কোডে k-কে সবচেয়ে ভেতরের loop-এ রাখা | Invariant জিজ্ঞেস করুন; ছোট চক্রে BFS-এর ফলের সঙ্গে মেলান |
| “Closure-গুলোর ক্রম কোনো ব্যাপার নয়” দাবি | R = {(1, 2), (3, 2)}-এ s(t(R)) আর t(s(R)) দুটিই হিসাব করুন |
| R* চিহ্নের অর্থ মিশিয়ে ফেলা (transitive না reflexive-transitive) | উত্তরে Δ আছে কি না, আর বইয়ের সংজ্ঞা কোনটি, মিলিয়ে নিন |
| Equivalence closure-কে শুধু t(s(R)) বলা | একটি বিচ্ছিন্ন উপাদান (কোনো জোড়ায় নেই) দিয়ে দেখুন: তার (a, a) আসে কি না |
| “Antisymmetric closure”-এর হিসাব করে দেওয়া, বা ইতিহাসের সাল নিশ্চিত সুরে বলা | উপপাদ্য ১.২-এর শর্ত (i) মেলান; সাল বিশ্বস্ত বইয়ে দেখুন |
AI-এর কাছে closure বা Warshall-এর সমাধান চাইলে এমন কাঠামো দিন, যাতে প্রতিটি ধাপ আলাদা করে যাচাই করা যায়:
AI-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
Warshall-এর হিসাব যাচাইয়ের সবচেয়ে সস্তা উপায় দুটি স্বাধীন পথ: Warshall আর BFS, বা Warshall আর R ∪ ⋯ ∪ Rn. দুটি একই ফল দিলে তবেই ভরসা করুন; আর প্রমাণের ক্ষেত্রে invariant-টি নিজে লিখে দেখুন, AI-এর যুক্তি সেই invariant ধরে চলছে কি না।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Algorithm-টির নাম Warshall-এর, কিন্তু অনেক বইয়ে Roy, Floyd বা Kleene-এর নামও আসে। একই তিন-loop ধারণা কীভাবে কয়েকবার আবিষ্কৃত হলো?
Stephen Warshall ১৯৬২ সালে Journal of the ACM-এ “A Theorem on Boolean Matrices” নামে একটি ছোট প্রবন্ধ প্রকাশ করেন বলে উল্লেখ করা হয়, যেখানে 0-1 matrix-এর transitive closure এই পদ্ধতিতে বের করা হয়েছে। কিন্তু ফরাসি গণিতবিদ Bernard Roy ১৯৫৯ সালে প্রায় একই পদ্ধতি প্রকাশ করেছিলেন বলে অনেক পাঠ্যবই জানায়; তাই কোথাও কোথাও একে “Roy–Warshall algorithm” বলা হয়। একই ১৯৬২ সালে Robert Floyd Communications of the ACM-এ “Algorithm 97: Shortest Path” নামে খুব ছোট একটি লেখা প্রকাশ করেন, যেখানে ∨ আর ∧-এর জায়গায় min আর + বসিয়ে সব জোড়ার সবচেয়ে ছোট পথ বের করা হয়েছে; আজ যাকে Floyd–Warshall বলা হয়।
আরও আগে, ১৯৫৬ সালের দিকে Stephen Kleene finite automaton থেকে regular expression পাওয়ার যে পদ্ধতি দেন (Kleene-এর উপপাদ্যের প্রমাণ), তাতেও ঠিক এই কাঠামো আছে: “শুধু 1..k অবস্থাকে মাঝের অবস্থা ধরে i থেকে j-তে যাওয়ার সব পথের বর্ণনা” k বাড়িয়ে বাড়িয়ে গড়া। সেখানে ∨-এর জায়গায় regular expression-এর union, ∧-এর জায়গায় concatenation, আর একটি বাড়তি Kleene star আসে (k-তে কতবার ঘোরা হলো)। McNaughton আর Yamada ১৯৬০ সালে এর একটি রূপ দেন বলে উল্লেখ আছে। Automata আর regular expression নিয়ে পরে একটি আলাদা Theory of Computation কোর্সে বিস্তারিত আসবে।
কেন একই ধারণা বারবার আসে? কারণ এর পেছনের গাণিতিক কাঠামো একটিই: একটি “semiring”, যেখানে দুটি অপারেশন আছে, একটি পথগুলো মেলানোর (∨, min, union) আর একটি পথ জোড়া দেওয়ার (∧, +, concatenation), আর তারা বণ্টন নিয়ম মানে। এমন যেকোনো কাঠামোয় “মাঝের বিন্দু একটি একটি করে খোলা” dynamic programming কাজ করে। ১৯৭০-এর দশকে এই সাধারণ “algebraic path problem” দৃষ্টিভঙ্গি বিভিন্ন লেখকের কাজে স্পষ্ট হয়। Reachability, সবচেয়ে ছোট পথ, সবচেয়ে চওড়া পথ (bottleneck), এমনকি regular expression: সবই একই algorithm-এর ভিন্ন ভিন্ন semiring-এ চালানো।
মূল উপলব্ধি: Warshall, Roy, Floyd আর Kleene-এর পদ্ধতি একই dynamic programming: মাঝের বিন্দু একটি একটি করে খোলা; অপারেশন বদলালে reachability থেকে সবচেয়ে ছোট পথ বা regular expression পাওয়া যায়।
প্র ০২Warshall O(n3), আর প্রতিটি বিন্দু থেকে BFS চালালে O(n(n + m)). বাস্তবে কোনটি কখন বেছে নেওয়া উচিত? আরও দ্রুত কিছু কি আছে?
দুটির তুলনা নির্ভর করে graph কতটা ঘন তার ওপর। m = তীরের সংখ্যা। প্রতিটি বিন্দু থেকে একটি BFS-এর খরচ n + m-এর মাপে, তাই n-টি BFS মিলে n(n + m). পাতলা graph-এ (যেমন রাস্তার নেটওয়ার্ক, যেখানে প্রতিটি মোড় থেকে গড়ে কয়েকটি রাস্তা, m প্রায় n-এর কয়েক গুণ) এটি প্রায় n2, যা n3-এর চেয়ে অনেক ভালো। ঘন graph-এ (m প্রায় n2) দুটিই n3-এর মাপে, আর তখন Warshall-এর সুবিধা অন্য জায়গায়: তিনটি সরল loop, কোনো queue বা তালিকা নেই, আর bitset-এ “সারি i OR সারি k” একবারে 64টি ঘর হালনাগাদ করে। এই পর্বের তুলনার ব্লকে graph-গুলো পাতলা ছিল, তাই BFS-এর তীর-পরীক্ষার সংখ্যা সবচেয়ে কম এসেছে।
আরেকটি গুরুত্বপূর্ণ বিষয় আউটপুটের আকার। পুরো transitive closure নিজেই n2টি bit, তাই n = এক লাখ হলে আউটপুটই প্রায় ১২৫ কোটি byte. বাস্তবে তাই প্রায়ই পুরো closure বানানো হয় না; বরং প্রশ্ন এলে (“এই গুদাম থেকে ওই জেলায় যাওয়া যায়?”) একটি BFS বা DFS চালানো হয়, অথবা একটি সংক্ষিপ্ত index রাখা হয়। একটি পুরোনো কৌশল হলো আগে strongly connected component বের করা (DSA কোর্সের পর্ব ৪৬): একই component-এর সব বিন্দুর reachability এক, তাই প্রতিটি component-কে একটি বিন্দুতে সংকুচিত করলে একটি DAG পাওয়া যায়, যার closure ছোট। Purdom ১৯৭০ সালের দিকে এমন একটি পদ্ধতি দেন বলে উল্লেখ করা হয়।
তত্ত্বের দিকে, transitive closure-এর খরচ matrix গুণের খরচের সঙ্গে বাঁধা: Strassen-ধাঁচের দ্রুত গুণ ব্যবহার করে n3-এর চেয়ে কম সময়ে closure পাওয়া যায় (পর্ব ১৯-এর ভাবনার প্রশ্নে matrix-multiplication exponent ω নিয়ে আলোচনা)। কিন্তু এই algorithm-গুলোর ধ্রুবক বড়, আর বাস্তব আকারের graph-এ সাধারণত bitset-এর Warshall বা BFS-ই দ্রুত।
বাজারঘরের জন্য সিদ্ধান্তটি এমন হতে পারে: ৬৪টি জেলার গুদাম-নেটওয়ার্ক ছোট আর তুলনামূলক ঘন, তাই রাতে একবার Warshall চালিয়ে পুরো 64 × 64 টেবিল রেখে দেওয়া সস্তা। কিন্তু লাখ লাখ ডেলিভারি-পয়েন্টের রাস্তার নেটওয়ার্কে প্রতিটি প্রশ্নে একটি BFS বা Dijkstra চালানোই বাস্তবসম্মত।
মূল উপলব্ধি: পাতলা graph-এ প্রতিটি উৎস থেকে BFS, ঘন বা ছোট graph-এ bitset Warshall; আর পুরো closure-এর n2 আকার নিজেই একটি খরচ, তাই অনেক সময় শুধু দরকারি প্রশ্নের উত্তর খোঁজা ভালো।
প্র ০৩Software build tool আর package manager “transitive dependency” নিয়ে কাজ করে। Closure-এর গণিত এখানে কোন প্রশ্নগুলোর উত্তর দেয়, আর কোন বাস্তব সমস্যা তৈরি হয়?
Unix-এর make প্রোগ্রামটি Stuart Feldman ১৯৭৬ সালের দিকে Bell Labs-এ লেখেন বলে সাধারণভাবে উল্লেখ করা হয়। এর মূল ধারণা একটি dependency graph: প্রতিটি লক্ষ্য (যেমন একটি executable) কিছু ফাইলের ওপর নির্ভর করে, সেগুলো আবার অন্য ফাইলের ওপর। একটি source file বদলালে কোনগুলো আবার build করতে হবে? উত্তর: উল্টো relation R−1-এর transitive closure-এ ওই ফাইলের সারি, অর্থাৎ যারা সরাসরি বা পরোক্ষে এর ওপর নির্ভর করে। আর কোন ক্রমে build করতে হবে? একটি topological order, যা থাকে ঠিক তখন, যখন graph-এ cycle নেই, অর্থাৎ transitive closure-এর কর্ণে কোনো 1 নেই (§৮-এর উপপাদ্য)।
Package manager-এ একই গণিত বড় আকারে। একটি প্রজেক্ট সরাসরি হয়তো ১০টি library ব্যবহার করে, কিন্তু তাদের transitive closure-এ শত শত package থাকতে পারে। নিরাপত্তার দিক থেকে এটি গুরুত্বপূর্ণ: কোনো গভীর স্তরের একটি package-এ দুর্বলতা থাকলে, closure-এর কলাম পড়ে বোঝা যায় কোন কোন প্রজেক্ট ঝুঁকিতে। এই কারণে আধুনিক টুলগুলো “dependency tree” দেখানোর কমান্ড রাখে, আর সফটওয়্যারের উপাদান-তালিকা (software bill of materials) বানানো মানে মূলত এই closure লিখে রাখা।
বাস্তবে সমস্যা আরও জটিল, কারণ প্রতিটি package-এর একাধিক সংস্করণ থাকে, আর “A-কে B-এর 2.x সংস্করণ লাগবে, C-কে B-এর 3.x” ধরনের শর্ত মেলাতে হয়। সংস্করণ বাছাইয়ের এই সমস্যা সাধারণ আকারে NP-complete বলে জানা যায়, কারণ এর মধ্যে SAT প্রকাশ করা যায়; কিছু package manager তাই ভেতরে SAT solver ব্যবহার করে (পর্ব ৭৭ “SAT আর SMT solver — discrete math দিয়ে software যাচাই”-এর পটভূমি)। শুধু reachability, অর্থাৎ “কোন package-গুলো আসবে”, সেটি সহজ অংশ; সংস্করণের শর্ত মেলানো কঠিন অংশ।
Cycle-এর প্রশ্নও বাস্তব: দুটি module একে অপরকে import করলে অনেক ভাষায় তা ত্রুটি ঘটায়, তাই build tool closure-এর কর্ণ পরীক্ষা করে (বা DFS দিয়ে cycle খুঁজে) cycle-টি দেখায়।
মূল উপলব্ধি: Build আর package-এর জগতে transitive closure উত্তর দেয় “কী কী লাগবে” আর “কী কী প্রভাবিত হবে”, কর্ণ উত্তর দেয় “cycle আছে কি না”; সংস্করণ মেলানোর কঠিন অংশটি closure নয়, SAT-এর সমস্যা।
প্র ০৪Database-এ “এই কর্মচারীর অধীনে মোট কারা কাজ করেন” বা “এই ক্যাটাগরির সব উপ-ক্যাটাগরি” প্রশ্ন transitive closure চায়। সাধারণ SQL বা relational algebra কেন যথেষ্ট নয়, আর সমাধান কী?
Relational algebra (পর্ব ৫৩ “n-ary relation আর relational database-এর গণিত”) একটি নির্দিষ্ট প্রশ্নে একটি নির্দিষ্ট সংখ্যক join করে। একটি join দেয় R2, দুটি join R3, কিন্তু query লেখার সময়ই join-এর সংখ্যা ঠিক হয়ে যায়। অথচ transitive closure-এর জন্য দরকারি power-এর সংখ্যা ডেটার ওপর নির্ভর করে: বাজারঘরের ক্যাটাগরি-গাছ আজ ৩ স্তরের, কাল ৭ স্তরের হতে পারে। একটি পরিচিত তাত্ত্বিক ফল (Aho আর Ullman-এর ১৯৭৯ সালের প্রবন্ধের সঙ্গে যুক্ত বলে উল্লেখ করা হয়) বলে, কোনো একটি নির্দিষ্ট relational algebra প্রকাশ সব ডেটায় transitive closure দিতে পারে না।
সমাধান হিসেবে SQL:1999 মানদণ্ডে recursive query যোগ হয়: recursive CTE (WITH RECURSIVE). এর গঠন ঠিক এই পর্বের উপপাদ্য ৩.৩-এর মতো: একটি শুরুর অংশ (R নিজে) আর একটি পুনরাবৃত্ত অংশ (আগের ফলের সঙ্গে R-এর join, অর্থাৎ Rn থেকে Rn+1), আর নতুন কোনো সারি না আসা পর্যন্ত চলে। এই থামার বিন্দুকে বলে fixpoint: যে সেটে আরেক ধাপ খাটালে কিছু বদলায় না। সসীম ডেটায় fixpoint আসবেই, কারণ প্রতিটি ধাপ শুধু জোড়া যোগ করে আর জোড়ার সংখ্যা সসীম, ঠিক উপপাদ্য ৪.১-এর যুক্তি। DBMS কোর্সের পর্ব ২৫-এ এর বাস্তব SQL আর উদাহরণ আছে।
গণিতের দিক থেকে transitive closure হলো সবচেয়ে ছোট T, যাতে T = R ∪ (T ∘ R), অর্থাৎ একটি least fixpoint. Datalog নামে logic-ভিত্তিক query ভাষায় এটি দুই লাইনের নিয়ম: “R(x, y) হলে path(x, y)” আর “path(x, z) আর R(z, y) হলে path(x, y)”. Datalog-এর মূল তত্ত্ব ঠিক এই least fixpoint, আর program analysis, নিরাপত্তা নীতির যাচাই বা নেটওয়ার্ক কনফিগারেশন পরীক্ষার মতো ক্ষেত্রে Datalog-ধাঁচের engine ব্যবহার হয় বলে জানা যায়।
একটি সতর্কতা: ডেটায় cycle থাকলে আর query প্রতিটি সারিতে বাড়তে থাকা “পথের দৈর্ঘ্য” রাখলে নতুন সারি আসতেই থাকে, তাই এমন query-তে গভীরতার সীমা বা cycle-চিহ্ন রাখা হয়।
মূল উপলব্ধি: নির্দিষ্ট সংখ্যক join দিয়ে transitive closure হয় না; recursive CTE আর Datalog closure-কে least fixpoint হিসেবে গোনে, আর সসীম ডেটায় তা থামে ঠিক উপপাদ্য ৪.১-এর কারণে।
প্র ০৫“সবচেয়ে ছোট বাড়তি” ধারণাটি গণিতের আর কোথায় আসে? Closure-গুলোর সাধারণ ধর্ম কী, আর কেন intersection-এ বন্ধ পরিবারই আসল চাবি?
এই পর্বের closure c একটি নির্দিষ্ট ধরনের ফাংশন: প্রতিটি relation R-কে একটি বড় relation-এ পাঠায়, আর তিনটি ধর্ম মানে। (১) বিস্তার: R ⊆ c(R). (২) Monotone: R ⊆ S হলে c(R) ⊆ c(S). (৩) Idempotent: c(c(R)) = c(R). এই তিনটি ধর্মওয়ালা যেকোনো ফাংশনকে বলে closure operator. আর উপপাদ্য ১.২-এর কাঠামো, “পূর্ণ সেটটি পরিবারে আছে আর পরিবারটি intersection-এ বন্ধ”, ঠিক এমন পরিবারকেই বলে closure system বা Moore family (E. H. Moore-এর নামে, বিশ শতকের শুরুর কাজের জন্য বলে উল্লেখিত)। দুটি ধারণা আসলে একই: প্রতিটি closure operator একটি Moore family দেয় (যাদের c(X) = X), আর প্রতিটি Moore family একটি closure operator দেয় (সবচেয়ে ছোট সদস্য, যাতে X আছে)।
উদাহরণ সর্বত্র। Linear algebra-য় কিছু vector-এর span: তাদের ধারণকারী সবচেয়ে ছোট subspace, আর subspace-এর intersection আবার subspace. জ্যামিতিতে convex hull: সবচেয়ে ছোট convex সেট। Topology-তে একটি সেটের closure: সবচেয়ে ছোট closed সেট। পর্ব ৫৩ “n-ary relation আর relational database-এর গণিত”-এ functional dependency-র attribute closure X+: X থেকে যত attribute বের করা যায়। Group তত্ত্বে কিছু উপাদান দিয়ে “generate” করা subgroup (পর্ব ৫১-এ দুটি quarter turn থেকে কিউবের ২৪টি rotation). আর formal language-এ Kleene star: একটি ভাষা ধারণকারী সবচেয়ে ছোট ভাষা, যাতে ফাঁকা string আছে আর যা জোড়া দেওয়ায় বন্ধ।
প্রতিটি ক্ষেত্রে দুটি বর্ণনা পাওয়া যায়, ঠিক এই পর্বের মতো। “ওপর থেকে”: সব উপযুক্ত বড় বস্তুর intersection (উপপাদ্য ১.২)। “নিচ থেকে”: শুরু থেকে নিয়মটি বারবার খাটিয়ে যা তৈরি হয় তার union (উপপাদ্য ৩.৩-এর R ∪ R2 ∪ ⋯, span-এর ক্ষেত্রে সব linear combination). ওপরের বর্ণনা প্রমাণে সুবিধাজনক (minimality প্রায় বিনামূল্যে আসে), নিচের বর্ণনা হিসাবে সুবিধাজনক। দুটি এক, তা দেখানোই প্রতিটি ক্ষেত্রের প্রথম উপপাদ্য।
Antisymmetric ধর্ম কেন ব্যর্থ হলো, এই ভাষায় এখন পরিষ্কার: antisymmetric relation-গুলো intersection-এ বন্ধ, কিন্তু পূর্ণ সেট A × A তাদের মধ্যে নেই, তাই “উপরের” intersection-এর জন্য পরিবার শূন্য হয়ে যেতে পারে। Partial order-এর ক্ষেত্রেও একই সমস্যা (পর্ব ৫৬-এর বিষয়), যার কারণে “একটি relation-এর partial order closure” তখনই থাকে, যখন তার transitive closure নিজেই antisymmetric, অর্থাৎ R-এ দুই বা তার বেশি ভিন্ন উপাদানের কোনো cycle নেই।
মূল উপলব্ধি: Closure মানে একটি closure operator: বিস্তার, monotone আর idempotent; তা থাকে ঠিক যখন ধর্মওয়ালা বস্তুগুলোর পরিবার intersection-এ বন্ধ আর পূর্ণ সেটটি তাতে আছে, আর span, convex hull, attribute closure, Kleene star সবই একই ছাঁচের।
অনুশীলন
আগে খাতায় করুন, তারপর উত্তর খুলে মেলান।
-
প্রমাণ: প্রমাণ করুন, R reflexive হলে t(R)-ও reflexive, আর R-এর কোনো cycle না থাকলে (অর্থাৎ কোনো a থেকে a-তে path নেই) t(R) irreflexive (কোনো (a, a) নেই)। এই দুটির কোনটি §৮-এর cycle-উপপাদ্যের সঙ্গে যুক্ত?
- R reflexive মানে Δ ⊆ R, আর R ⊆ t(R); তাই Δ ⊆ t(R), অর্থাৎ t(R) reflexive. — closure-এর শর্ত (খ)
- ধরুন কোনো (a, a) ∈ t(R). উপপাদ্য ৩.৩ অনুযায়ী t(R) = R*, আর lemma ৩.২ অনুযায়ী a থেকে a-তে দৈর্ঘ্য ≥ 1-এর একটি path আছে, যা একটি cycle. — উপপাদ্য ৩.৩, lemma ৩.২
- এটি “কোনো cycle নেই”-এর বিরোধী, তাই t(R)-এ কোনো (a, a) নেই। ∎ — proof by contradiction
দ্বিতীয় অংশটিই §৮-এর উপপাদ্যের এক দিক (cycle নেই ⇒ কর্ণ ফাঁকা)। যাচাই: R = {(1, 2), (2, 3)}-এ t(R) = {(1, 2), (2, 3), (1, 3)}, কর্ণ ফাঁকা।
-
হিসাব: A = {1, 2, 3, 4}, R = {(1, 2), (2, 1), (2, 3), (3, 4), (4, 3)} (§৩-এর গুদাম)। (ক) s(R) আর t(s(R))-এ কয়টি জোড়া? (খ) Warshall-এ মোট কয়টি bit-অপারেশন, আর Boolean power পদ্ধতিতে কয়টি? (গ) s(t(R))-এ কয়টি জোড়া, আর তা t(s(R))-এর সমান কি?
(ক) s(R) = R ∪ {(3, 2)}: R-এর পাঁচটির মধ্যে শুধু (2, 3)-এর উল্টো নেই, তাই ৬টি জোড়া। এখন 1 ⇄ 2 ⇄ 3 ⇄ 4 একটি দুই-মুখী শিকল, তাই প্রত্যেকে প্রত্যেকের কাছে পৌঁছায়, নিজের কাছেও (যেমন 1 → 2 → 1): t(s(R)) = A × A, ১৬টি.
(খ) n = 4: Warshall 2 · 43 = 128, Boolean power 2 · 43 · 3 = 384.
(গ) t(R)-এর ১২টি জোড়ার (§৫) সঙ্গে উল্টো (3, 1), (3, 2), (4, 1), (4, 2) যোগ করলে s(t(R))-ও ১৬টি। অর্থাৎ এই R-এ দুই ক্রমের ফল সমান; উপপাদ্য ৭.১(খ) বলে সমতা সবসময় হয় না, কখনো হয় না এমন নয়। (সব সংখ্যা scratch-এ Python দিয়ে মেলানো।)
-
আউটপুট অনুমান: নিচের কোডে Warshall-এর তিনটি loop-এর ক্রম বদলে k-কে সবচেয়ে ভেতরে রাখা হয়েছে। কোড না চালিয়ে বলুন কী ছাপা হবে, আর ফলটি সঠিক transitive closure কি না। তারপর যেকোনো Python-এ চালিয়ে মেলান।
M = [[0, 1, 0], [0, 0, 1], [1, 0, 0]] # 1->2, 2->3, 3->1 (0-based rows) n = 3 for i in range(n): for j in range(n): for k in range(n): # k innermost: the wrong order if M[i][k] and M[k][j]: M[i][j] = 1 print(["".join(map(str, row)) for row in M])['011', '111', '111']সঠিক উত্তর (তিন চক্রের closure, §৪-এর stepper) হলো সব 1: ['111', '111', '111']. ভুল ক্রমে (i, j) = (0, 0) ঘরটি একবারই দেখা হয়, তখন সারি 0-এ শুধু M[0][1] = 1, আর M[1][0] তখনও 0 (সারি 1 পরে আপডেট হবে), তাই M[0][0] ফাঁকা থেকে যায়, আর পরে আর কখনো দেখা হয় না। Warshall-এর invariant বাইরের loop k-এর ওপর দাঁড়ানো; k ভেতরে গেলে কোনো invariant থাকে না। আউটপুট CPython-এ চালিয়ে মেলানো।
-
প্রমাণের ভুল ধরুন: একজন লিখলেন: “দাবি: প্রতিটি relation R-এর antisymmetric closure আছে। প্রমাণ: antisymmetric relation-গুলোর intersection antisymmetric (কারণ (a, b) আর (b, a) intersection-এ থাকলে প্রতিটি সদস্যে থাকে, আর সেখানে a = b). তাই R ধারণকারী সব antisymmetric relation-এর intersection নিলেই সবচেয়ে ছোট antisymmetric superset পাওয়া যায়। ∎” ভুল কোথায়?
- যা ঠিক: antisymmetric relation-এর যেকোনো অশূন্য পরিবারের intersection সত্যিই antisymmetric; উপপাদ্য ১.২-এর শর্ত (ii) মেলে।
- ভুল ধাপ: পরিবারটি অশূন্য কি না দেখা হয়নি, অর্থাৎ শর্ত (i) বাদ। R = {(1, 2), (2, 1)} হলে R ধারণকারী কোনো antisymmetric relation নেই, পরিবার শূন্য। শূন্য পরিবারের intersection-এর প্রচলিত অর্থ পুরো A × A, যা antisymmetric নয়; আর এমনিতেও তা R-এর “antisymmetric superset” নয়।
- সংশোধন: দাবিটি শুধু তখন সত্য, যখন R নিজেই antisymmetric (তখন closure = R). §৩-এর প্রথম Python ব্লক দেখায়, {1, 2, 3}-এর ৫১২টি relation-এর ২৯৬টির কোনো antisymmetric superset নেই।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমWarshall-এর algorithm দিয়ে একটি relation-এর transitive closure বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Relation-কে 0-1 matrix W0 = MR হিসেবে লেখা
- k = 1 নিয়ে কলাম k-এ 1 আছে এমন সারিগুলো খুঁজে বের করা
- সেই প্রতিটি সারির সঙ্গে সারি k-কে OR করে Wk পাওয়া
- k এক বাড়িয়ে আগের দুই ধাপ k = n পর্যন্ত চালানো
- Wn-এর 1-গুলো পড়ে transitive closure-এর জোড়া লেখা
A = {1, 2, 3}, R = {(1, 2), (2, 3)}. R-এর transitive closure t(R) কোনটি?
- {(1, 2), (2, 3)}
- {(1, 2), (2, 1), (2, 3), (3, 2)}
- {(1, 2), (2, 3), (1, 3)}
- {(1, 1), (1, 2), (1, 3), (2, 3)}
A = {1, 2, 3, 4} আর R = {(1, 2), (2, 2), (3, 4)}. Symmetric closure s(R)-এ মোট কয়টি জোড়া?
- 3
- 5
- 6
- 7
Warshall-এর algorithm-এ Wk[i][j] = 1 ঠিক কখন?
- i থেকে j-তে ঠিক k দৈর্ঘ্যের path থাকলে
- i থেকে j-তে বড়জোর k দৈর্ঘ্যের path থাকলে
- i ≤ k আর j ≤ k হলে আর i থেকে j-তে path থাকলে
- i থেকে j-তে এমন path থাকলে, যার সব মাঝের বিন্দু {1, …, k}-এ
৫ উপাদানের একটি সেটে R ∪ R2 ∪ ⋯ ∪ Rm সব R-এর জন্য transitive closure দেয়, এমন সবচেয়ে ছোট m কত?
- 5
- 4
- 25
- 2
A = {1, 2, 3}, R = {(1, 2), (3, 2)}. s(t(R)) আর t(s(R))-এ যথাক্রমে কয়টি জোড়া?
- দুটিতেই 4
- দুটিতেই 9
- 4 আর 9
- 9 আর 4
নিচের কোন ধর্মের closure সব relation-এর জন্য থাকে না?
- reflexive
- antisymmetric
- symmetric
- transitive
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Closure = সবচেয়ে ছোট বাড়তি: ধর্মওয়ালা সব superset-এর intersection; থাকে যখন পূর্ণ relation-এর ধর্মটি আছে আর ধর্মটি intersection-এ টিকে থাকে। Antisymmetric-এর closure সাধারণভাবে নেই।
- সহজ দুটি: r(R) = R ∪ Δ, s(R) = R ∪ R−1; matrix-এ M ∨ I আর M ∨ MT.
- Transitive closure = R*: (a, b) ∈ t(R) ⇔ a থেকে b-তে path আছে; n উপাদানে R ∪ ⋯ ∪ Rn যথেষ্ট (pigeonhole), Boolean power-এ খরচ 2n3(n − 1).
- Warshall: Wk[i][j] = Wk−1[i][j] ∨ (Wk−1[i][k] ∧ Wk−1[k][j]); invariant “interior vertex সব 1..k-এ”, খরচ 2n3, বাইরের loop অবশ্যই k.
- ক্রম: s(t(R)) ⊆ t(s(R)), সমতা সবসময় নয়; r যেকোনো জায়গায় যায়; equivalence closure = t(s(r(R))); cycle আছে ⇔ t(R)-এর কর্ণে 1.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৫ · Equivalence relation আর partitionপরবর্তী পর্বEquivalence closure থেকে শ্রেণি আর partition.
- পর্ব ১৯ · Discrete math-এ matrix — 0-1 matrix আর Boolean productপুনরালোচনাBoolean product, Boolean power আর walk-এর উপপাদ্য।
- পর্ব ৩২ · Pigeonhole principle — সহজ থেকে generalizedপুনরালোচনা“n ধাপই যথেষ্ট” প্রমাণের মূল নীতি।
- ডিসক্রিট ম্যাথমেটিক্স · কোর্স হোম৮৩টি পর্বপুরো কোর্সের মানচিত্র আর ১৪টি মডিউল।
- DSA · পর্ব ৪৪ · Bellman-Ford আর Floyd-Warshallকোডে প্রয়োগএকই তিন-loop কাঠামোয় সব জোড়ার সবচেয়ে ছোট পথ।
- DBMS · পর্ব ২৫ · CTE আর recursive queryডেটাবেসে প্রয়োগSQL-এ transitive closure: WITH RECURSIVE.