গ্রিড এবং ব্যাকট্র্যাকিং (Grid and Backtracking in C)
ব্যাকট্র্যাকিং একটি জনপ্রিয় কৌশল যা সমস্যা সমাধানে সম্ভাব্য বিকল্পগুলো পরীক্ষা করে এবং কোনো ভুল পদ্ধতিতে চলে গেলে পূর্ববর্তী সিদ্ধান্তে ফিরে গিয়ে নতুন পথ অনুসরণ করে। এটি সাধারনত সমস্যা সমাধানে এডহক বা প্রতিক্রিয়াশীল (trial and error) পদ্ধতিতে কাজ করে।
গ্রিড (Grid) হলো একটি 2D ম্যাট্রিক্স বা অ্যারে, যেখানে বিভিন্ন কোষের মধ্যে উপাদান থাকতে পারে। ব্যাকট্র্যাকিং অনেক সমস্যায় ব্যবহার হয়, যেমন মহামারি সমস্যা, কুইন সমস্যা, পাজল সমস্যা এবং পাথ খোঁজা ইত্যাদি, যেখানে একটি গ্রিড ব্যবহার করা হয়।
ব্যাকট্র্যাকিং এর ধারণা
ব্যাকট্র্যাকিং কৌশলটি সাধারণত সেগুলির জন্য ব্যবহৃত হয় যেখানে সম্ভাব্য সমাধানগুলো সিরিয়াল ভাবে চেক করা হয় এবং কোনো সমাধান ভুল হলে সেটি থেকে ফিরে আসা হয়। একে একটি ডেপথ ফার্স্ট অনুসন্ধান (DFS) পদ্ধতির মতো ভাবা যেতে পারে, যেখানে সম্ভাব্য সিদ্ধান্তগুলো পর্যালোচনা করে, যদি তা ভুল পথে নিয়ে যায় তবে তা বাতিল করা হয় এবং পূর্ববর্তী সিদ্ধান্তে ফিরে এসে অন্য সিদ্ধান্ত নেয়া হয়।
ব্যাকট্র্যাকিং ব্যবহার করে গ্রিড সমস্যা সমাধান
ধরা যাক, আমাদের একটি মহামারি (Maze) গ্রিড দেয়া হয়েছে এবং আমাদের লক্ষ্য হলো একটি নির্দিষ্ট পয়েন্ট থেকে অন্য পয়েন্টে পৌঁছানো, যেখানে কিছু বাধা বা দেয়াল রয়েছে। এই সমস্যা সমাধানে ব্যাকট্র্যাকিং কৌশল ব্যবহার করা যায়।
গ্রিড (Maze) সমস্যা সমাধান (Backtracking in C):
এখানে একটি উদাহরণ দেওয়া হলো যেখানে আমরা একটি মহামারি (maze) গ্রিডে চলার পাথ খুঁজে বের করবো।
#include <stdio.h>
#define N 4 // Grid size
// Function to print the grid
void printSolution(int sol[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", sol[i][j]);
}
printf("\n");
}
}
// A utility function to check if a cell (x, y) is valid and not blocked
int isSafe(int maze[N][N], int x, int y) {
if (x >= 0 && x < N && y >= 0 && y < N && maze[x][y] == 1)
return 1;
return 0;
}
// Backtracking function to solve the Maze problem
int solveMazeUtil(int maze[N][N], int x, int y, int sol[N][N]) {
// If we reach the destination (bottom-right corner), return true
if (x == N - 1 && y == N - 1) {
sol[x][y] = 1;
return 1;
}
// Check if maze[x][y] is valid
if (isSafe(maze, x, y)) {
// Mark the current cell as part of the solution path
sol[x][y] = 1;
// Move right
if (solveMazeUtil(maze, x + 1, y, sol))
return 1;
// Move down
if (solveMazeUtil(maze, x, y + 1, sol))
return 1;
// If none of the moves work, backtrack
sol[x][y] = 0;
return 0;
}
return 0;
}
// Function to solve the maze problem using backtracking
int solveMaze(int maze[N][N]) {
int sol[N][N] = {0}; // Solution matrix initialized to 0
if (solveMazeUtil(maze, 0, 0, sol) == 0) {
printf("Solution does not exist");
return 0;
}
printSolution(sol);
return 1;
}
int main() {
// Maze grid where 1 represents a path and 0 represents a wall
int maze[N][N] = {
{1, 0, 0, 0},
{1, 1, 0, 1},
{0, 1, 0, 0},
{1, 1, 1, 1}
};
// Solve the maze and print the solution path
solveMaze(maze);
return 0;
}ব্যাখ্যা:
- প্রথম ধাপ:
solveMazeফাংশনটি প্রথমে একটিsolution matrixতৈরি করে, যেখানে গ্রিডের প্রতিটি কোষের জন্য ১ বা ০ থাকবে (১ মানে গ্রিডের সেলটি সলিউশনে অংশ নিচ্ছে এবং ০ মানে অংশ নিচ্ছে না)। - ব্যাকট্র্যাকিং ফাংশন:
solveMazeUtilফাংশনটি হল ব্যাকট্র্যাকিংয়ের মূল অংশ। এটি গ্রিডের প্রতিটি কোষে চলার চেষ্টা করে এবং যদি সঠিক পাথ পায়, তবে ওই পাথকে সমাধান হিসেবে চিহ্নিত করে। - isSafe ফাংশন: এটি চেক করে যে কোনও কোষ (x, y) গ্রিডের মধ্যে রয়েছে এবং সেটি ওয়াল নয়, অর্থাৎ এটি একটি চলমান পাথ।
- পাথ খোঁজা: ব্যাকট্র্যাকিংয়ের মাধ্যমে ডান দিকে এবং নিচে চলে যাওয়ার চেষ্টা করা হয়, এবং যদি কোনো পাথ কার্যকর না হয়, তবে পূর্ববর্তী সিদ্ধান্তে ফিরে গিয়ে অন্য দিকে চলে যায় (ব্যাকট্র্যাকিং)।
আউটপুট:
1 0 0 0
1 1 0 0
0 1 0 0
0 1 1 1 এখানে 1 হল সলিউশন পাথ এবং 0 হল গ্রিডের অচল স্থান। আমরা প্রথমে গ্রিডের উপরের বাম (0,0) কোষ থেকে শুরু করি এবং সঠিক পাথ (গ্রিডের শেষ কোষে) পৌঁছানোর চেষ্টা করি।
ব্যাকট্র্যাকিং এর অন্যান্য উদাহরণ:
- N-Queens Problem:
- একটি ৮-কুইন সমস্যা সমাধান যেখানে ৮টি কুইনকে একটি ৮x৮ গ্রিডে এমনভাবে স্থাপন করতে হবে যে তারা একে অপরকে আক্রমণ না করে।
- Subset Sum Problem:
- একটি নির্দিষ্ট সংখ্যার সমষ্টি গঠনের জন্য যেসব উপাদান নির্বাচন করা যেতে পারে তা বের করা।
- Sudoku Solver:
- একটি সুডোকু পাজল সমাধান করার জন্য ব্যাকট্র্যাকিং ব্যবহৃত হয়।
সারসংক্ষেপ:
ব্যাকট্র্যাকিং একটি শক্তিশালী কৌশল যা বিভিন্ন গ্রিড বা ম্যাট্রিক্স ভিত্তিক সমস্যার সমাধানে ব্যবহৃত হয়। এটি বিভিন্ন সম্ভাব্য বিকল্প পরীক্ষা করে এবং ভুল পদ্ধতিতে চলে গেলে পূর্ববর্তী সিদ্ধান্তে ফিরে আসে এবং নতুন সিদ্ধান্ত নেয়। গ্রিড ভিত্তিক সমস্যাগুলোর মধ্যে মহামারি সমস্যা, N-Queens সমস্যা, Sudoku Solver, ইত্যাদি সমাধানে ব্যাকট্র্যাকিং কার্যকরভাবে ব্যবহার করা হয়।
Backtracking এর মৌলিক ধারণা
Backtracking একটি সমস্যা সমাধানের কৌশল যা অনুসন্ধান (search) এবং অন্তর্নিহিত সিদ্ধান্ত গ্রহণের (decision making) উপর ভিত্তি করে কাজ করে। এটি মূলত একটি রিকার্সিভ কৌশল, যা ধাপে ধাপে সমাধান খুঁজে বের করে এবং যদি কোনো ধাপে অগ্রগতি সম্ভব না হয়, তাহলে পূর্ববর্তী ধাপে ফিরে গিয়ে অন্য বিকল্প খোঁজে। একে "Trial and Error" পদ্ধতিরও বলা হয়, যেখানে ভুল সিদ্ধান্ত নেওয়ার পরে পূর্বের সিদ্ধান্তে ফিরে গিয়ে পুনরায় সঠিক পথ খোঁজা হয়।
Backtracking এর মৌলিক ধারণা:
- সমস্যার সম্ভাব্য সমাধান গাছ তৈরি করা:
- Backtracking মূলত সম্ভাব্য সমাধান গাছ (solution tree) তৈরি করে, যেখানে প্রতিটি স্তরে সমাধান সম্ভাব্য বিকল্পগুলো তৈরি হয়।
- যদি কোনো স্তরে কোনো সিদ্ধান্ত ভুল হয়ে যায় বা কোনো শর্ত পূর্ণ না হয়, তবে প্রক্রিয়াটি আগের স্তরে ফিরে যায় এবং অন্য বিকল্পের জন্য চেষ্টা করে।
- গাছের শাখা অনুসন্ধান:
- Backtracking গাছের শাখাগুলো অনুসন্ধান করে একে একে সমাধান বা বিকল্প অবস্থান খুঁজে বের করে।
- প্রাথমিক সিদ্ধান্ত গ্রহণ:
- সমাধানের জন্য প্রথমে কিছু প্রাথমিক সিদ্ধান্ত নেওয়া হয়, যা পরবর্তী সমাধান খুঁজে বের করার জন্য দরকারি হতে পারে। যদি কোনো সিদ্ধান্ত ভুল হয় বা তা কোন নির্দিষ্ট শর্ত পূর্ণ না করে, তখন পূর্বের সিদ্ধান্তে ফিরে যাওয়া হয় (এটি হল "backtrack" করার অংশ)।
- সমস্যার সীমাবদ্ধতা চিহ্নিত করা:
- Backtracking কে constraint satisfaction problems (CSPs) এর জন্য ব্যবহৃত হয়, যেখানে কিছু শর্ত বা সীমাবদ্ধতা থাকে, যা পূর্ণ হলে একটি সমাধান পাওয়া যায়। যদি কোনো সিদ্ধান্ত সীমাবদ্ধতা পূর্ণ না করে, তবে সেখানে ফিরে যাওয়া হয় এবং অন্য বিকল্প বিবেচনা করা হয়।
Backtracking এর প্রক্রিয়া:
- ধাপে ধাপে সমাধান প্রক্রিয়া:
- Backtracking সমস্যা সমাধানের জন্য প্রথমে একটি সম্ভাব্য সমাধান খোঁজে। যদি সেই সমাধান ভুল হয়, তখন এটি পূর্ববর্তী ধাপে ফিরে গিয়ে অন্য বিকল্পের চেষ্টা করে।
- Recursive Nature:
- Backtracking একটি রিকার্সিভ পদ্ধতি, যেখানে এটি সমস্যার সমাধান প্রক্রিয়াটি পুনরাবৃত্তি করে এবং একটি সিদ্ধান্তের ভুল হলে পূর্ববর্তী অবস্থায় ফিরে গিয়ে অন্য বিকল্প চেষ্টা করে।
- Decision Tree:
- এটি একটি গাছের মতো কাঠামো তৈরি করে যেখানে প্রতিটি শাখা একটি বিকল্প বা সিদ্ধান্তের প্রতিনিধিত্ব করে এবং শাখাগুলি "backtrack" হওয়ার আগে সম্ভাব্য সমাধানগুলো পরীক্ষা করা হয়।
Backtracking এর ব্যবহার:
- N-Queens Problem:
- এই সমস্যায় N x N আকারের একটি বোর্ডে N টি রানী বসানোর জন্য উপযুক্ত স্থান খোঁজা হয়, যাতে কোনো রানী অন্য রানীর সঙ্গে একই সারিতে, কলামে বা ডায়াগনালে না থাকে। এটি একটি ক্লাসিক Backtracking সমস্যা।
- Subset Sum Problem:
- একটি নির্দিষ্ট সংখ্যার সমষ্টি তৈরি করার জন্য একটি সিকোয়েন্সের সঠিক উপ-সেট খুঁজে বের করা।
- Graph Coloring:
- একটি গ্রাফে সঠিক রঙ (যাতে কোনো দুটি যুক্ত নোড একই রঙ না হয়) ব্যবহার করে গ্রাফ রঙ করা। এটি একটি জনপ্রিয় Backtracking সমস্যা।
- Maze Solving:
- একটি মেজ বা лабিরিন্থে থেকে বের হওয়ার পথ খোঁজা। Backtracking ব্যবহার করে একে একে পথ অনুসন্ধান করা হয় এবং যদি কোনো পথ ভুল হয়, তবে ফিরে আসা হয় এবং অন্য পথ অনুসন্ধান করা হয়।
- Hamiltonian Path and Cycle:
- একটি গ্রাফের মধ্যে এমন একটি পথ বা চক্র খুঁজে বের করা, যা গ্রাফের প্রতিটি নোড একবার করে পরিদর্শন করে। এটি একটি Backtracking সমস্যা।
- Sudoku Solver:
- একটি সঠিক Sudoku পাজল সমাধান করা যেখানে প্রতিটি কলাম, সারি এবং উপ-বক্সে ১ থেকে ৯ পর্যন্ত সংখ্যাগুলি একবার করে ব্যবহৃত হবে।
Backtracking এর উদাহরণ:
N-Queens Problem (Backtracking):
এখানে N রানী বসানোর জন্য Backtracking কৌশল ব্যবহার করা হয়েছে, যাতে কোনো রানী অন্য রানীর সাথে একই সারি বা কলামে না থাকে।
#include <stdio.h>
#define N 4 // Board size
int board[N][N];
// Function to print the chessboard
void printSolution() {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", board[i][j]);
}
printf("\n");
}
}
// Function to check if it's safe to place a queen at board[row][col]
int isSafe(int row, int col) {
// Check the column
for (int i = 0; i < row; i++) {
if (board[i][col] == 1) {
return 0;
}
}
// Check the diagonal
for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 1) {
return 0;
}
}
// Check the other diagonal
for (int i = row, j = col; i >= 0 && j < N; i--, j++) {
if (board[i][j] == 1) {
return 0;
}
}
return 1;
}
// Function to solve the N-Queens problem using Backtracking
int solveNQueens(int row) {
if (row >= N) {
return 1; // All queens are placed
}
for (int col = 0; col < N; col++) {
if (isSafe(row, col)) {
board[row][col] = 1; // Place the queen
// Recur to place the rest of the queens
if (solveNQueens(row + 1)) {
return 1;
}
// Backtrack if placing queen doesn't lead to a solution
board[row][col] = 0;
}
}
return 0; // No solution found
}
int main() {
// Initialize the board with 0s (empty spaces)
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
board[i][j] = 0;
}
}
if (solveNQueens(0)) {
printSolution();
} else {
printf("Solution does not exist\n");
}
return 0;
}Backtracking এর সুবিধা ও অসুবিধা:
সুবিধা:
- সহজ ধারণা: সমস্যার সমাধান একে একে ছোট ছোট অংশে বিভক্ত করা যায় এবং প্রতিটি অংশের জন্য সমাধান খোঁজা হয়।
- সর্বোত্তম সমাধান: Backtracking নিশ্চিত করে যে সঠিক সমাধানই পাওয়া যাবে, কারণ এটি সমস্ত সম্ভাব্য বিকল্প পরীক্ষা করে।
- জটিল সমস্যা সমাধান: এমন জটিল সমস্যার সমাধান যেখানে ঐতিহ্যবাহী অ্যালগরিদম কার্যকরী না, সেগুলির জন্য উপযুক্ত।
অসুবিধা:
- সময়সীমা (Time Complexity): Backtracking এর সময় জটিলতা অনেক সময় বেশি হতে পারে, কারণ এটি অনেক সম্ভাবনা পরীক্ষা করে।
- অপ্টিমাইজেশন প্রয়োজন: কিছু সমস্যায় প্রতিটি বিকল্প যাচাই না করে তাড়াতাড়ি সিদ্ধান্ত নেওয়া যায়, তবে Backtracking সেই অপটিমাইজেশনের জন্য আরো শক্তিশালী।
সারসংক্ষেপ:
Backtracking একটি শক্তিশালী এবং বিস্তৃত কৌশল, যা সম্ভাব্য সমাধান খুঁজে বের করার জন্য ধাপে ধাপে চেষ্টা করে এবং ভুল সিদ্ধান্ত হলে পূর্ববর্তী অবস্থায় ফিরে গিয়ে অন্য বিকল্প পরীক্ষা করে। এটি ন-রানী সমস্যা, গ্রাফ রঙকরণ, সুদোকু সমাধান, হ্যামিলটনিয়ান চক্র এবং অন্যান্য অনেক সমস্যায় ব্যবহৃত হয়।
N-Queens Problem এবং Maze Solving
N-Queens Problem এবং Maze Solving দুটি ক্লাসিক সমস্যা যা ব্যাকট্র্যাকিং (Backtracking) অ্যালগরিদম ব্যবহার করে সমাধান করা যেতে পারে। ব্যাকট্র্যাকিং একটি পদ্ধতি যা সম্ভাব্য সমাধান অনুসন্ধান করে এবং যেকোনো সময় যদি একটি সমাধান সঠিক না হয়, তখন সেটি পরিত্যাগ করে অন্য সম্ভাবনা খোঁজে।
১. N-Queens Problem
N-Queens Problem একটি গ্রিডে Nটি কুইন বসানোর সমস্যা, যেখানে কুইনগুলোর মধ্যে একে অপরকে আক্রমণ করতে পারবে না। অর্থাৎ, কোন দুটি কুইন একে অপরকে সারি, কলাম বা ডায়াগনালে আক্রমণ করতে পারবে না। এই সমস্যা সমাধানে ব্যাকট্র্যাকিং অ্যালগরিদম ব্যবহার করা হয়।
N-Queens Problem এর সমাধান পদ্ধতি:
- একটি N x N গ্রিড তৈরি করা হয়।
- প্রথম কলাম থেকে কুইন বসানো শুরু করা হয় এবং প্রতিটি কুইন বসানোর সময় চেক করা হয় যে তা অন্যান্য কুইনদের আক্রমণ করতে পারবে কি না।
- যদি কোনো কুইন বসানো সম্ভব না হয়, তবে ব্যাকট্র্যাকিং করা হয় (পূর্ববর্তী কুইনটি সরিয়ে নতুন অবস্থানে বসানো হয়)।
- যদি সফলভাবে Nটি কুইন বসানো যায়, তবে সমাধান পাওয়া যায়।
সি প্রোগ্রামে N-Queens Problem এর ইমপ্লিমেন্টেশন:
#include <stdio.h>
#include <stdbool.h>
#define N 8
// একটি ফাংশন যা বোঝাবে একটি নির্দিষ্ট কলাম থেকে কুইন বসানো যাবে কিনা
bool isSafe(int board[N][N], int row, int col) {
// একে অপরকে আক্রমণ না করার জন্য সারি, কলাম এবং ডায়াগনাল পরীক্ষা করা
for (int i = 0; i < col; i++) {
if (board[row][i] == 1) return false;
}
for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 1) return false;
}
for (int i = row, j = col; j >= 0 && i < N; i++, j--) {
if (board[i][j] == 1) return false;
}
return true;
}
// ব্যাকট্র্যাকিং ফাংশন
bool solveNQueens(int board[N][N], int col) {
if (col >= N) return true; // সমস্ত কুইন বসানো হয়ে গেলে
for (int i = 0; i < N; i++) {
if (isSafe(board, i, col)) {
board[i][col] = 1; // কুইন বসান
if (solveNQueens(board, col + 1)) {
return true; // সফলভাবে সমাধান পাওয়া গেছে
}
board[i][col] = 0; // ব্যাকট্র্যাকিং
}
}
return false; // সমাধান সম্ভব না হলে
}
// কুইন বসানো দেখানোর জন্য ফাংশন
void printSolution(int board[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", board[i][j]);
}
printf("\n");
}
}
int main() {
int board[N][N] = {0};
if (solveNQueens(board, 0)) {
printSolution(board);
} else {
printf("No solution exists\n");
}
return 0;
}ব্যাখ্যা:
- isSafe() ফাংশনটি চেক করে যে একটি কুইন নির্দিষ্ট সারি ও কলামে বসানো যাবে কি না।
- solveNQueens() ফাংশনটি ব্যাকট্র্যাকিংয়ের মাধ্যমে সমস্ত কুইন বসানোর চেষ্টা করে।
- printSolution() কুইনগুলি বসানোর পরে তাদের সমাধান প্রদর্শন করে।
২. Maze Solving Problem
Maze Solving একটি ক্লাসিক সমস্যা যেখানে একটি মেজ (পথ) থেকে একটি নির্দিষ্ট পয়েন্ট (এন্ট্রান্স) থেকে অন্য পয়েন্ট (এক্সিট) পর্যন্ত পৌঁছানোর উপায় খুঁজে বের করা হয়। এটি সাধারণত একটি 2D মেট্রিক্সে উপস্থাপন করা হয় যেখানে বিভিন্ন কোষের মান হয়:
0: খালি জায়গা (যেখানে চলাচল করা যায়)1: বাধা (যেখানে চলাচল করা যায় না)
মেজ সলভিং সমস্যার সমাধানে ব্যাকট্র্যাকিং বা DFS (Depth First Search) ব্যবহার করা হয়।
Maze Solving এর সমাধান পদ্ধতি:
- মেজের শুরুর পয়েন্ট থেকে একটি রিকার্সিভ ফাংশন কল করা হয়।
- প্রতিটি কোষের জন্য পরীক্ষা করা হয়, যদি কোষটি পাসযোগ্য (0) হয়, তাহলে সেখানে যেতে হবে।
- যদি কোষটি ভিজিট করা হয়ে থাকে বা বাধা থাকে, তাহলে ব্যাকট্র্যাকিং করা হয় এবং অন্য পথে যাওয়ার চেষ্টা করা হয়।
- এক্সিট পয়েন্টে পৌঁছালে সমাধান পাওয়া যায়।
সি প্রোগ্রামে Maze Solving এর ইমপ্লিমেন্টেশন:
#include <stdio.h>
#include <stdbool.h>
#define N 4
// মেজের কোষে চলার জন্য নির্দেশিকা
int rowNum[] = {-1, 1, 0, 0};
int colNum[] = {0, 0, -1, 1};
// মেজ সলভিং ফাংশন
bool isSafe(int maze[N][N], int x, int y, bool visited[N][N]) {
return (x >= 0 && x < N && y >= 0 && y < N && maze[x][y] == 0 && !visited[x][y]);
}
bool solveMazeUtil(int maze[N][N], int x, int y, bool visited[N][N]) {
// এক্সিট পয়েন্টে পৌঁছালে সমাধান পাওয়া যাবে
if (x == N - 1 && y == N - 1) {
visited[x][y] = true;
return true;
}
// যদি চলা সম্ভব হয়
if (isSafe(maze, x, y, visited)) {
visited[x][y] = true; // কোষটি ভিজিট করা হয়েছে
// উপরে চলা
if (solveMazeUtil(maze, x - 1, y, visited)) return true;
// নিচে চলা
if (solveMazeUtil(maze, x + 1, y, visited)) return true;
// বাম দিকে চলা
if (solveMazeUtil(maze, x, y - 1, visited)) return true;
// ডান দিকে চলা
if (solveMazeUtil(maze, x, y + 1, visited)) return true;
visited[x][y] = false; // ব্যাকট্র্যাকিং
return false;
}
return false;
}
void printSolution(bool visited[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", visited[i][j]);
}
printf("\n");
}
}
int main() {
int maze[N][N] = {
{0, 1, 0, 0},
{0, 1, 0, 1},
{0, 0, 0, 0},
{1, 1, 0, 0}
};
bool visited[N][N] = {false};
if (solveMazeUtil(maze, 0, 0, visited)) {
printf("Solution found:\n");
printSolution(visited);
} else {
printf("No solution exists\n");
}
return 0;
}ব্যাখ্যা:
- isSafe() ফাংশনটি চেক করে যে একটি নির্দিষ্ট কোষটি পাসযোগ্য (0) এবং আগে ভিজিট করা হয়নি।
- solveMazeUtil() ফাংশনটি রিকার্সিভভাবে মেজের মধ্যে চলাচল করে এবং এক্সিট পয়েন্টে পৌঁছানোর চেষ্টা করে।
- visited[][] অ্যারে ব্যাকট্র্যাকিংয়ের জন্য ব্যবহৃত হয়, যাতে কোষ পুনরায় ভিজিট না হয়।
সারসংক্ষেপ
- N-Queens Problem: একটি নির্দিষ্ট আকারের গ্রিডে Nটি কুইন বসানোর সমস্যা যেখানে কুইনগুলি একে অপরকে আক্রমণ করতে পারে না। এটি ব্যাকট্র্যাকিং ব্যবহার করে সমাধান করা হয়।
- Maze Solving: একটি মেজ (পথ) সমাধানের সমস্যা যেখানে শুরুর পয়েন্ট থেকে এক্সিট পয়েন্টে পৌঁছানোর পথ খোঁ
জা হয়। এটি ব্যাকট্র্যাকিং বা DFS পদ্ধতি ব্যবহার করে সমাধান করা হয়।
ব্যাকট্র্যাকিংয়ের মাধ্যমে উভয় সমস্যাই কার্যকরভাবে সমাধান করা যায়, কারণ এটি সম্ভাব্য সমাধানগুলো পরীক্ষার মাধ্যমে সঠিক সমাধান খুঁজে বের করে।
Backtracking এর প্রয়োগ: Sudoku Solver, Hamiltonian Path
Backtracking একটি জনপ্রিয় সমস্যা সমাধানের কৌশল, যা পরীক্ষার মাধ্যমে সঠিক সমাধান খোঁজার জন্য ব্যবহৃত হয়। এটি একটি ব্রুট ফোর্স পদ্ধতি, যা সমাধানের জন্য সম্ভাব্য সমস্ত বিকল্প পরীক্ষা করে এবং যদি একটি বিকল্প ভুল হয়, তবে সেগুলিকে বাদ দিয়ে পরবর্তী বিকল্পগুলো পরীক্ষা করে। এটি সাধারণত সমস্যা সমাধানে রিকার্সন বা স্ট্যাক ব্যবহার করে।
এখানে Backtracking এর দুটি জনপ্রিয় প্রয়োগ Sudoku Solver এবং Hamiltonian Path নিয়ে আলোচনা করা হবে।
1. Sudoku Solver - Backtracking
Sudoku একটি ক্লাসিক্যাল পাজল যেখানে একটি 9x9 গ্রিডে কিছু সংখ্যা দেওয়া থাকে এবং বাকী স্থানগুলোর সংখ্যা সম্পূর্ণ করতে হবে। সংখ্যাগুলি 1 থেকে 9 পর্যন্ত হতে হবে এবং প্রতিটি সারি, কলাম, এবং 3x3 গ্রিডে কোনো সংখ্যার পুনরাবৃত্তি থাকতে পারবে না।
Sudoku Solver - Backtracking ইমপ্লিমেন্টেশন (সি প্রোগ্রামিং):
#include <stdio.h>
#include <stdbool.h>
#define N 9
// Check if placing num in grid[row][col] is valid
bool isValid(int grid[N][N], int row, int col, int num) {
// Check if num is not repeated in row
for (int i = 0; i < N; i++) {
if (grid[row][i] == num) return false;
}
// Check if num is not repeated in column
for (int i = 0; i < N; i++) {
if (grid[i][col] == num) return false;
}
// Check if num is not repeated in the 3x3 grid
int startRow = row - row % 3, startCol = col - col % 3;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (grid[i + startRow][j + startCol] == num) return false;
}
}
return true;
}
// Function to solve the Sudoku puzzle using backtracking
bool solveSudoku(int grid[N][N]) {
int row, col;
// Find the next empty cell
bool emptyFound = false;
for (row = 0; row < N; row++) {
for (col = 0; col < N; col++) {
if (grid[row][col] == 0) {
emptyFound = true;
break;
}
}
if (emptyFound) break;
}
// If no empty cell is found, puzzle is solved
if (!emptyFound) return true;
// Try placing numbers 1 to 9
for (int num = 1; num <= 9; num++) {
if (isValid(grid, row, col, num)) {
grid[row][col] = num;
// Recursively attempt to solve the rest of the grid
if (solveSudoku(grid)) {
return true;
}
// If placing num didn't work, backtrack
grid[row][col] = 0;
}
}
return false; // Trigger backtracking
}
// Function to print the Sudoku grid
void printGrid(int grid[N][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
printf("%d ", grid[i][j]);
}
printf("\n");
}
}
int main() {
// A Sudoku puzzle with 0's representing empty cells
int grid[N][N] = {
{5, 3, 0, 0, 7, 0, 0, 0, 0},
{6, 0, 0, 1, 9, 5, 0, 0, 0},
{0, 9, 8, 0, 0, 0, 0, 6, 0},
{8, 0, 0, 0, 6, 0, 0, 0, 3},
{4, 0, 0, 8, 0, 3, 0, 0, 1},
{7, 0, 0, 0, 2, 0, 0, 0, 6},
{0, 6, 0, 0, 0, 0, 2, 8, 0},
{0, 0, 0, 4, 1, 9, 0, 0, 5},
{0, 0, 0, 0, 8, 0, 0, 7, 9}
};
if (solveSudoku(grid)) {
printf("Solved Sudoku:\n");
printGrid(grid);
} else {
printf("No solution exists\n");
}
return 0;
}Output Example:
Solved Sudoku:
5 3 4 6 7 8 9 1 2
6 7 2 1 9 5 3 4 8
1 9 8 3 4 2 5 6 7
8 5 9 7 6 1 4 2 3
4 2 6 8 5 3 7 9 1
7 1 3 9 2 4 8 5 6
9 6 1 5 3 7 2 8 4
2 8 7 4 1 9 6 3 5
3 4 5 2 8 6 1 7 92. Hamiltonian Path - Backtracking
Hamiltonian Path হলো এমন একটি পথ যা গ্রাফের প্রতিটি শীর্ষে একবার করে পরিদর্শন করে। Hamiltonian Cycle যদি শেষ শীর্ষে গিয়ে আবার প্রথম শীর্ষে ফিরে আসে, তবে এটি একটি সাইকেল হয়। Hamiltonian Path এর সমস্যায় একটি গ্রাফে এমন একটি পথ খুঁজতে হয় যা প্রতিটি শীর্ষে একবার এবং একমাত্র একবার যায়।
Hamiltonian Path - Backtracking ইমপ্লিমেন্টেশন (সি প্রোগ্রামিং):
#include <stdio.h>
#include <stdbool.h>
#define V 5 // Number of vertices in graph
// Check if current vertex can be added to the path
bool isSafe(int graph[V][V], int path[], int pos, int v) {
// Check if this vertex is an adjacent vertex of the last vertex in path
if (graph[path[pos - 1]][v] == 0) {
return false;
}
// Check if vertex v is already in the path
for (int i = 0; i < pos; i++) {
if (path[i] == v) {
return false;
}
}
return true;
}
// Function to solve the Hamiltonian Path problem using backtracking
bool hamCycleUtil(int graph[V][V], int path[], int pos) {
// If all vertices are included in the path, return true
if (pos == V) {
return true;
}
// Try different vertices as the next candidate in the path
for (int v = 1; v < V; v++) {
if (isSafe(graph, path, pos, v)) {
path[pos] = v;
// Recur to construct the rest of the path
if (hamCycleUtil(graph, path, pos + 1)) {
return true;
}
// Backtrack
path[pos] = -1;
}
}
return false;
}
// Function to solve the Hamiltonian Path problem
bool hamCycle(int graph[V][V]) {
int path[V];
// Initialize the path
for (int i = 0; i < V; i++) {
path[i] = -1;
}
// Start from vertex 0
path[0] = 0;
if (hamCycleUtil(graph, path, 1)) {
printf("Hamiltonian Path: ");
for (int i = 0; i < V; i++) {
printf("%d ", path[i]);
}
printf("\n");
return true;
}
printf("No Hamiltonian Path exists\n");
return false;
}
int main() {
// Example graph (adjacency matrix)
int graph[V][V] = {
{0, 1, 0, 1, 0},
{1, 0, 1, 1, 1},
{0, 1, 0, 1, 0},
{1, 1, 1, 0, 1},
{0, 1, 0, 1, 0}
};
if (!hamCycle(graph)) {
printf("No Hamiltonian Path exists\n");
}
return 0;
}Output Example:
Hamiltonian Path: 0 1 2 3 4**স
ারসংক্ষেপ**
- Sudoku Solver: Backtracking ব্যবহার করে একটি Sudoku পাজল সমাধান করা সম্ভব। এটি খুঁজে বের করে একটি সংখ্যার জন্য বৈধ স্থানে স্থানান্তর করার সম্ভাব্যতা পরীক্ষা করে এবং যদি কোনো স্থানে সংখ্যার পুনরাবৃত্তি থাকে, তবে তা ফিরে আসে এবং পরবর্তী বিকল্প পরীক্ষা করে।
- Hamiltonian Path: Backtracking ব্যবহার করে একটি গ্রাফের মধ্যে এমন একটি পথ খুঁজে বের করা যায় যা প্রতিটি শীর্ষ একবার করে পরিদর্শন করে। যদি কোনো পাথ পাওয়া না যায়, তবে এটি ব্যাকট্র্যাক করে এবং পরবর্তী সম্ভাব্য পাথ পরীক্ষা করে।
Backtracking এর মূল উদ্দেশ্য হলো সমস্যার সকল সম্ভাব্য সমাধান খোঁজা, এবং যদি কোনো সমাধান ভুল হয়, তবে তা বাদ দিয়ে পরবর্তী সমাধানে যাওয়ার জন্য পুনরাবৃত্তি করা।
গ্রিড ভিত্তিক সমস্যা সমাধান (Grid-Based Problem Solving Techniques)
গ্রিড ভিত্তিক সমস্যা সমাধান এমন সমস্যাগুলির সমাধান করতে ব্যবহৃত হয় যেখানে ডেটা বা উপাদানগুলি একটি ম্যাট্রিক্স (grid) বা ল্যাটিস (lattice)-এ সাজানো থাকে। এই ধরনের সমস্যাগুলি সাধারণত নেটওয়ার্কের পথ খোঁজা, সার্চ অ্যালগরিদম, মোশন প্ল্যানিং, এবং স্পেসিফিকেশন প্যাটার্ন মেচিং এর মধ্যে পড়ে।
গ্রিড ভিত্তিক সমস্যা সমাধানে প্রধানত ২D গ্রিড, ৩D গ্রিড, বা আরও উচ্চ মাত্রার গ্রিড ব্যবহার করা হয় এবং বিভিন্ন অ্যালগরিদমের সাহায্যে সমাধান বের করা হয়। এই ধরনের সমস্যা সমাধানের জন্য কয়েকটি সাধারণ কৌশল ব্যবহার করা হয়।
গ্রিড ভিত্তিক সমস্যা সমাধানের কৌশল:
১. Breadth-First Search (BFS)
- BFS গ্রিড ভিত্তিক সমস্যার মধ্যে এক বা একাধিক পয়েন্টের মধ্যে সবচেয়ে ছোট পথ খুঁজে বের করার জন্য ব্যবহৃত হয়। এটি সাধারণত Unweighted Grid বা Unweighted Graph এ সবচেয়ে ছোট পথ খুঁজে বের করতে কার্যকর।
- BFS কিউ ডেটা স্ট্রাকচার ব্যবহার করে, যেখানে প্রথমে নিকটতম নোডগুলো প্রক্রিয়া করা হয়।
BFS উদাহরণ:
গ্রিডে দুটি পয়েন্টের মধ্যে সবচেয়ে ছোট পথ খুঁজতে BFS ব্যবহার করা হয়।
#include <stdio.h>
#include <queue>
#include <vector>
#define N 5
#define M 5
using namespace std;
// Direction vectors to move in grid (up, down, left, right)
int row[] = {-1, 1, 0, 0};
int col[] = {0, 0, -1, 1};
bool isValid(int x, int y, int grid[N][M], bool visited[N][M]) {
return (x >= 0 && x < N && y >= 0 && y < M && grid[x][y] == 1 && !visited[x][y]);
}
void bfs(int grid[N][M], int startX, int startY) {
bool visited[N][M] = {false};
queue<pair<int, int>> q;
visited[startX][startY] = true;
q.push({startX, startY});
while (!q.empty()) {
pair<int, int> cell = q.front();
q.pop();
int x = cell.first, y = cell.second;
printf("Visited: (%d, %d)\n", x, y);
for (int i = 0; i < 4; i++) {
int newX = x + row[i], newY = y + col[i];
if (isValid(newX, newY, grid, visited)) {
visited[newX][newY] = true;
q.push({newX, newY});
}
}
}
}
int main() {
int grid[N][M] = {
{1, 0, 1, 1, 1},
{1, 1, 0, 0, 1},
{1, 1, 1, 1, 0},
{0, 1, 0, 1, 1},
{1, 1, 1, 0, 1}
};
bfs(grid, 0, 0); // Start BFS from (0, 0)
return 0;
}২. Depth-First Search (DFS)
- DFS ব্যবহার করে গ্রিডের মাধ্যমে ডিপলি (গভীরভাবে) ট্রাভার্সাল করা হয়। এটি সাধারণত backtracking প্রয়োগে ব্যবহৃত হয়, যেখানে একটি নির্দিষ্ট পাথ অনুসন্ধান করা হয় এবং শাখা সঠিক না হলে পুনরায় আগের ধাপে ফিরে আসা হয়।
DFS উদাহরণ:
গ্রিডে একটি নির্দিষ্ট পয়েন্ট থেকে শুরু করে সকল সেল অনুসন্ধান করতে DFS ব্যবহার করা হয়।
#include <stdio.h>
#define N 5
#define M 5
using namespace std;
// Direction vectors to move in grid (up, down, left, right)
int row[] = {-1, 1, 0, 0};
int col[] = {0, 0, -1, 1};
bool isValid(int x, int y, int grid[N][M], bool visited[N][M]) {
return (x >= 0 && x < N && y >= 0 && y < M && grid[x][y] == 1 && !visited[x][y]);
}
void dfs(int x, int y, int grid[N][M], bool visited[N][M]) {
visited[x][y] = true;
printf("Visited: (%d, %d)\n", x, y);
for (int i = 0; i < 4; i++) {
int newX = x + row[i], newY = y + col[i];
if (isValid(newX, newY, grid, visited)) {
dfs(newX, newY, grid, visited);
}
}
}
int main() {
int grid[N][M] = {
{1, 0, 1, 1, 1},
{1, 1, 0, 0, 1},
{1, 1, 1, 1, 0},
{0, 1, 0, 1, 1},
{1, 1, 1, 0, 1}
};
bool visited[N][M] = {false};
dfs(0, 0, grid, visited); // Start DFS from (0, 0)
return 0;
}৩. A Algorithm*
- A (A-star)* একটি জনপ্রিয় সোনালী মানের (optimal) পথ খোঁজার অ্যালগরিদম যা BFS এবং greedy best-first search এর সংমিশ্রণ। এটি গ্রিড ভিত্তিক পথে সবচেয়ে ছোট এবং কার্যকরী রুট খোঁজে, যেখানে একটি heuristic (ধারণাগত) ফাংশন সাহায্যে ভবিষ্যৎ পয়েন্টের জন্য আনুমানিক খরচ হিসাব করা হয়।
৪. Dijkstra's Algorithm
- Dijkstra's Algorithm গ্রিড ভিত্তিক সমস্যা সমাধানের জন্য ব্যবহৃত হয়, যেখানে সোর্স থেকে অন্য নোড পর্যন্ত সর্বনিম্ন খরচের পথ বের করা হয়। এটি একটি non-negative weighted grid এর জন্য ব্যবহার করা যায়।
৫. Greedy Best-First Search
- Greedy Best-First Search পদ্ধতি সবচেয়ে দ্রুত পদ্ধতিতে যে কোন গ্রিডের মধ্যে একটি লক্ষ্য বা গন্তব্যস্থলে পৌঁছাতে সাহায্য করে। এটি সবচেয়ে কাছের (shortest estimated distance) নোডটি প্রথমে নির্বাচন করে।
Grid-Based Problem Solving Techniques এর অ্যাপ্লিকেশন:
- Pathfinding:
- গ্রিড ভিত্তিক সমস্যা সমাধান প্রধানত পথ খোঁজা (Pathfinding) সমস্যার সমাধান করতে ব্যবহৃত হয়, যেমন robot movement, gaming environments (e.g., moving a character in a grid-based game), map routing, ইত্যাদি।
- Game Development:
- 2D বা 3D গ্রিডে চরিত্রের চলাচল, টেরিটোরি ম্যানেজমেন্ট, এবং শত্রুদের পিছু ধরা।
- Robotics and Navigation:
- রোবটের জন্য পথ খোঁজা এবং অটোনোমাস ড্রাইভিং সিস্টেমে রাস্তার পথ নির্ধারণে ব্যবহৃত হয়।
- Image Processing:
- গ্রিড ভিত্তিক সমস্যার মধ্যে এক ধরনের সমস্যা হল image segmentation এবং pattern recognition, যেখানে প্রতিটি পিক্সেল একটি গ্রিড সেল হিসেবে দেখা হয়।
- Network Flow Problems:
- গ্রিড ভিত্তিক নেটওয়ার্ক ফ্লো সমস্যা যেমন maximum flow বা minimum cut সমস্যার সমাধানে ব্যবহৃত হয়।
সারসংক্ষেপ
গ্রিড ভিত্তিক সমস্যা সমাধান কৌশলগুলি শক্তিশালী এবং ব্যবহারিক, যেখানে সাধারণত গ্রিডের উপাদানগুলির মধ্যে একযোগিতার মাধ্যমে সমস্যাগুলির সমাধান করা হয়। জনপ্রিয় কৌশলগুলো যেমন BFS, DFS, A* Algorithm, Dijkstra’s Algorithm, ইত্যাদি গ্রিড ভিত্তিক সমস্যা সমাধান এবং রুট নির্ধারণের জন্য কার্যকর।
Read more