Articulation Point یا Cut Vertex رأسی در یک گراف بدونجهت است که اگر آن را همراه با یالهای متصل به آن حذف کنیم، تعداد Connected Componentهای گراف افزایش پیدا میکند.
به زبان ساده:
Articulation Point یک نقطهی شکست مهم در شبکه است.
اگر این Node از کار بیفتد، بخشهایی از Graph ممکن است دیگر نتوانند با یکدیگر ارتباط داشته باشند.
این مفهوم در تحلیل پایداری شبکه، طراحی زیرساخت، ارتباطات، حملونقل، معماری سرویسها و بسیاری از مسائل Graph Theory بسیار مهم است.
در این مقاله Articulation Point را از پایه بررسی میکنیم، تفاوت آن را با Bridge میفهمیم، مفهومهای discovery time و low-link را یاد میگیریم و الگوریتم Tarjan را با JavaScript و TypeScript پیادهسازی میکنیم.
یک مثال ساده
Graph زیر را در نظر بگیرید:
A -- B -- C
|
Dاگر B را حذف کنیم:
A C
Dسه بخش جدا داریم.
پس:
B = Articulation Pointاما اگر A را حذف کنیم:
B -- C
|
Dبخش باقیمانده هنوز Connected است.
پس A Articulation Point نیست.
تعریف دقیق
در یک گراف بدونجهت:
G = (V, E)رأس u یک Articulation Point است اگر حذف u باعث افزایش تعداد Connected Componentها شود.
یعنی:
components(G - u)
>
components(G)چرا Articulation Point مهم است؟
فرض کنید Graph یک Network واقعی است.
مثلاً:
Router = Vertex
Link = Edgeاگر یک Router خاص تنها مسیر ارتباط بین دو بخش شبکه باشد، خرابی آن Router کل ارتباط را قطع میکند.
پس آن Router:
Single Point of Failureاست.
Articulation Point دقیقاً چنین Nodeهایی را از نظر Graph Connectivity پیدا میکند.
تفاوت Articulation Point با Bridge
این تفاوت بسیار مهم است.
Articulation Point
یک Vertex بحرانی است.
اگر حذف شود، Graph disconnectedتر میشود.
Bridge
یک Edge بحرانی است.
اگر حذف شود، تعداد Connected Componentها افزایش پیدا میکند.
پس:
Articulation Point = critical vertex
Bridge = critical edgeمثال
Graph:
A -- B -- Cدر این Graph:
BArticulation Point است.
و هر دو Edge:
A -- B
B -- CBridge هستند.
روش ساده Brute Force
یک روش ساده برای پیدا کردن Articulation Pointها:
برای هر Vertex:
- آن Vertex را موقتاً حذف کن.
- تعداد Connected Componentها را حساب کن.
- Vertex را برگردان.
- اگر تعداد Componentها بیشتر شد، Articulation Point است.
مشکل Brute Force
اگر برای هر Vertex یک DFS کامل اجرا کنیم:
V بارو هر DFS:
O(V + E)باشد، Complexity تقریباً:
O(V × (V + E))میشود.
برای Graph بزرگ مناسب نیست.
الگوریتم بهتر: Tarjan
با یک DFS میتوان تمام Articulation Pointها را پیدا کرد.
Complexity:
O(V + E)کل ایده بر دو مقدار مهم بنا شده است:
disc[u]
low[u]discovery time چیست؟
disc[u] زمان اولین ورود DFS به رأس u است.
مثلاً اگر DFS ترتیب زیر را داشته باشد:
A → B → C → Dممکن است:
disc[A] = 1
disc[B] = 2
disc[C] = 3
disc[D] = 4باشد.
این عدد فقط ترتیب Discovery را نشان میدهد.
low-link value چیست؟
low[u] کمترین discovery time رأسی است که از Subtree مربوط به u میتوان با:
- Tree Edgeهای DFS
- و حداکثر یک Back Edge
به آن رسید.
به زبان سادهتر:
low میگوید Subtree این Node تا چه Ancestor بالاتری میتواند بدون عبور دوباره از Parent اصلی فرار کند.
مثال ساده برای low
فرض کنید:
A
|
B
|
C
|
Dو یک Back Edge داریم:
D -- Aپس از Subtree D میتوان دوباره به A رسید.
بنابراین:
low[D] = disc[A]و این مقدار به بالا propagate میشود.
چرا low مهم است؟
فرض کنید DFS Tree:
u
|
vباشد.
اگر Subtree مربوط به v هیچ راهی برای رسیدن به Ancestorهای u نداشته باشد، حذف u باعث جدا شدن Subtree v میشود.
شرط این حالت:
low[v] >= disc[u]است.
شرط Articulation Point برای Node غیرریشه
اگر u Root نباشد و یک DFS Child مثل v داشته باشد که:
low[v] >= disc[u]آنگاه:
u = Articulation Pointچرا؟
چون Subtree v نمیتواند به هیچ Ancestor بالاتر از u وصل شود.
پس اگر u حذف شود، آن Subtree جدا میشود.
چرا >= و نه > ؟
برای Bridge شرط معروف:
low[v] > disc[u]است.
اما برای Articulation Point:
low[v] >= disc[u]چون اگر Subtree فقط بتواند به خود u برگردد، حذف u همچنان اتصال را از بین میبرد.
این تفاوت بسیار مهم است:
Bridge:
low[v] > disc[u]
Articulation Point:
low[v] >= disc[u]شرط Root
Root در DFS Tree قانون متفاوتی دارد.
Root زمانی Articulation Point است که:
بیش از یک DFS childداشته باشد.
یعنی:
children > 1چرا Root قانون جدا دارد؟
برای Nodeهای معمولی Parent و Ancestor داریم.
اما Root Ancestor ندارد.
اگر Root فقط یک DFS Child داشته باشد، تمام Graph reachable آن در همان Subtree است.
حذف Root لزوماً تعداد Componentها را افزایش نمیدهد.
اما اگر Root دو یا چند Child مستقل داشته باشد:
R
/ \
A BDFS Subtreeهای A و B بدون Root راهی به یکدیگر ندارند.
پس حذف R آنها را جدا میکند.
الگوریتم مرحلهبهمرحله
برای هر Component از Graph:
- DFS را شروع کن.
disc[u]را ثبت کن.- مقدار اولیه:
Code
low[u] = disc[u] - برای هر Neighbor:
- اگر unvisited است، DFS اجرا کن.
- بعد:
Code
low[u] = min(low[u], low[v]) - شرط Articulation را بررسی کن.
- اگر Neighbor قبلاً visited شده و Parent نیست:
Code
low[u] = min(low[u], disc[v])
پیادهسازی کامل JavaScript
function findArticulationPoints(graph) {
const nodes = new Set();
for (const [u, neighbors] of Object.entries(graph)) {
nodes.add(u);
for (const v of neighbors) {
nodes.add(v);
}
}
const visited = new Set();
const disc = new Map();
const low = new Map();
const parent = new Map();
const articulation = new Set();
let time = 0;
function dfs(u) {
visited.add(u);
disc.set(u, ++time);
low.set(u, disc.get(u));
let children = 0;
for (const v of graph[u] ?? []) {
if (!visited.has(v)) {
children++;
parent.set(v, u);
dfs(v);
low.set(
u,
Math.min(
low.get(u),
low.get(v)
)
);
const isRoot =
!parent.has(u);
if (
isRoot &&
children > 1
) {
articulation.add(u);
}
if (
!isRoot &&
low.get(v) >= disc.get(u)
) {
articulation.add(u);
}
} else if (
v !== parent.get(u)
) {
low.set(
u,
Math.min(
low.get(u),
disc.get(v)
)
);
}
}
}
for (const node of nodes) {
if (!visited.has(node)) {
dfs(node);
}
}
return [...articulation];
}مثال اجرا
const graph = {
A: ["B"],
B: ["A", "C", "D"],
C: ["B"],
D: ["B", "E", "F"],
E: ["D", "F"],
F: ["D", "E"]
};
console.log(
findArticulationPoints(graph)
);در این Graph ممکن است:
B
DArticulation Point باشند.
بررسی شهودی مثال
Graph:
A -- B -- C
|
D
/ \
E---Fاگر B حذف شود:
A
C
D-E-Fقسمتها جدا میشوند.
پس B بحرانی است.
اگر D حذف شود، مثلث E-F از B جدا میشود.
پس D نیز بحرانی است.
محاسبه disc و low روی DFS
فرض کنید DFS:
A → B → C
|
D → E → F
↑ |
└───┘ممکن است discovery timeها:
A = 1
B = 2
C = 3
D = 4
E = 5
F = 6باشند.
به دلیل Back Edge بین F و E یا سایر Connectionها، lowهای Subtree میتوانند کاهش پیدا کنند.
تحلیل low نشان میدهد کدام Subtree راه جایگزین به بالا دارد.
Tree Edge و Back Edge
در DFS دو نوع Edge در این بحث مهماند.
Tree Edge
Edgeای که با آن برای اولین بار وارد یک Node جدید میشویم.
مثلاً:
u → vو v unvisited بوده.
Back Edge
Edgeای که از Node یا Subtree به Ancestor قبلی برمیگردد.
Back Edge باعث کاهش:
low[u]میشود.
وجود Back Edge یعنی مسیر جایگزین برای اتصال وجود دارد.
چرا low-link مفهوم «راه فرار» است؟
اگر Subtree یک Node بتواند با Back Edge به Ancestor بالاتر برگردد، حذف Parent اصلی لزوماً آن Subtree را جدا نمیکند.
مثلاً:
A
| \
B |
| |
C--+اگر B حذف شود، C هنوز شاید از طریق Back Edge به A برسد.
پس B لزوماً Articulation نیست.
Articulation Point در Graph دارای Cycle
وجود Cycle معمولاً Redundancy ایجاد میکند.
مثلاً Triangle:
A
|\
| \
B--Cهیچ رأس Articulation نیست.
اگر یکی حذف شود، دو رأس دیگر هنوز مستقیم Connected هستند.
Graph زنجیرهای
A -- B -- C -- D -- Eدر این Graph:
B
C
DArticulation Point هستند.
چون حذف هرکدام Chain را به دو بخش تقسیم میکند.
Star Graph
B
|
C -- A -- D
|
Eرأس:
AArticulation Point بسیار واضح است.
حذف A باعث میشود B، C، D و E کاملاً جدا شوند.
Complete Graph
در Complete Graph با حداقل سه رأس:
K_nهیچ Articulation Pointی وجود ندارد.
چون بین رأسها مسیرهای جایگزین زیادی وجود دارد.
Graph Disconnected
الگوریتم باید تمام Componentها را بررسی کند.
یک DFS از یک Start کافی نیست.
برای همین در کد:
for (const node of nodes) {
if (!visited.has(node)) {
dfs(node);
}
}داریم.
تفاوت Articulation Point با Degree بالا
Degree بالا الزاماً به معنی بحرانی بودن نیست.
مثلاً در Complete Graph هر Node Degree بالایی دارد، ولی هیچ Articulation Pointی نیست.
همچنین Node با Degree کم هم ممکن است بحرانی باشد.
پس:
degree ≠ articulation statusSingle Point of Failure
در طراحی سیستمها Articulation Point یک مدل ساده برای پیدا کردن:
Single Point of Failureاست.
مثلاً اگر Service یا Router خاصی تنها مسیر اتصال بین دو بخش باشد، Failure آن Connectivity را از بین میبرد.
اما باید توجه کرد که Graph Model دقیق باشد.
مثلاً اگر Failover، Load Balancer یا مسیر پشتیبان وجود دارد باید در Graph هم Edge/Node مناسب مدل شود.
کاربرد در Computer Networks
مدل:
Router / Switch = Vertex
Link = EdgeArticulation Point میتواند Router یا Switchی باشد که خرابی آن Network را Partition میکند.
کاربرد:
- redundancy analysis
- fault tolerance
- network redesign
- disaster planning
کاربرد در Data Center
فرض کنید چند Cluster از Serverها فقط از طریق یک Gateway خاص به هم متصلاند.
اگر آن Gateway Articulation Point باشد، Failure آن بخشهایی از Data Center را Isolate میکند.
این اطلاعات میتواند برای:
- redundancy
- secondary gateway
- topology redesign
استفاده شود.
کاربرد در حملونقل
مدل:
Station / Intersection = Vertex
Road / Rail Connection = Edgeاگر Station خاصی حذف شود و شبکه به چند بخش جدا تقسیم شود، آن Station از نظر Connectivity بحرانی است.
کاربرد در شبکههای برق
مدل ساده:
Substation = Vertex
Transmission connection = EdgeArticulation Point میتواند یک Substation حساس را نشان دهد.
البته Power Grid واقعی Constraintهای فیزیکی بسیار پیچیدهتری دارد و Graph Connectivity فقط یک Approximation ساختاری است.
کاربرد در Social Network
در یک Graph ارتباطی، ممکن است یک User یا Organization تنها Bridge انسانی بین دو گروه باشد.
اگر آن Node حذف شود، Network به چند Component تبدیل شود.
این نوع Node میتواند نقش:
broker
connector
gatekeeperداشته باشد.
اما Articulation Point با مفاهیمی مثل Betweenness Centrality یکی نیست.
تفاوت با Betweenness Centrality
Articulation Point یک ویژگی Binary است:
critical / not criticalاما Betweenness Centrality میسنجد یک Node چه مقدار روی Shortest Pathهای بین سایر Nodeها قرار دارد.
ممکن است Node:
- Articulation نباشد
- ولی Betweenness بسیار بالایی داشته باشد.
و بالعکس.
کاربرد در Microservice Architecture
اگر Service Dependency را به شکل Undirected Connectivity مدل کنیم، Articulation Point میتواند سرویس مرکزی با ریسک Failure را نشان دهد.
اما Dependency واقعی معمولاً Directed است.
پس استفاده مستقیم از Articulation Point نیاز به مدلسازی دقیق دارد.
برای Directed Graph مفاهیم متفاوتی ممکن است مناسبتر باشند.
Articulation Point معمولاً برای Undirected Graph است
الگوریتم کلاسیک Tarjan که اینجا بررسی میکنیم برای:
Undirected Graphاست.
برای Directed Graph، مفهوم Cut Vertex و Connectivity شرایط متفاوت و پیچیدهتری دارد.
پس نباید همین شرط:
low[child] >= disc[u]را بدون تغییر روی Directed Graph استفاده کنیم.
Biconnected Graph چیست؟
یک Graph بدونجهت Biconnected یا 2-vertex-connected است اگر:
- Connected باشد.
- هیچ Articulation Pointی نداشته باشد.
یعنی حذف هر یک Vertex، Graph باقیمانده را disconnected نمیکند.
این مفهوم معیار مهمی برای Redundancy است.
Biconnected Components
Articulation Pointها Graph را به بخشهایی تقسیم میکنند که داخل هرکدام Connectivity مقاومتری وجود دارد.
این بخشها:
Biconnected Componentsنامیده میشوند.
Articulation Point میتواند عضو چند Biconnected Component باشد.
Block-Cut Tree چیست؟
میتوان ساختار Graph را به یک Tree تبدیل کرد که در آن:
- یک نوع Node = Articulation Point
- نوع دیگر Node = Biconnected Component
باشد.
این ساختار:
Block-Cut Treeنام دارد.
برای Queryهای ساختاری و تحلیل Connectivity بسیار مفید است.
تفاوت Bridge و Articulation Point از نظر low
برای DFS Edge:
u → vBridge
اگر:
low[v] > disc[u]Edge (u,v) Bridge است.
Articulation Point
اگر u Root نباشد و:
low[v] >= disc[u]آنگاه u Articulation Point است.
این دو فرمول باید از هم تفکیک شوند.
آیا هر Endpoint یک Bridge، Articulation Point است؟
خیر.
مثلاً:
A -- BEdge یک Bridge است.
اما اگر فقط دو Node داشته باشیم، حذف A باعث میشود فقط B باقی بماند و بسته به تعریف component count، A الزاماً Articulation Point به مفهوم کلاسیک نیست.
پس Bridge و Articulation Point رابطه یکبهیک ندارند.
Edge Case: یک Node
Graph:
Aهیچ Articulation Pointی ندارد.
Edge Case: دو Node
A -- Bهیچکدام Articulation Point نیستند.
Edge Case: Root با یک Child
Root اگر فقط یک DFS Child داشته باشد:
children = 1Articulation Point محسوب نمیشود.
این یکی از رایجترین اشتباهات پیادهسازی است.
Edge Case: Root با دو Child
children > 1Root Articulation Point است.
Edge Case: Self-loop
Self-loop معمولاً Connectivity بین Componentهای مختلف ایجاد نمیکند.
نباید باعث شود Node اشتباهاً Articulation Point تشخیص داده شود.
Edge Case: Parallel Edge
در MultiGraph اگر بین Parent و Child چند Edge موازی داشته باشیم، Parent-edge tracking فقط با Vertex ممکن است کافی نباشد.
بهتر است Edge ID مستقل Track شود.
این موضوع مشابه الگوریتم Bridge است.
نسخه TypeScript
type Graph = Record<string, string[]>;
function findArticulationPoints(
graph: Graph
): string[] {
const nodes = new Set<string>();
for (const [u, neighbors] of Object.entries(graph)) {
nodes.add(u);
for (const v of neighbors) {
nodes.add(v);
}
}
const visited = new Set<string>();
const disc = new Map<string, number>();
const low = new Map<string, number>();
const parent = new Map<string, string>();
const result = new Set<string>();
let time = 0;
function dfs(u: string): void {
visited.add(u);
disc.set(u, ++time);
low.set(u, disc.get(u)!);
let children = 0;
for (const v of graph[u] ?? []) {
if (!visited.has(v)) {
children++;
parent.set(v, u);
dfs(v);
low.set(
u,
Math.min(
low.get(u)!,
low.get(v)!
)
);
const isRoot =
!parent.has(u);
if (
isRoot &&
children > 1
) {
result.add(u);
}
if (
!isRoot &&
low.get(v)! >= disc.get(u)!
) {
result.add(u);
}
} else if (
v !== parent.get(u)
) {
low.set(
u,
Math.min(
low.get(u)!,
disc.get(v)!
)
);
}
}
}
for (const node of nodes) {
if (!visited.has(node)) {
dfs(node);
}
}
return [...result];
}پیچیدگی زمانی
Tarjan DFS هر Vertex را یک بار Visit میکند.
هر Edge نیز تعداد محدودی بررسی میشود.
پس:
Time = O(V + E)پیچیدگی فضایی
ساختارهای:
- visited
- disc
- low
- parent
- recursion stack
تقریباً:
O(V)فضای کمکی میخواهند.
چرا Brute Force معمولاً انتخاب خوبی نیست؟
Brute Force از نظر مفهومی ساده است و برای تست کوچک مفید است، اما برای Graph بزرگ:
O(V × (V + E))بسیار گرانتر از Tarjan است.
Tarjan تمام اطلاعات مورد نیاز را در یک DFS جمع میکند.
Recursive DFS و Stack Overflow
اگر Graph بسیار عمیق باشد:
1 -- 2 -- 3 -- ... -- 100000Recursive DFS در JavaScript ممکن است Stack Overflow بدهد.
در Production میتوان:
- DFS Iterative پیچیدهتر
- Stack دستی
- یا Environment مناسب
را در نظر گرفت.
خطاهای رایج
1. استفاده از شرط Bridge
اشتباه:
low[v] > disc[u]برای Articulation Point.
درست:
low[v] >= disc[u]برای Non-root.
2. فراموش کردن Root Rule
Root فقط زمانی بحرانی است که:
children > 13. استفاده روی Directed Graph بدون تغییر
الگوریتم کلاسیک این مقاله برای Undirected Graph است.
4. فقط یک Component را DFS کردن
Graph ممکن است disconnected باشد.
5. اشتباه گرفتن Parent Edge با Back Edge
Edge برگشت مستقیم به Parent نباید low را مثل Back Edge عمومی Update کند.
6. فرض کردن Degree بالا یعنی Critical
Degree بهتنهایی کافی نیست.
7. فراموش کردن Nodeهایی که فقط در Neighbor List آمدهاند
باید مجموعه کامل Vertexها را بسازیم.
چه زمانی از Articulation Points استفاده کنیم؟
وقتی:
- Single Point of Failure میخواهیم.
- Network Reliability تحلیل میکنیم.
- Router یا Gateway بحرانی میخواهیم.
- Transportation Network تحلیل میکنیم.
- Infrastructure Connectivity مهم است.
- Graph Robustness بررسی میکنیم.
- Biconnected Components میخواهیم.
- Critical Vertex Analysis داریم.
چه زمانی مناسب نیست؟
اگر Edge بحرانی میخواهیم:
Bridgesاگر Shortest Path میخواهیم:
BFS / Dijkstraاگر Directed Graph و Mutual Reachability مهم است:
SCCیا الگوریتمهای مخصوص Directed Connectivity مناسبترند.
اگر میخواهیم اهمیت آماری Node در مسیرها را بسنجیم:
Centrality Metricsممکن است مفیدتر باشند.
جدول مقایسه
| مفهوم | چه چیزی بحرانی است؟ | شرط اصلی |
|---|---|---|
| Articulation Point | Vertex | حذف Vertex Componentها را زیاد کند |
| Bridge | Edge | حذف Edge Componentها را زیاد کند |
| SCC | گروه Vertex در Directed Graph | Mutual Reachability |
| Connected Component | گروه Vertex | Reachability در Undirected Graph |
| Betweenness Centrality | اهمیت Node | حضور روی Shortest Paths |
جمعبندی
Articulation Point رأسی است که حذف آن Connectivity Graph را ضعیف میکند و تعداد Connected Componentها را افزایش میدهد.
روش بهینه کلاسیک با DFS و دو مقدار انجام میشود:
disc[u]
low[u]برای Node غیرریشه:
low[child] >= disc[u]
→ u is articulationو برای Root:
DFS children > 1
→ root is articulationپیچیدگی:
O(V + E)است.
مهمترین تفاوت با Bridge:
Articulation Point
→ critical vertex
Bridge
→ critical edgePattern تشخیص مسئله نیز روشن است:
اگر یک Node از شبکه حذف شود،
آیا بخشی از Graph از بخش دیگر جدا میشود؟
→ Articulation Pointدر سیستمهای واقعی، این مفهوم میتواند اولین قدم برای پیدا کردن Single Point of Failure و طراحی Redundancy بهتر باشد.