آرین سليمان زاده
  • الرئيسية
  • المدونة
  • البودكاست
  • الفيديوهات
  • تواصل
العربيةArabic
DeutschGerman
EnglishEnglish
فارسیPersian
한국어Korean
中文Chinese
اللوحة•تواصل سريع

Languages

Choose your interface locale

ar

العربية

Arabic

de

Deutsch

German

en

English

English

fa

فارسی

Persian

ko

한국어

Korean

zh

中文

Chinese

احجز موعداً

أرسل رسالة قصيرة — سأرد في أقرب وقت ممكن.

LinkedInاستجابة سريعة
الرئيسية/المقالات/تنفيذ خوارزمية Kosaraju باستخدام JavaScript وTypeScript
Kosarajuمقال

تنفيذ خوارزمية Kosaraju باستخدام JavaScript وTypeScript

شرح عملي لتنفيذ خوارزمية Kosaraju باستخدام JavaScript وTypeScript، بدءاً من تمثيل الرسم البياني وDFS وحتى بناء الرسم المعكوس واستخراج SCC وتحليل التعقيد والأخطاء الشائعة.

٢٠ أغسطس ٢٠٢٦9 دقيقة قراءة1 المشاهدات
#Kosaraju#JavaScript#TypeScript#Graph Algorithms#SCC#DFS#Directed Graph

Arian Soleimanzadeh

Software Engineer & Researcher

تنفيذ خوارزمية Kosaraju باستخدام JavaScript وTypeScript مع DFS والرسم المعكوس وSCC

Arian Soleimanzadeh

ذكاء اصطناعي · برمجة · منتج

بحث + هندسة
في هذه الصفحة
مقدمةتمثيل الرسم البيانيDFS الأول وترتيب الانتهاءإنشاء Transpose GraphDFS الثانيالتنفيذ الكامل بـ JavaScriptTypeScript Genericمثال Microservicesالتعقيدمشكلة Recursion في JavaScriptأخطاء شائعةالخلاصة

مقدمة

تُستخدم خوارزمية Kosaraju لاكتشاف Strongly Connected Components (SCCs) في الرسوم البيانية الموجهة.

بعد فهم الفكرة النظرية، سننتقل هنا إلى تنفيذ عملي باستخدام JavaScript وTypeScript.

الخوارزمية تعتمد على ثلاث مراحل رئيسية:

Code
123456789
DFS على الرسم الأصلي
        ↓
ترتيب انتهاء العقد
        ↓
عكس جميع الحواف
        ↓
DFS على الرسم المعكوس
        ↓
SCCs

تمثيل الرسم البياني

يمكن استخدام Adjacency List بواسطة Map:

JavaScript
1234567
const graph = new Map([
  [0, [1]],
  [1, [2, 3]],
  [2, [0]],
  [3, [4]],
  [4, [3]]
]);

يمثل هذا:

Code
12345
0 → 1
1 → 2, 3
2 → 0
3 → 4
4 → 3

DFS الأول وترتيب الانتهاء

يجب إضافة العقدة إلى Stack بعد معالجة جميع جيرانها:

JavaScript
1234567891011
function fillOrder(node, graph, visited, stack) {
  visited.add(node);

  for (const neighbor of graph.get(node) ?? []) {
    if (!visited.has(neighbor)) {
      fillOrder(neighbor, graph, visited, stack);
    }
  }

  stack.push(node);
}

مكان stack.push(node) مهم جداً لأن Kosaraju يحتاج إلى Finish Order وليس Discovery Order.


إنشاء Transpose Graph

نعكس كل حافة:

Code
1
A → B

تصبح:

Code
1
B → A
JavaScript
12345678910111213141516171819
function transposeGraph(graph) {
  const transpose = new Map();

  for (const node of graph.keys()) {
    transpose.set(node, []);
  }

  for (const [node, neighbors] of graph.entries()) {
    for (const neighbor of neighbors) {
      if (!transpose.has(neighbor)) {
        transpose.set(neighbor, []);
      }

      transpose.get(neighbor).push(node);
    }
  }

  return transpose;
}

DFS الثاني

بعد إنشاء الرسم المعكوس نمسح حالة الزيارة:

JavaScript
1
visited.clear();

ثم نأخذ العقد من Stack واحدة تلو الأخرى وننفذ DFS على الرسم المعكوس.

كل DFS جديد يمثل SCC واحدة.


التنفيذ الكامل بـ JavaScript

JavaScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
function kosaraju(graph) {
  const visited = new Set();
  const stack = [];

  function fillOrder(node) {
    visited.add(node);

    for (const neighbor of graph.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        fillOrder(neighbor);
      }
    }

    stack.push(node);
  }

  for (const node of graph.keys()) {
    if (!visited.has(node)) {
      fillOrder(node);
    }
  }

  const transpose = new Map();

  for (const node of graph.keys()) {
    transpose.set(node, []);
  }

  for (const [node, neighbors] of graph.entries()) {
    for (const neighbor of neighbors) {
      if (!transpose.has(neighbor)) {
        transpose.set(neighbor, []);
      }

      transpose.get(neighbor).push(node);
    }
  }

  visited.clear();

  const components = [];

  function collect(node, component) {
    visited.add(node);
    component.push(node);

    for (const neighbor of transpose.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        collect(neighbor, component);
      }
    }
  }

  while (stack.length > 0) {
    const node = stack.pop();

    if (!visited.has(node)) {
      const component = [];
      collect(node, component);
      components.push(component);
    }
  }

  return components;
}

TypeScript Generic

يمكن تعريف الرسم البياني كالتالي:

