Arian Soleimanzadeh
  • 홈
  • 블로그
  • 팟캐스트
  • 비디오
  • 문의
العربية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빠른 응답
홈/아티클/JavaScript와 TypeScript로 Kosaraju 알고리즘 구현하기
Kosaraju아티클

JavaScript와 TypeScript로 Kosaraju 알고리즘 구현하기

JavaScript와 TypeScript를 사용해 Kosaraju 알고리즘을 직접 구현합니다. 그래프 표현, DFS 종료 순서, 전치 그래프, SCC 추출, 복잡도와 실제 구현 시 주의사항까지 설명합니다.

2026년 8월 20일9 분 읽기1 조회수
#Kosaraju#JavaScript#TypeScript#Graph Algorithms#SCC#DFS#Software Engineering

Arian Soleimanzadeh

Software Engineer & Researcher

JavaScript와 TypeScript로 구현한 Kosaraju 알고리즘, DFS, Transpose Graph 및 SCC

Arian Soleimanzadeh

AI · 코드 · 제품

연구 + 엔지니어링
이 페이지에서
소개그래프 표현첫 번째 DFSTranspose Graph 만들기전체 JavaScript 구현TypeScript Generic 구현Microservice 예제시간 복잡도JavaScript 재귀 제한자주 하는 실수정리

소개

Kosaraju 알고리즘은 방향 그래프에서 Strongly Connected Components(SCC) 를 찾습니다.

이 글에서는 알고리즘의 이론을 실제 JavaScript와 TypeScript 코드로 옮겨 보겠습니다.

전체 흐름은 다음과 같습니다.

Code
123456789
원본 그래프 DFS
      ↓
Finish Order
      ↓
모든 간선 반전
      ↓
Transpose Graph DFS
      ↓
SCCs

그래프 표현

Adjacency List를 Map으로 표현할 수 있습니다.

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

이 방식은 특히 sparse graph에서 효율적입니다.


첫 번째 DFS

Kosaraju의 첫 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);
}

이 과정이 Finish 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;
}

전체 JavaScript 구현

JavaScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
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[]>;

Generic을 사용하면 정점이 숫자가 아니라 서비스 이름이어도 사용할 수 있습니다.

TypeScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
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;
}

Microservice 예제

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

Kosaraju는 다음과 같은 그룹을 찾을 수 있습니다.

Code
12
{user, payment, notification}
{report, analytics}

이는 실제 시스템에서 순환 의존성을 찾는 데 활용할 수 있습니다.


시간 복잡도

Code
123
첫 DFS      O(V + E)
Transpose   O(V + E)
두 번째 DFS O(V + E)

따라서 전체 시간 복잡도는:

Code
1
O(V + E)

입니다.


JavaScript 재귀 제한

매우 깊은 그래프에서는 recursive DFS가 다음 오류를 일으킬 수 있습니다.

Code
1
RangeError: Maximum call stack size exceeded

대규모 Production 환경에서는 iterative DFS를 고려하는 것이 좋습니다.


자주 하는 실수

대표적인 실수는 다음과 같습니다.

  • Finish 전에 Stack에 정점을 넣는 것
  • 두 번째 DFS 전에 visited를 초기화하지 않는 것
  • 두 번째 DFS를 원본 그래프에서 실행하는 것
  • outgoing edge가 없는 정점을 Map에 등록하지 않는 것
  • SCC 내부 순서가 항상 동일하다고 가정하는 것

정리

Kosaraju 구현의 핵심 흐름은 다음과 같습니다.

Code
123456789
DFS
↓
Finish Order
↓
Transpose
↓
DFS
↓
SCCs

Adjacency List를 사용하면 시간 복잡도는 O(V + E)입니다.

TypeScript의 Generic과 Type Safety를 이용하면 Module, Package, Microservice 의존성 분석과 같은 실제 시스템에서도 재사용하기 좋은 구조를 만들 수 있습니다.

가장 중요한 개념은 다음과 같습니다.

첫 번째 DFS는 올바른 처리 순서를 만들고, Transpose는 도달 방향을 뒤집으며, 두 번째 DFS는 SCC의 경계를 분리합니다.

이 페이지에서
소개그래프 표현첫 번째 DFSTranspose Graph 만들기전체 JavaScript 구현TypeScript Generic 구현Microservice 예제시간 복잡도JavaScript 재귀 제한자주 하는 실수정리

아티클 정보

게시 정보, 읽기 시간 및 조회 데이터입니다.

게시일

2026년 8월 20일

업데이트

2026년 8월 19일

읽기 시간

9 분 읽기

조회수

1

작성자

Arian Soleimanzadeh

다음 아티클

B2B CRM과 B2C CRM의 차이: 영업 프로세스부터 소프트웨어 아키텍처까지

깔끔하고 빠르며 아름다운 것을 함께 만들어 봅시다.

협업, 컨설팅, 제품 작업을 위한 빠른 문의입니다.

빠른 문의이메일 보내기
Arian Soleimanzadeh

현대적인 웹 엔지니어링, UI 시스템, 실용적인 AI 제품에 집중한 개인 포트폴리오 — 깨끗한 코드, 명확한 디자인.

빠른 링크

  • 소개
  • 블로그
  • 프로젝트
  • 문의

문의

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

가능 시간: 평일

보통 다음 시간 내에 답변합니다 24시간.

뉴스레터

글, 프로젝트, 새 릴리스에 대한 업데이트를 받아보세요.

© 2026 ariansoleimanzadeh.site — 모든 권리 보유.

LinkedIn