মেট্রিক TSP অ্যাপ্রক্সিমেশন
এই পাঠে যা শিখবেন
- মেট্রিক TSP কী, এবং ত্রিভুজ অসমতা কেন এই অ্যাপ্রক্সিমেশনের জন্য অপরিহার্য
- MST-ভিত্তিক ২-অ্যাপ্রক্সিমেশন অ্যালগরিদমের সম্পূর্ণ নির্মাণ — MST, DFS প্রি-অর্ডার, শর্টকাট
- সম্পূর্ণ প্রমাণ ধাপে ধাপে — কেন এই ট্যুর কখনো অপটিমামের দ্বিগুণের বেশি হয় না
- ব্রুট-ফোর্স এক্স্যাক্ট TSP-র বিপরীতে বহু র্যান্ডম ইউক্লিডিয়ান ইনস্ট্যান্সে রেশিও কম্পিউটেশনালি যাচাই
১ · মেট্রিক TSP — সাধারণ TSP-র একটি গুরুত্বপূর্ণ বিশেষ কেস
ট্র্যাভেলিং সেলসম্যান প্রবলেম (TSP) — সব শহর ঠিক একবার ভ্রমণ করে সর্বনিম্ন মোট দূরত্বে শুরুর শহরে ফিরে আসা — ইতিমধ্যে DSA কোর্সে এবং এই কোর্সের M8/L39-এ ব্রাঞ্চ-অ্যান্ড-বাউন্ড দিয়ে দেখা হয়েছে। সাধারণভাবে TSP অ্যাপ্রক্সিমেট করাও কঠিন — যদি দূরত্বগুলো স্বেচ্ছাচারী হয় (কোনো কাঠামো ছাড়া), তাহলে $P \ne NP$ ধরে নিলে কোনো ধ্রুবক-ফ্যাক্টর অ্যাপ্রক্সিমেশনই সম্ভব নয়।
কিন্তু বাস্তব-জীবনের বেশিরভাগ দূরত্ব একটি বিশেষ ধর্ম মেনে চলে — ত্রিভুজ অসমতাTriangle Inequalityযেকোনো তিনটি বিন্দু $a, b, c$-এর জন্য $d(a,c) \le d(a,b) + d(b,c)$ — অর্থাৎ $b$ হয়ে ঘুরে যাওয়া কখনো সরাসরি যাওয়ার চেয়ে ছোট পথ হতে পারে না।: সমতলে ইউক্লিডিয়ান দূরত্ব, সড়কপথের দূরত্ব ইত্যাদি সবই এটি মেনে চলে। এই ধর্ম থাকা TSP-কে মেট্রিক TSP বলা হয় — এবং এই একটি অতিরিক্ত শর্তই একটি চমৎকার ধ্রুবক-২ অ্যাপ্রক্সিমেশন সম্ভব করে তোলে।
২ · অ্যালগরিদম — MST তৈরি, DFS প্রি-অর্ডার, শর্টকাট
অ্যালগরিদমের তিনটি ধাপ:
- MST তৈরি করো: সব শহরের উপর একটি মিনিমাম স্প্যানিং ট্রি বানাও (ক্রুসকাল বা প্রিম — দেখুন M5/L23-এ প্রমাণসহ পূর্ণ আলোচনা, এখানে শুধু ব্যবহার করা হচ্ছে)।
- DFS প্রি-অর্ডার ওয়াক করো: যেকোনো একটি রুট থেকে MST-তে DFS চালাও এবং প্রতিটি ভার্টেক্স প্রথমবার ভিজিট হওয়ার ক্রম (প্রি-অর্ডার) রেকর্ড করো।
- শর্টকাট করে ট্যুর বানাও: এই প্রি-অর্ডার ক্রমটিই ট্যুর — কারণ প্রতিটি ভার্টেক্স এখানে ঠিক একবার আসে (প্রথমবার ভিজিট হওয়ার সময়ই তালিকায় যোগ হয়, আবার ফিরে এলে তা তালিকায় যোগ হয় না, বরং পরবর্তী নতুন ভার্টেক্সে সরাসরি "শর্টকাট" করা হয়েছে বলে ধরা হয়)। শেষে শুরুর ভার্টেক্সে ফিরে ট্যুর সম্পূর্ণ করো।
৩ · প্রমাণ — কেন এই ট্যুর কখনো $2\cdot\text{OPT}$ ছাড়ায় না
প্রমাণটি তিনটি ধাপে ভাঙা যায়:
অপটিমাল ট্যুর থেকে যেকোনো একটি এজ বাদ দিলে একটি হ্যামিল্টোনিয়ান পাথ পাওয়া যায়, যা নিজেই সব ভার্টেক্স স্পর্শ করা একটি স্প্যানিং ট্রি। MST হলো সবচেয়ে ছোট স্প্যানিং ট্রি, তাই এর ওজন এই পাথের ওজনের চেয়ে বেশি হতে পারে না, আর এই পাথের ওজন $\text{OPT}$-এর চেয়ে কম বা সমান (একটি এজ বাদ দেওয়ায় ওজন কমেছে বৈ বাড়েনি)।
MST-তে একটি সম্পূর্ণ DFS ওয়াক (নিচে নামা, আবার উপরে ব্যাকট্র্যাক করা) প্রতিটি এজ ঠিক দুইবার ব্যবহার করে — একবার নামার সময়, একবার ফেরার সময়। তাই এই ওয়াকের মোট দৈর্ঘ্য ঠিক $2\cdot w(\text{MST})$।
প্রি-অর্ডার তালিকা তৈরি করে আমরা এই দীর্ঘ ওয়াক থেকে পুনরাবৃত্ত ভিজিট বাদ দিয়ে সরাসরি পরের নতুন ভার্টেক্সে যাচ্ছি — ত্রিভুজ অসমতার কারণে সরাসরি দূরত্ব কখনো ঘুরপথের দূরত্বের চেয়ে বেশি হতে পারে না। তাই শেষ ট্যুরের দৈর্ঘ্য $\le 2\cdot w(\text{MST})$।
তিনটি ধাপ একসাথে মিলিয়ে:
$$\text{ALG}(I) \le 2\cdot w(\text{MST}) \le 2\cdot\text{OPT}(I)$$এখানে লক্ষ্য করুন — ধাপ ৩ ঠিক সেই জায়গা যেখানে ত্রিভুজ অসমতা ব্যবহার হচ্ছে। এই শর্ত ছাড়া (সাধারণ TSP-তে) শর্টকাট করলে দৈর্ঘ্য বাড়তেও পারে, তাই এই প্রমাণটি ভেঙে পড়ে — এই কারণেই এই অ্যালগরিদম শুধু মেট্রিক TSP-তে কাজ করে।
এবার এটি বাস্তবায়ন করে দেখা যাক — ছোট র্যান্ডম ইউক্লিডিয়ান ইনস্ট্যান্সে (যা স্বয়ংক্রিয়ভাবেই ত্রিভুজ অসমতা মেনে চলে) এই অ্যালগরিদম এবং একটি ব্রুট-ফোর্স এক্স্যাক্ট TSP সলভার (সব পারমুটেশন চেষ্টা করে) দুটোই চালিয়ে, রেশিও সত্যিই গণনা করে দেখানো হচ্ছে:
import random
import math
from itertools import permutations
def euclidean(p, q):
return math.hypot(p[0] - q[0], p[1] - q[1])
def build_mst_prim(points):
# প্রিমের অ্যালগরিদম (দেখুন M5/L23-এ পূর্ণ প্রমাণসহ আলোচনা) -- এখানে শুধু ব্যবহার করা হচ্ছে
n = len(points)
in_tree = [False] * n
dist = [math.inf] * n
parent = [-1] * n
dist[0] = 0
mst_edges = []
for _ in range(n):
u = min((v for v in range(n) if not in_tree[v]), key=lambda v: dist[v])
in_tree[u] = True
if parent[u] != -1:
mst_edges.append((parent[u], u))
for v in range(n):
if not in_tree[v]:
d = euclidean(points[u], points[v])
if d < dist[v]:
dist[v] = d
parent[v] = u
return mst_edges
def approx_tsp_tour(points):
n = len(points)
mst_edges = build_mst_prim(points)
adj = {i: [] for i in range(n)}
for u, v in mst_edges:
adj[u].append(v)
adj[v].append(u)
visited = [False] * n
order = []
def dfs(u):
visited[u] = True
order.append(u)
for v in sorted(adj[u]):
if not visited[v]:
dfs(v)
dfs(0)
return order
def tour_length(points, order):
total = 0.0
n = len(order)
for i in range(n):
a = points[order[i]]
b = points[order[(i + 1) % n]]
total += euclidean(a, b)
return total
def brute_force_tsp(points):
n = len(points)
best = math.inf
rest = list(range(1, n))
for perm in permutations(rest):
order = [0] + list(perm)
length = tour_length(points, order)
if length < best:
best = length
return best
rng = random.Random(11)
worst_ratio = 0.0
print(f"{'trial':>5} | {'n':>3} | {'ALG':>8} | {'OPT':>8} | {'ratio':>6}")
for trial in range(10):
n = rng.randint(4, 8)
points = [(rng.uniform(0, 10), rng.uniform(0, 10)) for _ in range(n)]
order = approx_tsp_tour(points)
alg_len = tour_length(points, order)
opt_len = brute_force_tsp(points)
ratio = alg_len / opt_len
worst_ratio = max(worst_ratio, ratio)
print(f"{trial:>5} | {n:>3} | {alg_len:>8.2f} | {opt_len:>8.2f} | {ratio:>6.2f}")
assert ratio <= 2 + 1e-9, "২-অ্যাপ্রক্সিমেশন বাউন্ড ভঙ্গ হয়েছে!"
print(f"\nসর্বোচ্চ পর্যবেক্ষিত ratio (ALG/OPT): {worst_ratio:.2f} -- সবসময় <= 2.00")
points সমতলে র্যান্ডম বিন্দু, এবং euclidean দূরত্ব স্বয়ংক্রিয়ভাবেই
ত্রিভুজ অসমতা মেনে চলে (এটি একটি জ্যামিতিক সত্য, আলাদা করে প্রমাণ করার দরকার নেই)। brute_force_tsp
প্রথম শহরকে স্থির রেখে বাকি $(n-1)!$টি ক্রম পরীক্ষা করে সত্যিকারের অপটিমাম বের করছে — $n \le 8$ হওয়ায়
সর্বোচ্চ $7! = 5040$টি ক্রম, যা Pyodide-এ তাৎক্ষণিক। প্রতিটি ট্রায়ালে assert সত্যিই যাচাই
করছে রেশিও ২ ছাড়ায়নি।
এই লেসনটি M5/L23-এর MST-র উপর সরাসরি নির্ভরশীল — সেখানে দেখানো হয়েছিল কেন কাট প্রপার্টি ক্রুসকাল ও প্রিমকে সঠিক MST দিতে বাধ্য করে। এখানে আমরা সেই সঠিকতাকে axiom হিসেবে ধরে নিয়ে ($w(\text{MST})$ সত্যিই ন্যূনতম স্প্যানিং ওজন) তার উপর একটি সম্পূর্ণ নতুন প্রমাণ — TSP অ্যাপ্রক্সিমেশন — নির্মাণ করেছি। এটাই অ্যালগরিদম ডিজাইনের একটি সাধারণ প্যাটার্ন: একটি প্রমাণিত বিল্ডিং ব্লক (MST) নিয়ে তার উপর একটি নতুন গ্যারান্টি (TSP ২-অ্যাপ্রক্সিমেশন) দাঁড় করানো।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ যদি দূরত্বগুলো ত্রিভুজ অসমতা না মানত (যেমন কোনো "শর্টকাট" অস্বাভাবিকভাবে দীর্ঘ হতে পারত), তাহলে প্রমাণের ঠিক কোন ধাপটি ভেঙে পড়ত?
ধাপ ৩ — "শর্টকাট করলে দৈর্ঘ্য বাড়ে না" এই দাবিটি সরাসরি ত্রিভুজ অসমতার উপর নির্ভরশীল ($d(a,c) \le d(a,b)+d(b,c)$)। ত্রিভুজ অসমতা না থাকলে সরাসরি $a$ থেকে $c$-তে যাওয়া $b$ হয়ে যাওয়ার চেয়ে বেশি দূরত্বের হতে পারে — তখন শর্টকাট করলে ট্যুরের দৈর্ঘ্য বেড়ে যেতে পারে, এবং $\text{ALG} \le 2\cdot w(\text{MST})$ দাবিটি আর সত্য থাকে না। এই কারণেই অ্যালগরিদমটি শুধু মেট্রিক TSP-তে কাজ করে, সাধারণ TSP-তে নয়।
প্র ০২ ধাপ ১-এ বলা হয়েছে "অপটিমাল ট্যুর থেকে একটি এজ বাদ দিলে একটি স্প্যানিং ট্রি পাওয়া যায়"। এই স্প্যানিং ট্রিটি কি নিজেই MST হতে হবে?
না, এবং সেটাই মূল বিষয়। এই বাদ-দেওয়া-এজ-এর ট্রিটি কোনো একটি বৈধ স্প্যানিং ট্রি (সব ভার্টেক্স সংযুক্ত করে, সাইকেল ছাড়া) — MST না হয়েও চলবে। যেহেতু MST সংজ্ঞা অনুযায়ী সব স্প্যানিং ট্রির মধ্যে সর্বনিম্ন ওজনের, তাই MST-র ওজন এই নির্দিষ্ট স্প্যানিং ট্রির ওজনের চেয়ে বেশি হতে পারে না — আর এই নির্দিষ্ট ট্রির ওজন আবার $\text{OPT}$-এর চেয়ে কম বা সমান। এই দুই ধাপের তুলনা মিলিয়েই $w(\text{MST}) \le \text{OPT}$ প্রমাণিত হয়, কোনো নির্দিষ্ট MST অ্যালগরিদমের বিস্তারিত না জেনেই।
প্র ০৩
উপরের কোডে dfs ফাংশনটি sorted(adj[u]) ব্যবহার করে প্রতিবেশী ভ্রমণ করে।
যদি এর বদলে অন্য কোনো ক্রমে (যেমন reversed) ভ্রমণ করা হতো, তাহলে কি প্রমাণিত ২-অ্যাপ্রক্সিমেশন
গ্যারান্টি নষ্ট হয়ে যেত?
না। প্রমাণটি (ধাপ ১-৩) কোনো নির্দিষ্ট DFS ভ্রমণ-ক্রমের উপর নির্ভর করে না — যেকোনো ক্রমে DFS চালালেই
একটি সম্পূর্ণ ওয়াক তৈরি হয় যার দৈর্ঘ্য ঠিক $2\cdot w(\text{MST})$ (প্রতিটি এজ দুইবার ব্যবহৃত হয়, ক্রম
নির্বিশেষে), এবং শর্টকাট করলে ত্রিভুজ অসমতার কারণে দৈর্ঘ্য কমে বা সমান থাকে, বাড়ে না — এটিও ক্রম-নির্বিশেষে
সত্য। তাই sorted() এখানে শুধু ফলাফল reproducible (পুনরুৎপাদনযোগ্য) রাখার জন্য ব্যবহার করা
হয়েছে, প্রমাণের জন্য আবশ্যিক নয়।
অনুশীলন
-
চিন্তা করুন: ৩টি বিন্দু একটি সরলরেখায় থাকলে (যেমন $(0,0)$, $(1,0)$, $(2,0)$), MST-ভিত্তিক
অ্যালগরিদম কী ট্যুর তৈরি করবে, এবং এটি কি অপটিমাল হবে?
MST এখানে একটি পাথ: $(0,0)-(1,0)-(2,0)$ (ওজন $1+1=2$)। DFS প্রি-অর্ডার (রুট $(0,0)$ থেকে শুরু করলে) দেবে $[(0,0),(1,0),(2,0)]$, আর ট্যুর দৈর্ঘ্য হবে $1+1+2=4$ (শেষ থেকে শুরুতে ফেরত)। প্রকৃত অপটিমাম ট্যুরও একই — $4$ (৩টি বিন্দুতে ট্যুরের ক্রম কোনো ব্যাপার না, একই দৈর্ঘ্য)। এখানে অ্যালগরিদম রেশিও $1.0$ — অপটিমাল, যদিও গ্যারান্টি ছিল শুধু $\le 2$।
-
পরীক্ষা করুন: উপরের কোড সেলে
rng = random.Random(11)-এর জায়গায়rng = random.Random(2026)বসিয়ে এবংrange(10)-কেrange(15)করে Run চেপে দেখুন — আরও বেশি ট্রায়ালেও রেশিও কখনো ২ ছাড়ায় কি না।seed ও ট্রায়াল সংখ্যা যাই হোক না কেন, প্রতিটি ট্রায়ালে
assert ratio <= 2 + 1e-9পাস করবে — কারণ প্রমাণটি কোনো নির্দিষ্ট বিন্দু-বিন্যাসের উপর নির্ভর করে না, বরং ত্রিভুজ অসমতা মেনে চলা যেকোনো দূরত্ব-ফাংশনের জন্য সাধারণভাবে সত্য। আপনি লক্ষ্য করবেন বেশিরভাগ রেশিও আসলে $1.0$-$1.5$-এর মধ্যেই থাকে — ২ শুধুই একটি ওয়ার্স্ট-কেস উচ্চসীমা।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরবর্তী পাঠ — র্যান্ডোমাইজড অ্যালগরিদম, লাস ভেগাস বনাম মন্টি কার্লো L53 ইনট্র্যাক্টেবিলিটি সামলানোর আরেকটি টুল — অ্যাপ্রক্সিমেশনের বদলে র্যান্ডমনেস ব্যবহার করে।
- M5 — MST, ক্রুসকাল ও প্রিম, প্রমাণসহ L23 এই লেসনে ব্যবহৃত MST-র নিজস্ব সঠিকতা প্রমাণ (কাট প্রপার্টি) সেখানে বিস্তারিত।
- M8 — TSP, ব্রাঞ্চ-অ্যান্ড-বাউন্ড L39 যখন সত্যিকারের অপটিমাল ট্যুর দরকার (শুধু অ্যাপ্রক্সিমেশন নয়) — একটি এক্স্যাক্ট কিন্তু এক্সপোনেনশিয়াল-ওয়ার্স্ট-কেস পদ্ধতি।