ব্যাকট্র্যাকিং (Backtracking)
ধরুন আপনি একটি গোলকধাঁধা বা মেজ (maze) সমাধান করছেন। আপনি একটি পথ ধরে হাঁটলেন, বাঁয়ে ঘুরলেন, আরেকটু হাঁটলেন, এবং একটি দেয়ালে গিয়ে আটকে গেলেন। আপনি কিন্তু হাল ছেড়ে দেন না — আপনি ব্যাকট্র্যাক (backtrack) করে আবার আগের সংযোগস্থলে বা জংশনে ফিরে আসেন এবং এরপরের সম্ভাব্য পথটি চেষ্টা করেন। আপনি পদ্ধতিগতভাবে এটি করতেই থাকেন, যতক্ষণ না পর্যন্ত আপনি বের হওয়ার পথটি খুঁজে পান অথবা অন্তত এটুকু নিশ্চিত হন যে এখানে কোনো বের হওয়ার পথ নেই।
সংক্ষেপে এটিই হলো ব্যাকট্র্যাকিং (backtracking): একটু একটু করে একটি সমাধান তৈরি করা, এবং যখনই সেই আংশিক সমাধানটি কোনো শর্ত বা নিয়ম ভঙ্গ করে, ঠিক তক্ষুনি সেই পুরো ব্রাঞ্চ বা শাখাকে বাতিল করে দেওয়া এবং অন্য কোনো উপায় চেষ্টা করে দেখা। ডেড এন্ড বা অন্ধগলির নিচের দিকে আর কী আছে তা অন্বেষণ করার কোনো প্রয়োজন নেই।
তিন-ধাপের মূল ছন্দ (The three-step heartbeat)
প্রতিটি ব্যাকট্র্যাকিং অ্যালগরিদম ঠিক একই ছন্দে কাজ করে:
১. বাছাই করা (Choose) — বর্তমান স্লটের বা অবস্থার জন্য একটি উপযুক্ত মান বেছে নিন (যেমন: রানীর জন্য কলাম, সুডোকু ডিজিট, বা পরবর্তী যুক্ত করার আইটেম)।
২. অন্বেষণ করা (Explore) — যদি এই বাছাই হওয়া মানটি কোনো শর্ত ভঙ্গ না করে, তবে পরবর্তী স্লটের জন্য সেটিকে রিকার্স (recurse) বা পুনরাবৃত্তি করুন।
৩. আগের অবস্থায় ফেরা (Undo) — যখন রিকার্সনটি রিটার্ন করে (সেটি সমাধান খুঁজে পাক বা ডেড এন্ড বা অন্ধগলিতে পৌঁছাক), তখন আপনার বাছাই করা মানটিকে আবার মুছে নিয়ে আগের অবস্থায় ফিরে আসুন এবং এরপরের সম্ভাব্য মানটি নিয়ে চেষ্টা করুন। মূলত এই রিস্টোর (restore) বা পূর্বাবস্থায় ফিরে আসার ধাপটির জন্যই এর নামকরণ ব্যাকট্র্যাকিং করা হয়েছে।
এই "আগের অবস্থায় ফেরা বা আনডু (undo)" অংশটি অত্যন্ত গুরুত্বপূর্ণ। এটি ছাড়া, একটি ব্রাঞ্চ বা শাখার সিদ্ধান্ত অন্য শাখায় মিশে যাবে এবং পুরো সার্চিং প্রক্রিয়াটিকে নষ্ট করে দেবে।
এন-কুইন্স (N-Queens) — একটি ক্লাসিক উদাহরণ
একটি n×n সাইজের দাবার বোর্ডে n সংখ্যক রানী এমনভাবে স্থাপন করুন যাতে কোনো দুজন রানী একে অপরকে আক্রমণ করতে না পারে। এর চমৎকার কৌশলটি হলো: রানীগুলোকে সারি অনুযায়ী (row by row) স্থাপন করুন। r সারিতে, প্রতিটি কলাম c চেষ্টা করুন। যদি আগে থেকে থাকা কোনো রানী c কলাম, বাঁদিকের ডায়াগোনাল বা কর্ণ, অথবা ডানদিকের ডায়াগোনাল বা কর্ণ শেয়ার না করে থাকে — তবে সেখানে রানীকে বসান এবং r+1 সারির জন্য আবার রিকার্স (recurse) করুন। আর এরপর যখন আপনি ফিরে আসবেন (return), তখন আপনি সেই রানীটিকে সরিয়ে ফেলবেন এবং c+1 কলামের জন্য চেষ্টা করবেন।
n=8 এর ক্ষেত্রে প্রায় ১৬ মিলিয়ন (\(8^8\)) সাধারণ কম্বিনেশন বা সংমিশ্রণ রয়েছে, কিন্তু প্রুনিং (pruning)-এর মাধ্যমে অ্যালগরিদমটি শুধুমাত্র প্রায় ১৫,০০০টি নোড ভিজিট করে — যা ১০০০ গুণ দ্রুতগতি (1000× speedup) সম্পন্ন।
সুডোকু (Sudoku)
প্রতিটি ফাঁকা ঘরের জন্য, ১-৯ পর্যন্ত ডিজিট বা সংখ্যা বসানোর চেষ্টা করুন। একই সারি, কলাম বা ৩×৩ বক্সে ইতিমধ্যে রয়েছে এমন যেকোনো সংখ্যা এড়িয়ে যান। যদি কোনো সংখ্যা সঠিকভাবে মিলে যায়, তবে সেটিকে পূরণ করুন এবং পরবর্তী ফাঁকা ঘরটির জন্য রিকার্স করুন। কিন্তু যদি কোনো সংখ্যাই না মেলে, তবে ফলস (false) রিটার্ন করুন এবং কলারকে (caller) ব্যাকট্র্যাক করার সুযোগ দিন।
এর সাথে কনস্ট্রেইন্ট প্রোপাগেশন (constraint propagation) যুক্ত করুন — একটি ডিজিট বসানোর পর, তৎক্ষণাৎ সেটিকে অন্য সমস্ত পিয়ার সেল (peer cell) বা সংলগ্ন ঘরগুলোর সম্ভাব্য তালিকা থেকে সরিয়ে ফেলুন — এবং এর ফলে দেখবেন বেশিরভাগ সুডোকু পাজল দশটারও কম মোট ব্যাকট্র্যাক ব্যবহার করে সমাধান করা যায়।
বিন্যাস (Permutations) এবং সাবসেট (Subsets)
ব্যাকট্র্যাকিং খুব স্বাভাবিকভাবেই সমস্ত পারমুটেশন বা বিন্যাস এবং সমস্ত সাবসেট তৈরি করে (প্রতিটি পজিশনে, অব্যবহৃত বা আনইউজড প্রতিটি এলিমেন্ট চেষ্টা করুন, সেটিকে ব্যবহৃত হিসেবে চিহ্নিত করুন, রিকার্স করুন, আবার অব্যবহৃত চিহ্নিত করুন) এবং সমস্ত সাবসেট তৈরি করে (প্রতিটি এলিমেন্টে, হয় যুক্ত করুন অথবা বাদ দিন এরকম ব্রাঞ্চ তৈরি করুন)। "চলমান যোগফল ইতিমধ্যেই টার্গেট বা লক্ষ্যমাত্রা অতিক্রম করেছে" — এরকম প্রুনিং শর্তগুলো পুরো ব্রাঞ্চকে শুরুতেই বাতিল বা বাদ দিয়ে দিতে সাহায্য করে।
এন-কুইন্স (N-Queens) — সকল সমাধান
Complexity
ছোট কুইজ
পড়া চালিয়ে যান