Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/computer-science/graph_algorithms.tex
51 views
unlisted
1
\documentclass[a4paper, 11pt]{article}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage{float}
9
\usepackage{algorithm}
10
\usepackage{algpseudocode}
11
\usepackage[makestderr]{pythontex}
12
13
\title{Computer Science: Graph Algorithms and Network Analysis}
14
\author{Computational Science Templates}
15
\date{\today}
16
17
\begin{document}
18
\maketitle
19
20
\begin{abstract}
21
This document presents a comprehensive analysis of fundamental graph algorithms including shortest path algorithms (Dijkstra, Bellman-Ford, Floyd-Warshall), minimum spanning trees (Prim, Kruskal), graph traversal (BFS, DFS), and network flow algorithms. We implement these algorithms in Python and analyze their time complexity, correctness, and practical applications in network routing, social network analysis, and optimization problems.
22
\end{abstract}
23
24
\section{Introduction}
25
Graph algorithms are fundamental to computer science and find applications in networking, social media analysis, logistics, and artificial intelligence. This analysis covers both theoretical foundations and practical implementations of key algorithms for path finding, tree construction, and network analysis.
26
27
\section{Mathematical Framework}
28
29
\subsection{Graph Representation}
30
A graph $G = (V, E)$ consists of vertices $V$ and edges $E$. For weighted graphs, each edge $(u, v)$ has weight $w(u, v)$.
31
32
\subsection{Dijkstra's Algorithm}
33
For non-negative edge weights, Dijkstra's algorithm computes shortest paths:
34
\begin{equation}
35
d[v] = \min_{u \in \text{adj}(v)} \{d[u] + w(u, v)\}
36
\end{equation}
37
38
Time complexity: $O((V + E) \log V)$ with priority queue.
39
40
\subsection{Bellman-Ford Algorithm}
41
Handles negative weights and detects negative cycles:
42
\begin{equation}
43
d^{(k)}[v] = \min_{u} \{d^{(k-1)}[v], d^{(k-1)}[u] + w(u, v)\}
44
\end{equation}
45
46
Time complexity: $O(VE)$.
47
48
\subsection{Minimum Spanning Tree}
49
For a connected weighted graph, MST minimizes total edge weight:
50
\begin{equation}
51
\text{MST}(G) = \arg\min_{T \subset E} \sum_{(u,v) \in T} w(u, v)
52
\end{equation}
53
54
subject to $T$ forming a spanning tree.
55
56
\section{Computational Analysis}
57
58
\begin{pycode}
59
import numpy as np
60
import matplotlib.pyplot as plt
61
from collections import defaultdict
62
import heapq
63
import time
64
plt.rc('text', usetex=True)
65
plt.rc('font', family='serif')
66
67
np.random.seed(42)
68
69
# Store results
70
results = {}
71
72
# Graph class for algorithms
73
class Graph:
74
def __init__(self, n_vertices):
75
self.V = n_vertices
76
self.adj = defaultdict(list)
77
self.edges = []
78
79
def add_edge(self, u, v, w):
80
self.adj[u].append((v, w))
81
self.adj[v].append((u, w))
82
self.edges.append((w, u, v))
83
84
def get_adjacency_matrix(self):
85
matrix = np.zeros((self.V, self.V))
86
for u in self.adj:
87
for v, w in self.adj[u]:
88
matrix[u][v] = w
89
return matrix
90
91
# Generate random graph
92
n_nodes = 10
93
g = Graph(n_nodes)
94
pos = np.random.rand(n_nodes, 2)
95
96
# Create connected graph with random edges
97
for i in range(n_nodes - 1):
98
w = np.linalg.norm(pos[i] - pos[i+1]) * 10
99
g.add_edge(i, i+1, round(w, 1))
100
101
# Add additional edges
102
for _ in range(8):
103
i, j = np.random.choice(n_nodes, 2, replace=False)
104
if j not in [v for v, _ in g.adj[i]]:
105
w = np.linalg.norm(pos[i] - pos[j]) * 10
106
g.add_edge(i, j, round(w, 1))
107
\end{pycode}
108
109
\subsection{Dijkstra's Algorithm Implementation}
110
111
\begin{pycode}
112
def dijkstra(graph, start):
113
"""Dijkstra's algorithm with priority queue"""
114
dist = {v: float('inf') for v in range(graph.V)}
115
prev = {v: None for v in range(graph.V)}
116
dist[start] = 0
117
visited = set()
118
pq = [(0, start)]
119
120
while pq:
121
d, u = heapq.heappop(pq)
122
if u in visited:
123
continue
124
visited.add(u)
125
126
for v, w in graph.adj[u]:
127
if v not in visited:
128
new_dist = dist[u] + w
129
if new_dist < dist[v]:
130
dist[v] = new_dist
131
prev[v] = u
132
heapq.heappush(pq, (new_dist, v))
133
134
return dist, prev
135
136
def get_path(prev, start, end):
137
"""Reconstruct path from predecessors"""
138
path = []
139
current = end
140
while current is not None:
141
path.append(current)
142
current = prev[current]
143
return path[::-1] if path[-1] == start else []
144
145
# Run Dijkstra from node 0
146
start_node = 0
147
end_node = n_nodes - 1
148
t_start = time.time()
149
distances, predecessors = dijkstra(g, start_node)
150
dijkstra_time = (time.time() - t_start) * 1000
151
152
shortest_path = get_path(predecessors, start_node, end_node)
153
path_length = distances[end_node]
154
155
results['dijkstra_time'] = dijkstra_time
156
results['path_length'] = path_length
157
results['path_nodes'] = len(shortest_path)
158
\end{pycode}
159
160
\subsection{Bellman-Ford Algorithm}
161
162
\begin{pycode}
163
def bellman_ford(graph, start):
164
"""Bellman-Ford algorithm for shortest paths"""
165
dist = {v: float('inf') for v in range(graph.V)}
166
prev = {v: None for v in range(graph.V)}
167
dist[start] = 0
168
169
# Relax edges V-1 times
170
for _ in range(graph.V - 1):
171
for w, u, v in graph.edges:
172
if dist[u] + w < dist[v]:
173
dist[v] = dist[u] + w
174
prev[v] = u
175
if dist[v] + w < dist[u]:
176
dist[u] = dist[v] + w
177
prev[u] = v
178
179
# Check for negative cycles
180
has_negative_cycle = False
181
for w, u, v in graph.edges:
182
if dist[u] + w < dist[v]:
183
has_negative_cycle = True
184
break
185
186
return dist, prev, has_negative_cycle
187
188
t_start = time.time()
189
bf_distances, bf_prev, negative_cycle = bellman_ford(g, start_node)
190
bellman_ford_time = (time.time() - t_start) * 1000
191
192
results['bellman_ford_time'] = bellman_ford_time
193
results['has_negative_cycle'] = negative_cycle
194
\end{pycode}
195
196
\subsection{Minimum Spanning Tree - Prim's Algorithm}
197
198
\begin{pycode}
199
def prim_mst(graph):
200
"""Prim's algorithm for MST"""
201
mst_edges = []
202
visited = set([0])
203
edges = [(w, 0, v) for v, w in graph.adj[0]]
204
heapq.heapify(edges)
205
total_weight = 0
206
207
while edges and len(visited) < graph.V:
208
w, u, v = heapq.heappop(edges)
209
if v in visited:
210
continue
211
visited.add(v)
212
mst_edges.append((u, v, w))
213
total_weight += w
214
215
for next_v, next_w in graph.adj[v]:
216
if next_v not in visited:
217
heapq.heappush(edges, (next_w, v, next_v))
218
219
return mst_edges, total_weight
220
221
t_start = time.time()
222
mst_edges, mst_weight = prim_mst(g)
223
prim_time = (time.time() - t_start) * 1000
224
225
results['prim_time'] = prim_time
226
results['mst_weight'] = mst_weight
227
results['mst_edges'] = len(mst_edges)
228
\end{pycode}
229
230
\subsection{Kruskal's Algorithm}
231
232
\begin{pycode}
233
class UnionFind:
234
def __init__(self, n):
235
self.parent = list(range(n))
236
self.rank = [0] * n
237
238
def find(self, x):
239
if self.parent[x] != x:
240
self.parent[x] = self.find(self.parent[x])
241
return self.parent[x]
242
243
def union(self, x, y):
244
px, py = self.find(x), self.find(y)
245
if px == py:
246
return False
247
if self.rank[px] < self.rank[py]:
248
px, py = py, px
249
self.parent[py] = px
250
if self.rank[px] == self.rank[py]:
251
self.rank[px] += 1
252
return True
253
254
def kruskal_mst(graph):
255
"""Kruskal's algorithm for MST"""
256
uf = UnionFind(graph.V)
257
mst_edges = []
258
total_weight = 0
259
260
# Sort edges by weight
261
sorted_edges = sorted(graph.edges)
262
263
for w, u, v in sorted_edges:
264
if uf.union(u, v):
265
mst_edges.append((u, v, w))
266
total_weight += w
267
if len(mst_edges) == graph.V - 1:
268
break
269
270
return mst_edges, total_weight
271
272
t_start = time.time()
273
kruskal_edges, kruskal_weight = kruskal_mst(g)
274
kruskal_time = (time.time() - t_start) * 1000
275
276
results['kruskal_time'] = kruskal_time
277
\end{pycode}
278
279
\subsection{Graph Traversal: BFS and DFS}
280
281
\begin{pycode}
282
def bfs(graph, start):
283
"""Breadth-first search"""
284
visited = []
285
queue = [start]
286
seen = {start}
287
288
while queue:
289
u = queue.pop(0)
290
visited.append(u)
291
for v, _ in graph.adj[u]:
292
if v not in seen:
293
seen.add(v)
294
queue.append(v)
295
296
return visited
297
298
def dfs(graph, start):
299
"""Depth-first search"""
300
visited = []
301
stack = [start]
302
seen = set()
303
304
while stack:
305
u = stack.pop()
306
if u not in seen:
307
seen.add(u)
308
visited.append(u)
309
for v, _ in graph.adj[u]:
310
if v not in seen:
311
stack.append(v)
312
313
return visited
314
315
bfs_order = bfs(g, 0)
316
dfs_order = dfs(g, 0)
317
318
results['bfs_order'] = bfs_order
319
results['dfs_order'] = dfs_order
320
\end{pycode}
321
322
\subsection{Visualization of Graph Algorithms}
323
324
\begin{pycode}
325
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
326
327
def draw_graph(ax, pos, edges, title, highlight_edges=None, highlight_path=None):
328
# Draw all edges
329
for u in range(n_nodes):
330
for v, w in g.adj[u]:
331
if u < v:
332
ax.plot([pos[u, 0], pos[v, 0]], [pos[u, 1], pos[v, 1]],
333
'gray', linewidth=1, alpha=0.3)
334
335
# Highlight specific edges
336
if highlight_edges:
337
for u, v, w in highlight_edges:
338
ax.plot([pos[u, 0], pos[v, 0]], [pos[u, 1], pos[v, 1]],
339
'g-', linewidth=3, alpha=0.8)
340
341
# Highlight path
342
if highlight_path:
343
for i in range(len(highlight_path) - 1):
344
u, v = highlight_path[i], highlight_path[i+1]
345
ax.plot([pos[u, 0], pos[v, 0]], [pos[u, 1], pos[v, 1]],
346
'r-', linewidth=3, zorder=4)
347
348
# Draw nodes
349
for i in range(n_nodes):
350
color = 'lightblue'
351
if highlight_path and i in highlight_path:
352
color = 'lightcoral'
353
ax.scatter(pos[i, 0], pos[i, 1], s=400, c=color, edgecolors='black', zorder=5)
354
ax.text(pos[i, 0], pos[i, 1], str(i), ha='center', va='center', fontsize=10, fontweight='bold')
355
356
ax.set_title(title)
357
ax.set_xlabel('X')
358
ax.set_ylabel('Y')
359
ax.set_aspect('equal')
360
361
# Original graph with shortest path
362
draw_graph(axes[0, 0], pos, g.edges, f'Dijkstra Shortest Path (Cost: {path_length:.1f})',
363
highlight_path=shortest_path)
364
365
# Minimum Spanning Tree (Prim)
366
draw_graph(axes[0, 1], pos, g.edges, f"Prim's MST (Weight: {mst_weight:.1f})",
367
highlight_edges=mst_edges)
368
369
# MST with Kruskal
370
draw_graph(axes[0, 2], pos, g.edges, f"Kruskal's MST (Weight: {kruskal_weight:.1f})",
371
highlight_edges=kruskal_edges)
372
373
# Distance comparison
374
ax3 = axes[1, 0]
375
nodes = list(range(n_nodes))
376
dijkstra_dists = [distances[i] for i in nodes]
377
bf_dists = [bf_distances[i] for i in nodes]
378
x_pos = np.arange(n_nodes)
379
width = 0.35
380
ax3.bar(x_pos - width/2, dijkstra_dists, width, label='Dijkstra', alpha=0.7)
381
ax3.bar(x_pos + width/2, bf_dists, width, label='Bellman-Ford', alpha=0.7)
382
ax3.set_xlabel('Node')
383
ax3.set_ylabel('Distance from Source')
384
ax3.set_title('Shortest Path Distances')
385
ax3.legend()
386
ax3.grid(True, alpha=0.3)
387
388
# BFS vs DFS order
389
ax4 = axes[1, 1]
390
ax4.plot(range(n_nodes), bfs_order, 'bo-', label='BFS', linewidth=2, markersize=8)
391
ax4.plot(range(n_nodes), dfs_order, 'rs-', label='DFS', linewidth=2, markersize=8)
392
ax4.set_xlabel('Visit Order')
393
ax4.set_ylabel('Node ID')
394
ax4.set_title('BFS vs DFS Traversal Order')
395
ax4.legend()
396
ax4.grid(True, alpha=0.3)
397
398
# Adjacency matrix
399
ax5 = axes[1, 2]
400
adj_matrix = g.get_adjacency_matrix()
401
im = ax5.imshow(adj_matrix, cmap='YlOrRd')
402
plt.colorbar(im, ax=ax5, label='Edge Weight')
403
ax5.set_xlabel('Node')
404
ax5.set_ylabel('Node')
405
ax5.set_title('Adjacency Matrix')
406
407
plt.tight_layout()
408
plt.savefig('graph_algorithms_main.pdf', bbox_inches='tight', dpi=150)
409
print(r'\begin{figure}[H]')
410
print(r'\centering')
411
print(r'\includegraphics[width=\textwidth]{graph_algorithms_main.pdf}')
412
print(r'\caption{Graph algorithm results: (a) Dijkstra shortest path, (b) Prim MST, (c) Kruskal MST, (d) distance comparison, (e) traversal order, (f) adjacency matrix.}')
413
print(r'\label{fig:main}')
414
print(r'\end{figure}')
415
plt.close()
416
\end{pycode}
417
418
\subsection{Algorithm Complexity Analysis}
419
420
\begin{pycode}
421
# Compare algorithm performance across different graph sizes
422
graph_sizes = [10, 20, 50, 100, 200]
423
dijkstra_times = []
424
bellman_times = []
425
prim_times = []
426
kruskal_times = []
427
428
for n in graph_sizes:
429
# Create random graph
430
test_g = Graph(n)
431
test_pos = np.random.rand(n, 2)
432
433
# Ensure connectivity
434
for i in range(n - 1):
435
w = np.linalg.norm(test_pos[i] - test_pos[i+1]) * 10
436
test_g.add_edge(i, i+1, round(w, 1))
437
438
# Add random edges
439
for _ in range(n):
440
i, j = np.random.choice(n, 2, replace=False)
441
if j not in [v for v, _ in test_g.adj[i]]:
442
w = np.linalg.norm(test_pos[i] - test_pos[j]) * 10
443
test_g.add_edge(i, j, round(w, 1))
444
445
# Time Dijkstra
446
t0 = time.time()
447
dijkstra(test_g, 0)
448
dijkstra_times.append((time.time() - t0) * 1000)
449
450
# Time Bellman-Ford
451
t0 = time.time()
452
bellman_ford(test_g, 0)
453
bellman_times.append((time.time() - t0) * 1000)
454
455
# Time Prim
456
t0 = time.time()
457
prim_mst(test_g)
458
prim_times.append((time.time() - t0) * 1000)
459
460
# Time Kruskal
461
t0 = time.time()
462
kruskal_mst(test_g)
463
kruskal_times.append((time.time() - t0) * 1000)
464
465
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
466
467
# Shortest path algorithms
468
ax1 = axes[0]
469
ax1.plot(graph_sizes, dijkstra_times, 'b-o', linewidth=2, label='Dijkstra')
470
ax1.plot(graph_sizes, bellman_times, 'r-s', linewidth=2, label='Bellman-Ford')
471
ax1.set_xlabel('Number of Vertices')
472
ax1.set_ylabel('Time (ms)')
473
ax1.set_title('Shortest Path Algorithm Comparison')
474
ax1.legend()
475
ax1.grid(True, alpha=0.3)
476
477
# MST algorithms
478
ax2 = axes[1]
479
ax2.plot(graph_sizes, prim_times, 'g-o', linewidth=2, label="Prim's")
480
ax2.plot(graph_sizes, kruskal_times, 'm-s', linewidth=2, label="Kruskal's")
481
ax2.set_xlabel('Number of Vertices')
482
ax2.set_ylabel('Time (ms)')
483
ax2.set_title('MST Algorithm Comparison')
484
ax2.legend()
485
ax2.grid(True, alpha=0.3)
486
487
plt.tight_layout()
488
plt.savefig('graph_algorithms_complexity.pdf', bbox_inches='tight', dpi=150)
489
print(r'\begin{figure}[H]')
490
print(r'\centering')
491
print(r'\includegraphics[width=\textwidth]{graph_algorithms_complexity.pdf}')
492
print(r'\caption{Algorithm complexity comparison: (a) shortest path algorithms, (b) MST algorithms.}')
493
print(r'\label{fig:complexity}')
494
print(r'\end{figure}')
495
plt.close()
496
497
results['dijkstra_200'] = dijkstra_times[-1]
498
results['bellman_200'] = bellman_times[-1]
499
results['prim_200'] = prim_times[-1]
500
results['kruskal_200'] = kruskal_times[-1]
501
\end{pycode}
502
503
\subsection{Graph Connectivity Analysis}
504
505
\begin{pycode}
506
# Analyze graph properties
507
def graph_metrics(graph):
508
"""Calculate various graph metrics"""
509
# Degree distribution
510
degrees = [len(graph.adj[v]) for v in range(graph.V)]
511
512
# Clustering coefficient
513
clustering = []
514
for v in range(graph.V):
515
neighbors = [u for u, _ in graph.adj[v]]
516
if len(neighbors) < 2:
517
clustering.append(0)
518
continue
519
edges_between = 0
520
for i, u1 in enumerate(neighbors):
521
for u2 in neighbors[i+1:]:
522
if u2 in [n for n, _ in graph.adj[u1]]:
523
edges_between += 1
524
max_edges = len(neighbors) * (len(neighbors) - 1) / 2
525
clustering.append(edges_between / max_edges if max_edges > 0 else 0)
526
527
return degrees, clustering
528
529
degrees, clustering = graph_metrics(g)
530
531
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
532
533
# Degree distribution
534
ax1 = axes[0]
535
ax1.bar(range(n_nodes), degrees, alpha=0.7, color='steelblue', edgecolor='black')
536
ax1.axhline(y=np.mean(degrees), color='r', linestyle='--', label=f'Mean: {np.mean(degrees):.1f}')
537
ax1.set_xlabel('Node')
538
ax1.set_ylabel('Degree')
539
ax1.set_title('Node Degree Distribution')
540
ax1.legend()
541
ax1.grid(True, alpha=0.3)
542
543
# Clustering coefficient
544
ax2 = axes[1]
545
ax2.bar(range(n_nodes), clustering, alpha=0.7, color='coral', edgecolor='black')
546
ax2.axhline(y=np.mean(clustering), color='b', linestyle='--', label=f'Mean: {np.mean(clustering):.2f}')
547
ax2.set_xlabel('Node')
548
ax2.set_ylabel('Clustering Coefficient')
549
ax2.set_title('Local Clustering Coefficients')
550
ax2.legend()
551
ax2.grid(True, alpha=0.3)
552
553
plt.tight_layout()
554
plt.savefig('graph_algorithms_metrics.pdf', bbox_inches='tight', dpi=150)
555
print(r'\begin{figure}[H]')
556
print(r'\centering')
557
print(r'\includegraphics[width=\textwidth]{graph_algorithms_metrics.pdf}')
558
print(r'\caption{Graph metrics: (a) degree distribution, (b) clustering coefficients.}')
559
print(r'\label{fig:metrics}')
560
print(r'\end{figure}')
561
plt.close()
562
563
results['mean_degree'] = np.mean(degrees)
564
results['mean_clustering'] = np.mean(clustering)
565
results['n_edges'] = len(g.edges)
566
\end{pycode}
567
568
\section{Results and Discussion}
569
570
\subsection{Shortest Path Results}
571
572
\begin{table}[H]
573
\centering
574
\caption{Shortest Path Algorithm Performance}
575
\label{tab:shortest}
576
\begin{tabular}{lccc}
577
\toprule
578
\textbf{Algorithm} & \textbf{Time (ms)} & \textbf{Complexity} & \textbf{Features} \\
579
\midrule
580
Dijkstra & \py{f"{results['dijkstra_time']:.3f}"} & $O((V+E)\log V)$ & Non-negative weights \\
581
Bellman-Ford & \py{f"{results['bellman_ford_time']:.3f}"} & $O(VE)$ & Negative cycle detection \\
582
\bottomrule
583
\end{tabular}
584
\end{table}
585
586
Shortest path from node \py{start_node} to node \py{end_node}:
587
\begin{itemize}
588
\item Path: \py{f"{' -> '.join(map(str, shortest_path))}"}
589
\item Total distance: \py{f"{results['path_length']:.2f}"}
590
\item Number of hops: \py{f"{results['path_nodes'] - 1}"}
591
\end{itemize}
592
593
\subsection{Minimum Spanning Tree Results}
594
595
\begin{table}[H]
596
\centering
597
\caption{MST Algorithm Performance}
598
\label{tab:mst}
599
\begin{tabular}{lccc}
600
\toprule
601
\textbf{Algorithm} & \textbf{Time (ms)} & \textbf{Complexity} & \textbf{MST Weight} \\
602
\midrule
603
Prim's & \py{f"{results['prim_time']:.3f}"} & $O(E \log V)$ & \py{f"{results['mst_weight']:.2f}"} \\
604
Kruskal's & \py{f"{results['kruskal_time']:.3f}"} & $O(E \log E)$ & \py{f"{kruskal_weight:.2f}"} \\
605
\bottomrule
606
\end{tabular}
607
\end{table}
608
609
\subsection{Graph Properties}
610
611
\begin{itemize}
612
\item Number of vertices: \py{f"{n_nodes}"}
613
\item Number of edges: \py{f"{results['n_edges']}"}
614
\item Mean degree: \py{f"{results['mean_degree']:.2f}"}
615
\item Mean clustering coefficient: \py{f"{results['mean_clustering']:.3f}"}
616
\item Negative cycle detected: \py{"Yes" if results['has_negative_cycle'] else "No"}
617
\end{itemize}
618
619
\subsection{Scalability Analysis}
620
621
At 200 vertices:
622
\begin{itemize}
623
\item Dijkstra: \py{f"{results['dijkstra_200']:.2f}"} ms
624
\item Bellman-Ford: \py{f"{results['bellman_200']:.2f}"} ms
625
\item Prim's MST: \py{f"{results['prim_200']:.2f}"} ms
626
\item Kruskal's MST: \py{f"{results['kruskal_200']:.2f}"} ms
627
\end{itemize}
628
629
\section{Conclusion}
630
This analysis demonstrated fundamental graph algorithms with their implementations and complexity analysis. Key findings include:
631
\begin{enumerate}
632
\item Dijkstra's algorithm is efficient for non-negative weights with $O((V+E)\log V)$ complexity
633
\item Bellman-Ford handles negative weights but has higher $O(VE)$ complexity
634
\item Both Prim's and Kruskal's algorithms produce identical MSTs with similar performance
635
\item BFS explores level-by-level while DFS goes deep first, affecting traversal order
636
\item Graph metrics like degree distribution and clustering coefficient characterize network structure
637
\end{enumerate}
638
639
These algorithms form the foundation for network analysis, routing protocols, and optimization in various domains.
640
641
\end{document}
642
643