Graphs
60. What is a graph, and how is it represented in code?
Graph হলো একটি non-linear data structure, যা একগুচ্ছ vertices (নোড) এবং তাদের মধ্যে সংযোগকারী edges নিয়ে গঠিত। Tree-এর মতো এখানে কোনো strict hierarchy বা root থাকে না — যেকোনো vertex, যেকোনো অন্য vertex-এর সাথে সংযুক্ত থাকতে পারে, এমনকি cycle তৈরি হতে পারে।
Real-world examples:
- Social network: user হলো vertex, friendship/follow হলো edge
- Google Maps: city/intersection হলো vertex, road হলো edge
- Computer network: router/server হলো vertex, connection হলো edge
- Course prerequisite: course হলো vertex, prerequisite relation হলো directed edge
Basic terms:
- Vertex/Node: graph এর একটা point/entity
- Edge: দুইটা vertex এর connection
- Degree: কোনো vertex এর সাথে কতগুলো edge connected
- Path: এক vertex থেকে আরেক vertex এ যাওয়ার sequence
- Cycle: কোনো path আবার starting vertex এ ফিরে আসে
- Connected graph: যেকোনো node থেকে অন্য node এ যাওয়া যায়
- Directed Graph: Edge-গুলোর একটি নির্দিষ্ট দিক (direction) থাকে (যেমন
A → B) - Undirected Graph: Edge-গুলোর কোনো দিক থাকে না (যেমন
A - B, দুইদিকেই যাওয়া যায়) - Weighted Graph: প্রতিটি edge-এর সাথে একটি weight/cost যুক্ত থাকে
- Unweighted Graph: সব edge-এর গুরুত্ব সমান ধরা হয়
Example graph:
PreviewClick to view details
0 ----- 1
| / |
| / |
2 ----- 3
Vertices: 0, 1, 2, 3
Edges: (0,1), (0,2), (1,2), (1,3), (2,3)
Graph সাধারণত দুইভাবে represent করা হয়:
- Adjacency Matrix
- Adjacency List
What is the difference between an adjacency matrix and an adjacency list?
একটি 2D matrix (আকার V x V, যেখানে V = vertex সংখ্যা) ব্যবহার করে vertex গুলোর মধ্যে connection represent করা হয়। যদি i এবং j vertex-এর মধ্যে edge থাকে, তাহলে matrix[i][j] = 1 (অথবা weight), নাহলে 0।
PreviewClick to view details
Graph:
0 -- 1
| |
2 -- 3
Adjacency Matrix:
0 1 2 3
0: 0 1 1 0
1: 1 0 0 1
2: 1 0 0 1
3: 0 1 1 0
Code ExampleC++Click to view details
#include <bits/stdc++.h>
using namespace std;
int main() {
int n = 4;
vector<vector<int>> matrix(n, vector<int>(n, 0));
vector<pair<int, int>> edges = {
{0, 1}, {0, 2}, {1, 3}, {2, 3}
};
for (auto [u, v] : edges) {
matrix[u][v] = 1;
matrix[v][u] = 1; // undirected graph
}
cout << "Is there an edge between 0 and 2? ";
cout << (matrix[0][2] ? "yes" : "no") << endl;
return 0;
}
Adjacency List এ প্রতিটি vertex এর neighbor list store করা হয়।
Adjacency List:
0: 1, 2
1: 0, 3
2: 0, 3
3: 1, 2
Code ExampleC++Click to view details
#include <bits/stdc++.h>
using namespace std;
int main() {
int n = 4;
vector<vector<int>> adj(n);
vector<pair<int, int>> edges = {
{0, 1}, {0, 2}, {1, 3}, {2, 3}
};
for (auto [u, v] : edges) {
adj[u].push_back(v);
adj[v].push_back(u); // undirected graph
}
for (int u = 0; u < n; u++) {
cout << u << ": ";
for (int v : adj[u]) {
cout << v << " ";
}
cout << endl;
}
return 0;
}
What are the trade-offs of each representation in terms of space and time for different operations?
| Operation/Feature | Adjacency Matrix | Adjacency List |
|---|---|---|
| Space | O(V^2) | O(V + E) |
Check edge u-v | O(1) | O(degree(u)) |
| Iterate neighbors | O(V) | O(degree(u)) |
| Sparse graph | memory waste বেশি | best choice |
| Dense graph | acceptable | also works |
| Weighted graph | weight store করা easy | pair/struct দিয়ে weight store |
Rule of thumb:
- Graph sparse হলে adjacency list use করা ভালো
- Edge existence বারবার check করতে হলে adjacency matrix convenient
- Most interview graph problems এ adjacency list preferred
Weighted adjacency list:
int n = 5;
vector<vector<pair<int, int>>> adj(n); // {neighbor, weight}
adj[0].push_back({1, 10});
adj[0].push_back({2, 5});
61. What is the difference between a directed graph and an undirected graph?
Undirected graph এ edge দুই দিকে কাজ করে। যদি u এবং v connected হয়, তাহলে u থেকে v এবং v থেকে u দুই দিকেই যাওয়া যায়।
PreviewClick to view details
Undirected:
0 ----- 1
Meaning:
0 -> 1
1 -> 0
বাস্তব উদাহরণ:
- Facebook Friendship: A, B-এর friend হলে B-ও A-এর friend
- Road Network (two-way road-এর ক্ষেত্রে): দুই দিকেই যাতায়াত সম্ভব
- Computer Network: দুইটি device-এর মধ্যে direct connection
Directed graph এ edge এর direction থাকে। u -> v থাকলে u থেকে v যাওয়া যায়, কিন্তু v থেকে u যাওয়া যাবে এমন guarantee নেই।
Directed:
0 ----> 1
Meaning:
0 -> 1 only
বাস্তব উদাহরণ:
- Twitter/Instagram Follow System: A, B-কে follow করলেও B, A-কে follow নাও করতে পারে
- Web Page Links: Page A-তে Page B-এর link থাকলেই B-তে A-এর link থাকা বাধ্যতামূলক নয়
- Task Dependency (DAG): Task A সম্পন্ন হওয়ার পর Task B শুরু হবে
Code difference:
Code ExampleC++Click to view details
// Undirected edge
adj[u].push_back(v);
adj[v].push_back(u);
// Directed edge
adj[u].push_back(v);
| বিষয় | Directed Graph | Undirected Graph |
|---|---|---|
| Edge direction | থাকে | থাকে না |
| Example | prerequisites, web links | friendship, road map |
| Cycle detection | recursion stack/color লাগে | parent tracking লাগে |
| Degree | in-degree, out-degree | single degree |
What is a weighted graph, and how does it change traversal algorithms?
Weighted graph এ প্রতিটি edge এর সাথে একটা cost/weight থাকে।
0 --(5)-- 1
| |
(2) (7)
| |
2 --(3)-- 3
Weight distance, cost, time, latency, risk, capacity ইত্যাদি represent করতে পারে।
Unweighted graph এ shortest path বের করতে BFS enough, কারণ সব edge এর cost same ধরা হয়। Weighted graph এ shortest path এর জন্য edge weight consider করতে হয়।
Common algorithms:
- Non-negative weights: Dijkstra
- Negative weights allowed: Bellman-Ford
- All-pairs shortest path: Floyd-Warshall
int n = 4;
vector<vector<pair<int, int>>> adj(n);
adj[0].push_back({1, 5}); // 0 -> 1, weight 5
adj[0].push_back({2, 2}); // 0 -> 2, weight 2
কীভাবে এটি Traversal Algorithm পাল্টায়:
| বিষয় | Unweighted Graph | Weighted Graph |
|---|---|---|
| Shortest Path Algorithm | BFS যথেষ্ট (কারণ প্রতিটি edge-এর cost সমান, তাই fewest edges = shortest path) | BFS কাজ করে না, বরং Dijkstra's Algorithm (non-negative weight) বা Bellman-Ford (negative weight থাক লে) লাগে |
| Data Structure ব্যবহার | সাধারণ Queue (FIFO) | Priority Queue (Min-Heap) ব্যবহার করতে হয়, যাতে সবসময় সবচেয়ে কম cost-এর path আগে explore হয় |
| "নিকটতম" node নির্ধারণ | edge সংখ্যা (level) দিয়ে নির্ধারিত হয় | সঞ্চিত total weight/cost দিয়ে নির্ধারিত হয় |
| MST (Minimum Spanning Tree) | প্রযোজ্য নয় (সব edge সমান হলে MST-এর কোনো মানে থাকে না) | Prim's বা Kruskal's Algorithm ব্যবহার করে ন্যূনতম total weight-এর spanning tree বের করা হয় |
MST precision: Unweighted graph-কেও সব edge-এর weight
1ধরে MST define করা যায়। তখন connected graph-এর যেকোনো spanning tree-এর total weightV-1, তাই optimizationটি trivial হলেও MST “প্রযোজ্য নয়” নয়।
What is a multigraph, and where might it appear in real systems?
Multigraph হলো এমন একটি graph, যেখানে দুইটি নির্দিষ্ট vertex-এর মধ্যে একাধিক (multiple) edge থাকতে পারে। সাধারণ (simple) graph-এ দুই vertex-এর মধ্যে সর্বোচ্চ একটি edge থাকে, কিন্তু multigraph-এ এই নিয়ম শিথিল।
A ═══ B (A ও B-এর মধ্যে ৩টি আলাদা edge)
╲╱
╱╲
Real-world examples:
- দুই city এর মধ্যে multiple roads/routes
- network এ multiple physical links
- airline graph এ same city pair এর multiple flights
- transaction graph এ একই account pair এর মধ্যে multiple transfers
Adjacency list multigraph naturally support করে, কারণ same neighbor multiple times store করা যায়।
vector<vector<pair<int, int>>> adj(2);
adj[0].push_back({1, 10}); // route 1 cost 10
adj[0].push_back({1, 20}); // route 2 cost 20
62. What are BFS and DFS, and when would you use each?
BFS হলো একটি graph traversal algorithm, যা level-by-level (breadth-wise) ভাবে graph explore করে। এটি প্রথমে source node-এর সব immediate neighbor visit করে, তারপর তাদের neighbor-দের visit করে — এভাবে "ঢেউ" (wave) আকারে বাইরের দিকে ছড়িয়ে পড়ে। Data Structure: Queue (FIFO)
DFS হলো একটি graph traversal algorithm, যা একটি path ধরে যতদূর সম্ভব গভীরে (depth-wise) চলে যায়, তারপর backtrack করে অন্য path explore করে। Data Structure: Stack (recursive call stack অথবা explicit stack)
Code ExampleC++Click to view details
// Recursive DFS
void dfs(int curr, vector<list<int>>& adjList, vector<bool>& visited) {
visited[curr] = true;
// curr node process করা
for (int neighbor : adjList[curr]) {
if (!visited[neighbor]) {
dfs(neighbor, adjList, visited);
}
}
}
কখন কোনটি ব্যবহার করবেন
| ব্যবহারের ক্ষেত্র | উপযুক্ত Algorithm | কারণ |
|---|---|---|
| Shortest Path (Unweighted Graph) | BFS | Level-by-level explore করায় প্রথমবার যখন target-এ পৌঁছায়, সেটাই shortest path নিশ্চিত করে |
| Connected Components খুঁজে বের করা | DFS (বা BFS, দুটোই সমানভাবে কাজ করে) | পুরো component একবারে explore করার জন্য উপযোগী |
| Cycle Detection | DFS | Recursion stack track করে back edge সহজে ধরা যায় |
| Topological Sort | DFS | Post-order finish time ব্যবহার করে ordering পাওয়া যায় |
| Maze/Puzzle Solving (একটি path খুঁজে বের করা) | DFS | কম memory লাগে, deep path দ্রুত explore করে |
| Level-order প্রয়োজন হলে (যেমন social network-এ "k-degree connection") | BFS | স্বাভাবিকভাবেই level ধরে ধরে কাজ করে |
| Minimum Spanning Tree, Network Flow-related Problem | DFS/BFS উভয়ই ব্যবহৃত হয় | Algorithm-ভেদে ভিন্ন (Prim's-এ BFS-এর মতো priority queue, Kruskal's-এ Union-Find) |
What is the time and space complexity of BFS and DFS?
| বিষয় | BFS | DFS |
|---|---|---|
| Time Complexity | O(V + E) | O(V + E) |
| Space Complexity (Adjacency List) | O(V) — queue-তে worst case সব vertex থাকতে পারে | O(V) — recursion stack (বা explicit stack) worst case সব vertex ধারণ করতে পারে |
| Space Complexity (Adjacency Matrix) | O(V) | O(V) |
- V = vertex সংখ্যা, E = edge সংখ্যা
- Time O(V+E) কারণ: প্রতিটি vertex ঠিক একবার visit হয় (O(V)), এবং প্রতিটি edge-ও একবার (undirected হলে দুইবার, কিন্তু constant factor) explore হয় (O(E))
- Space নির্ভর করে graph-এর shape-এর উপর:
- BFS-এ worst case space লাগে যখন graph-এর width বেশি (যেমন একটি node-এর অনেক neighbor)
- DFS-এ worst case space লাগে যখন graph-এর depth বেশি ( যেমন একটি লম্বা chain)
How is BFS used to find the shortest path in an unweighted graph?
BFS স্বভাবতই level-by-level explore করে, তাই যখন কোনো node প্রথমবার visit/discover করা হয়, সেই মুহূর্তেই তার shortest distance (source থেকে edge সংখ্যায়) নিশ্চিত হয়ে যায় — কারণ BFS কখনো কোনো node-এ পরে কম distance দিয়ে পৌঁছাতে পারে না (unweighted graph-এ)।
Code ExampleC++Click to view details
vector<int> bfsShortestPath(int source, vector<list<int>>& adjList, int n) {
vector<int> dist(n, -1); // -1 মানে unreachable
queue<int> q;
dist[source] = 0;
q.push(source);
while (!q.empty()) {
int curr = q.front();
q.pop();
for (int neighbor : adjList[curr]) {
if (dist[neighbor] == -1) { // এখনো visit হয়নি
dist[neighbor] = dist[curr] + 1;
q.push(neighbor);
}
}
}
return dist; // প্রতিটি node-এর জন্য shortest distance (edge count)
}
কেন এটি সঠিক Shortest Path দেয়:
A
/ \
B C
| |
D---E
A থেকে BFS চালালে:
- Level 0:
A(distance = 0) - Level 1:
B, C(distance = 1) — একই সাথে discover হয় - Level 2:
D, E(distance = 2) —D,Bথেকে আসতে পারে অথবাE,Cথেকে, কিন্তু উভয়ই distance 2-তে discover হবে
যেহেতু BFS একটি queue (FIFO) ব্যবহার করে সব node একই level-এ একসাথে process করে, তাই কোনো node-এ আগে যে distance দিয়ে পৌঁছানো হয়, সেটিই সবসময় ন্যূনতম (shortest) — এরপর যদি সেই node-এ আবার পৌঁছানোর চেষ্টা করা হয় (longer path দিয়ে), তা simply ignore করা হয় (dist[neighbor] == -1 check-এর মাধ্যমে)।
Path Reconstruction (actual path বের করতে চাইলে):
Code ExampleC++Click to view details
vector<int> parent(n, -1);
// BFS-এর মধ্যেই parent track করা:
if (dist[neighbor] == -1) {
dist[neighbor] = dist[curr] + 1;
parent[neighbor] = curr;
q.push(neighbor);
}
// Target থেকে backtrack করে path বের করা:
vector<int> getPath(int target, vector<int>& parent) {
vector<int> path;
for (int curr = target; curr != -1; curr = parent[curr]) {
path.push_back(curr);
}
reverse(path.begin(), path.end());
return path;
}
⚠️ গুরুত্বপূর্ণ Limitation: BFS শুধুমাত্র unweighted graph-এ shortest path দেয়। Weighted graph-এ shortest path বের করতে হলে Dijkstra's Algorithm (priority queue সহ BFS-এর একটি variant) প্রয়োজন।
How is DFS used to detect connected components?
Connected Component হলো একটি graph-এর এমন একটি subgraph, যেখানে যেকোনো দুইটি vertex-এর মধ্যে একটি path আছে, কিন্তু সেই subgraph-এর বাইরের কোনো vertex-এর সাথে সংযোগ নেই।
মূল Logic:
প্রতিটি unvisited vertex থেকে একটি নতুন DFS শুরু করা হয়। একটি সম্পূর্ণ DFS call, একটি সম্পূর্ণ connected component-কে cover করে (যেহেতু DFS তার reachable সব vertex visit করে ফেলে)। যতবার নতুন DFS শুরু করতে হয়, ততগুলোই connected component।
Code ExampleC++Click to view details
void dfsUtil(int curr, vector<list<int>>& adjList, vector<bool>& visited, vector<int>& component) {
visited[curr] = true;
component.push_back(curr);
for (int neighbor : adjList[curr]) {
if (!visited[neighbor]) {
dfsUtil(neighbor, adjList, visited, component);
}
}
}
vector<vector<int>> findConnectedComponents(vector<list<int>>& adjList, int n) {
vector<bool> visited(n, false);
vector<vector<int>> allComponents;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
vector<int> component;
dfsUtil(i, adjList, visited, component);
allComponents.push_back(component);
}
}
return allComponents;
}
এই implementation undirected graph-এর connected components-এর জন্য। Directed graph-এ direction ignore করে weak components, অথবা Tarjan/Kosaraju দিয়ে strongly connected components আলাদাভাবে বের করতে হয়।
Component 1: 0 - 1 Component 2: 3 - 4 Component 3: 5
|
2
Code ExampleC++Click to view details
// Graph structure:
// 0-1, 1-2, 0-2 (Component 1: {0,1,2})
// 3-4 (Component 2: {3,4})
// 5-এর কোনো edge নেই (Component 3: {5})
// findConnectedComponents() output:
// [[0, 1, 2], [3, 4], [5]]