TypeScript
1
type Graph<T> = Map<T, T[]>;

وبذلك يمكن استخدام أرقام أو أسماء خدمات أو أي معرفات أخرى كعقد.

TypeScript
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
function kosaraju<T>(graph: Graph<T>): T[][] {
  const visited = new Set<T>();
  const stack: T[] = [];

  const fillOrder = (node: T): void => {
    visited.add(node);

    for (const neighbor of graph.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        fillOrder(neighbor);
      }
    }

    stack.push(node);
  };

  for (const node of graph.keys()) {
    if (!visited.has(node)) {
      fillOrder(node);
    }
  }

  const transpose: Graph<T> = new Map();

  for (const node of graph.keys()) {
    transpose.set(node, []);
  }

  for (const [node, neighbors] of graph.entries()) {
    for (const neighbor of neighbors) {
      if (!transpose.has(neighbor)) {
        transpose.set(neighbor, []);
      }

      transpose.get(neighbor)!.push(node);
    }
  }

  visited.clear();

  const components: T[][] = [];

  const collect = (node: T, component: T[]): void => {
    visited.add(node);
    component.push(node);

    for (const neighbor of transpose.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        collect(neighbor, component);
      }
    }
  };

  while (stack.length > 0) {
    const node = stack.pop()!;

    if (!visited.has(node)) {
      const component: T[] = [];
      collect(node, component);
      components.push(component);
    }
  }

  return components;
}

مثال Microservices

TypeScript
12345678
const services: Graph<string> = new Map([
  ["user", ["payment"]],
  ["payment", ["notification"]],
  ["notification", ["user"]],
  ["report", ["analytics"]],
  ["analytics", ["report"]],
  ["admin", ["user"]]
]);

يمكن للخوارزمية اكتشاف مجموعات مثل:

Code
123
{user, payment, notification}
{report, analytics}
{admin}

وتشير أول مجموعتين إلى اعتماديات دائرية.


التعقيد

DFS الأول:

Code
1
O(V + E)

إنشاء الرسم المعكوس:

Code
1
O(V + E)

DFS الثاني:

Code
1
O(V + E)

إذن التعقيد الكلي:

Code
1
O(V + E)

والذاكرة أيضاً من رتبة:

Code
1
O(V + E)

مشكلة Recursion في JavaScript

في الرسوم العميقة جداً قد يؤدي DFS المتكرر إلى:

Code
1
RangeError: Maximum call stack size exceeded

لذلك يُفضل استخدام DFS iterative عند التعامل مع رسوم ضخمة في بيئة Production.


أخطاء شائعة

من أهم الأخطاء:

  • وضع العقدة في Stack قبل إنهاء جيرانها.
  • عدم تنفيذ visited.clear() قبل DFS الثاني.
  • تنفيذ DFS الثاني على الرسم الأصلي بدلاً من Transpose Graph.
  • نسيان العقد التي لا تمتلك حواف خارجة.
  • الاعتماد على ترتيب عناصر SCC عند كتابة الاختبارات.

الخلاصة

تنفيذ Kosaraju يعتمد على فكرة بسيطة ولكن ترتيب الخطوات مهم جداً:

Code
123456789
DFS الأول
↓
Finish Order
↓
Transpose
↓
DFS الثاني
↓
SCCs

باستخدام Adjacency List تعمل الخوارزمية في O(V + E).

نسخة TypeScript مناسبة بشكل خاص لتطبيقات حقيقية مثل تحليل Circular Dependencies بين Modules أو Packages أو Microservices بفضل Generic Types وType Safety.

الفكرة الأساسية التي يجب تذكرها هي:

DFS الأول يحدد ترتيب المعالجة، والرسم المعكوس يقلب اتجاه الوصول بين المكونات، ثم يكشف DFS الثاني حدود كل SCC.

في هذه الصفحة
مقدمةتمثيل الرسم البيانيDFS الأول وترتيب الانتهاءإنشاء Transpose GraphDFS الثانيالتنفيذ الكامل بـ JavaScriptTypeScript Genericمثال Microservicesالتعقيدمشكلة Recursion في JavaScriptأخطاء شائعةالخلاصة

تفاصيل المقال

بيانات النشر ووقت القراءة وعدد المشاهدات.

تاريخ النشر

٢٠ أغسطس ٢٠٢٦

آخر تحديث

١٩ أغسطس ٢٠٢٦

وقت القراءة

9 دقيقة قراءة

المشاهدات

1

الكاتب

Arian Soleimanzadeh

المقال التالي

ما الفرق بين B2B CRM وB2C CRM؟ من عملية المبيعات إلى بنية البرمجيات

لنبنِ شيئاً نظيفاً، سريعاً، وجميلاً.

تواصل سريع للتعاون، أو الاستشارة، أو العمل على المنتجات.

تواصل سريعراسلني عبر البريد
آرین سليمان زاده

معرض أعمال شخصي يركز على هندسة الويب الحديثة، وأنظمة الواجهات، ومنتجات الذكاء الاصطناعي العملية — كود نظيف، وتصميم نقي.

روابط سريعة

  • نبذة
  • المدونة
  • المشاريع
  • تواصل

تواصل

  • info@ariansoleimanzadeh.site
  • soleimanzadeh.a.work@gmail.com

التوفر: أيام الأسبوع

عادةً يتم الرد خلال 24 ساعة.

النشرة البريدية

احصل على تحديثات حول المقالات، والمشاريع، والإصدارات الجديدة.

© 2026 ariansoleimanzadeh.site — جميع الحقوق محفوظة.

لينكدإن