تمثيلات الرسوم البيانية وإعداد الاجتياز
أنشئ رسومًا بيانية موجهة وغير موجهة باستخدام قوائم التجاور، وهيّئ BFS باستخدام deque وDFS باستخدام مكدس أو استدعاء ذاتي، مع تتبع العقد التي تمت زيارتها
تمثيلات الرسوم البيانية وإعداد الاجتياز درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
ما الرسم البياني؟
الرسم البياني هو مجموعة من العقد (الرؤوس) المتصلة بواسطة الحواف. وعلى خلاف الأشجار، يمكن أن تحتوي الرسوم البيانية على دورات، ومسارات متعددة بين العقد، ومكوّنات غير متصلة. وتمثل الرسوم البيانية أنظمة واقعية مثل الشبكات الاجتماعية، وخرائط الطرق، وأشجار الاعتماديات، وروابط صفحات الويب. وتتطرق تقريبًا كل مقابلة جادة في تصميم الأنظمة والخوارزميات إلى الرسوم البيانية، لذا فإن إتقان تمثيلها واجتيازها أمر أساسي.
# Graph terminology:
# - V: set of vertices (nodes)
# - E: set of edges
# - Directed graph: edges have direction (A -> B but not B -> A)
# - Undirected graph: edges are bidirectional
# - Weighted graph: edges have costs/weights
# - Cyclic: contains at least one cycle
# - Acyclic: no cycles (DAG = Directed Acyclic Graph)
# - Connected: every node reachable from every other
# - Disconnected: multiple isolated components
print('Graph: nodes + edges, directed/undirected, weighted/unweighted')تمثيل قائمة التجاور
تخزّن قائمة التجاور قائمة جيران كل عقدة. في Python، استخدموا dict يربط كل عقدة بقائمة العقد المجاورة لها. وهذا أكثر تمثيلات الرسوم البيانية شيوعاً في مسائل المقابلات: مساحة O(V + E) (وهي فعّالة للرسوم البيانية المتناثرة)، وزمن O(degree) لتكرار الجيران، ومتوسط زمن O(1) للتحقق من التجاور عند استخدام صيغة تعتمد على مجموعة تجزئة. تستخدم معظم مسائل الرسوم البيانية في LeetCode هذه الصيغة.
from collections import defaultdict
# Build an undirected graph
def build_undirected(edges):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u) # both directions
return graph
edges = [(0,1), (0,2), (1,3), (2,3), (3,4)]
graph = build_undirected(edges)
print(dict(graph))
# {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2,4], 4:[3]}
# Directed graph: only one direction
def build_directed(edges):
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v) # only u -> v
return graphتمثيل مصفوفة التجاور
مصفوفة التجاور هي مصفوفة ثنائية الأبعاد بحجم V×V، حيث تكون قيمة matrix[i][j] = 1 (أو وزن الحافة) إذا وُجدت حافة من i إلى j، وتكون 0 خلاف ذلك. وتوفّر عملية بحث عن الحواف بزمن O(1)، لكنها تستخدم مساحة O(V²) بغض النظر عن عدد الحواف، مما يجعلها مهدرة للمساحات في الرسوم البيانية المتناثرة. ويُفضّل استخدامها عندما يكون الرسم البياني كثيفاً (أي يحتوي على حواف كثيرة)، أو عندما تكون عمليات التحقق السريع من وجود الحواف ضرورية، كما في خوارزمية Floyd-Warshall لإيجاد أقصر المسارات بين جميع أزواج الرؤوس.
# Adjacency matrix for 5 nodes
V = 5
matrix = [[0] * V for _ in range(V)]
edges = [(0,1), (0,2), (1,3), (2,3), (3,4)]
for u, v in edges:
matrix[u][v] = 1
matrix[v][u] = 1 # undirected
# Print the matrix:
for row in matrix:
print(row)
# Neighbour check: O(1)
print('Edge 0-2:', bool(matrix[0][2])) # True
print('Edge 0-4:', bool(matrix[0][4])) # False
# Space: O(V^2) vs adjacency list O(V+E)
# Dense graph: matrix often better; sparse: list betterتمثيل قائمة الحواف
قائمة الحواف هي أبسط تمثيل: مجرد قائمة من الصفوف (المصدر، الوجهة)، مع أوزان اختيارية. وتستخدم مساحة O(E)، كما يسهل تكرار جميع الحواف فيها. لكن العثور على جيران عقدة يتطلب فحص جميع الحواف، أي بزمن O(E). وتُستخدم قوائم الحواف في خوارزميات الرسوم البيانية التي تكرّر على جميع الحواف بشكل صريح، مثل Bellman-Ford (إرخاء جميع الحواف n-1 مرة) وخوارزمية Kruskal لإيجاد الشجرة الممتدة الدنيا.
# Weighted edge list: (source, destination, weight)
edge_list = [
(0, 1, 4),
(0, 2, 1),
(1, 3, 1),
(2, 3, 5),
(3, 4, 3)
]
# Useful for:
# Bellman-Ford: iterate all edges n-1 times
# Kruskal's MST: sort by weight then union-find
# Sort by weight for Kruskal:
edge_list_sorted = sorted(edge_list, key=lambda e: e[2])
print('Sorted by weight:', edge_list_sorted)
# Finding neighbours: O(E) scan -- inefficient for traversal
node_0_neighbors = [v for u, v, w in edge_list if u == 0]
print('Node 0 neighbors:', node_0_neighbors)إعداد BFS: الطابور ومجموعة العقد التي تمت زيارتها
تستكشف BFS (البحث بعرض الرسم البياني) الرسم البياني مستوى تلو الآخر باستخدام طابور. والمكوّن الأساسي هو مجموعة العقد التي تمت زيارتها لتجنّب زيارة العقد مجدداً في الرسوم البيانية الدورية. فمن دون هذه المجموعة، ستدخل BFS في حلقة لا نهائية عند التعامل مع رسم بياني دوري. والإعداد القياسي هو: تهيئة الطابور بعقدة المصدر، ووضع علامة على أنها زِيرت، ثم إزالة العقد من الطابور ومعالجتها وإضافة جيرانها الذين لم تتم زيارتهم بعد، بشكل متكرر.
from collections import deque
def bfs(graph, start):
visited = {start} # mark source as visited
queue = deque([start]) # initialise queue
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbour in graph[node]:
if neighbour not in visited:
visited.add(neighbour) # mark BEFORE enqueue
queue.append(neighbour)
return order
from collections import defaultdict
graph = defaultdict(list)
for u, v in [(0,1),(0,2),(1,3),(2,3),(3,4)]:
graph[u].append(v); graph[v].append(u)
print(bfs(graph, 0)) # [0, 1, 2, 3, 4]إعداد DFS: المكدس أو الاستدعاء الذاتي
تستكشف DFS (البحث بعمق الرسم البياني) كل فرع إلى أبعد حد ممكن قبل التراجع. ويمكنكم تنفيذها باستخدام الاستدعاء الذاتي (من خلال مكدس الاستدعاءات) أو بشكل تكراري (باستخدام مكدس صريح). ويتطلب الأسلوبان مجموعة للعقد التي تمت زيارتها عند التعامل مع الرسوم البيانية الدورية. ويدفع الإصدار التكراري الجيران بترتيب عكسي لمحاكاة ترتيب اجتياز DFS باستخدام الاستدعاء الذاتي، مع أن ترتيب الاستكشاف قد يختلف بين التطبيقين.
def dfs_recursive(graph, node, visited=None, order=None):
if visited is None: visited = set(); order = []
visited.add(node)
order.append(node)
for neighbour in graph[node]:
if neighbour not in visited:
dfs_recursive(graph, neighbour, visited, order)
return order
def dfs_iterative(graph, start):
visited = set()
stack = [start]
order = []
while stack:
node = stack.pop()
if node in visited: continue
visited.add(node)
order.append(node)
for neighbour in reversed(graph[node]): # reverse for same order as recursive
if neighbour not in visited:
stack.append(neighbour)
return order
print('Recursive DFS:', dfs_recursive(graph, 0))
print('Iterative DFS:', dfs_iterative(graph, 0))متى نستخدم BFS ومتى نستخدم DFS
اختاروا BFS عندما تحتاجون إلى أقصر مسار (أي أقل عدد من الحواف) في رسم بياني غير موزون، أو عندما تحتاجون إلى معالجة العقد مستوى تلو الآخر. واختاروا DFS عندما تحتاجون إلى استكشاف جميع العقد القابلة للوصول، أو اكتشاف الدورات، أو العثور على المكوّنات المتصلة، أو إجراء الترتيب الطوبولوجي، أو تعداد جميع المسارات. عملياً: استخدموا BFS من أجل «أقصر مسار/أقل عدد من القفزات»، وDFS من أجل «الوجود/إمكانية الوصول/التعداد».
# BFS use cases:
# - Shortest path in unweighted graph (fewest edges)
# - Level-order traversal
# - Word ladder (minimum transformations)
# - Clone graph
# DFS use cases:
# - Connected components (flood fill)
# - Cycle detection
# - Topological sort
# - All paths between two nodes
# - Maze solving (any path)
# - N-queens, Sudoku (backtracking)
# Both: O(V + E) time, O(V) space for visited
print('BFS: shortest hops | DFS: existence and enumeration')الرسم البياني من صيغ إدخال LeetCode
تأتي مسائل الرسوم البيانية في LeetCode بصيغ إدخال مختلفة. قائمة الحواف: [[0,1],[0,2]] — أنشئوا قائمة تجاور. قائمة تجاور تعتمد على الفهرس: graph[i] هي قائمة جيران i. الشبكة/المصفوفة: مصفوفة ثنائية الأبعاد بحجم m×n، حيث تمثل الخلايا العقد، وتمثل الخلايا المتجاورة (أعلى/أسفل/يمين/يسار) الجيران. عقدة ذات أبناء: أصناف مخصصة مثل Node(val, neighbors). تعرّفوا على هذه الصيغ وحوّلوها إلى قائمة تجاور كخطوتكم الأولى.
# Format 1: edge list -> adjacency list
def edges_to_adj(n, edges):
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
return graph
# Format 2: 2D grid -> adjacency (implicit)
# Neighbours of (r, c): (r-1,c), (r+1,c), (r,c-1), (r,c+1)
DIRS = [(-1,0),(1,0),(0,-1),(0,1)]
def grid_neighbours(grid, r, c):
rows, cols = len(grid), len(grid[0])
return [(r+dr, c+dc) for dr, dc in DIRS
if 0 <= r+dr < rows and 0 <= c+dc < cols]
grid = [[1,1,0],[0,1,1],[1,0,0]]
print('Neighbours of (0,0):', grid_neighbours(grid, 0, 0))
print('Neighbours of (1,1):', grid_neighbours(grid, 1, 1))وضع علامة على الخلايا التي تمت زيارتها في الشبكات
في مسائل الشبكات، توجد طريقتان لتتبّع الخلايا التي تمت زيارتها. الخيار أ: استخدموا مجموعة visited منفصلة من صفوف (row, col)، وتحتاج إلى مساحة إضافية O(m*n). الخيار ب: عدّلوا الشبكة في مكانها بوضع قيمة مميّزة على الخلايا التي تمت زيارتها (مثل '#' أو 2)، ثم أعيدوا القيم إلى ما كانت عليه عند الحاجة. ويستخدم الأسلوب داخل المكان مساحة إضافية O(1)، وهو شائع في مسائل الملء الانتشاري وعدّ الجزر.
def num_islands(grid):
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
count = 0
def dfs(r, c):
if r < 0 or r >= rows or c < 0 or c >= cols:
return
if grid[r][c] != '1':
return
grid[r][c] = '#' # mark as visited (in-place)
dfs(r+1, c); dfs(r-1, c)
dfs(r, c+1); dfs(r, c-1)
for r in range(rows):
for c in range(cols):
if grid[r][c] == '1':
dfs(r, c)
count += 1
return count
grid = [['1','1','0','0'],
['1','1','0','0'],
['0','0','1','0'],
['0','0','0','1']]
print(num_islands(grid)) # 3تهيئة BFS باستخدام مصادر متعددة
تبدأ BFS متعددة المصادر من عدة عقد في الوقت نفسه، وذلك بتهيئة الطابور بجميع عقد المصدر ووضع علامة على أنها زِيرت. ويُستخدم هذا الأسلوب في مسائل مثل «المسافة إلى أقرب 0» و«البرتقالات المتعفنة» و«الجدران والبوابات»، عندما تريدون أقصر مسافة من أي عقدة من عقد المصدر. تعمل BFS متعددة المصادر بزمن O(V + E)، وهو الزمن نفسه لـBFS أحادية المصدر، لأن كل عقدة لا تزال تُزار مرة واحدة على الأكثر.
from collections import deque
def rotting_oranges(grid):
rows, cols = len(grid), len(grid[0])
queue = deque()
fresh = 0
# Multi-source: all rotten oranges start at time=0
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
queue.append((r, c, 0)) # (row, col, time)
elif grid[r][c] == 1:
fresh += 1
dirs = [(0,1),(0,-1),(1,0),(-1,0)]
time = 0
while queue:
r, c, t = queue.popleft()
for dr, dc in dirs:
nr, nc = r+dr, c+dc
if 0<=nr<rows and 0<=nc<cols and grid[nr][nc]==1:
grid[nr][nc] = 2 # mark rotten
fresh -= 1
queue.append((nr, nc, t+1))
time = t + 1
return time if fresh == 0 else -1
print(rotting_oranges([[2,1,1],[1,1,0],[0,1,1]])) # 4كثافة الرسم البياني واختيار التمثيل
يعتمد الاختيار بين قائمة التجاور والمصفوفة على كثافة الرسم البياني، أي النسبة E/V². يستفيد الرسم البياني المتناثر (E << V²) من قوائم التجاور: مساحة O(V+E) بدلاً من O(V²) للمصفوفة. ويستفيد الرسم البياني الكثيف (E ≈ V²) من مصفوفات التجاور: بحث عن الحواف بزمن O(1) مقابل O(degree) في القوائم. وفي مسائل المقابلات، تكون قوائم التجاور تقريباً دائماً الخيار الصحيح، لأن معظم المسائل تتعامل مع رسوم بيانية متناثرة.
# Graph density comparison:
# Sparse: social network (V=1B users, avg 200 friends)
# E = 200 * 1B = 200B << V^2 = 10^18 -> adjacency list
# Dense: complete graph (every node connected to every other)
# E = V*(V-1)/2 ≈ V^2 -> adjacency matrix
# Interview rule of thumb:
# - Default to adjacency list (defaultdict(list))
# - Use matrix only when asked about dense graph or O(1) edge lookup
# - Grid problems: use implicit adjacency (4-directional neighbours)
print('Sparse graph (E << V^2): use adjacency list')
print('Dense graph (E ~ V^2): consider adjacency matrix')تحقق سريع
اختبروا فهمكم لمفاهيم Data Structures & Algorithms — Coding Interview Prep الواردة في هذا الدرس.
مراجعة الدرس
تعلّمتم في هذا الدرس: ثلاثة تمثيلات للرسوم البيانية (قائمة التجاور، ومصفوفة التجاور، وقائمة الحواف) ومتى تختارون كلّاً منها، وإعداد BFS وDFS باستخدام مجموعات العقد التي تمت زيارتها لتجنّب الحلقات اللانهائية في الرسوم البيانية الدورية، وأنماطاً عملية مثل وضع العلامات داخل الشبكة وBFS متعددة المصادر. في الخطوة التالية، سنطبّق BFS للعثور على أقصر المسارات واجتياز المستويات.
الأسئلة الشائعة
هل درس «تمثيلات الرسوم البيانية وإعداد الاجتياز» مجاني؟
نعم — نص درس «تمثيلات الرسوم البيانية وإعداد الاجتياز» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «تمثيلات الرسوم البيانية وإعداد الاجتياز»؟
أنشئ رسومًا بيانية موجهة وغير موجهة باستخدام قوائم التجاور، وهيّئ BFS باستخدام deque وDFS باستخدام مكدس أو استدعاء ذاتي، مع تتبع العقد التي تمت زيارتها تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟
لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «تمثيلات الرسوم البيانية وإعداد الاجتياز»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟
نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تمثيلات الرسوم البيانية وإعداد الاجتياز
- BFS: أقصر مسار والاجتياز حسب المستويات
- DFS: المكوّنات المتصلة والملء التلقائي
- اكتشاف الدورات في الرسوم الموجهة وغير الموجهة