Graph Algorithms (그래프 알고리즘)

"그래프 문제에서 정점을 어떤 순서로 방문할지(BFS·DFS·위상 정렬), 모든 정점을 최소 비용으로 어떻게 연결할지(MST·Kruskal)는 각각 어떤 자료구조와 전제를 쓰고, 최단 경로 문제와는 어디서 갈리는가?" 이 페이지는 그 질문에 답한다. 원문 노트들은 BFS·DFS를 탐색 기법으로, 위상 정렬을 정렬·순서 문제로, MST와 Kruskal을 최소 신장 트리로 따로 묶는다.[1] 이 페이지는 이를 "정점을 방문하는 순서를 정하는 문제"와 "간선 가중치의 합을 줄이는 문제" 두 갈래로 다시 묶어 읽는다. 이 두 갈래 구분은 원문 분류를 이 Wiki가 재구성한 것이다. 가중치 경로의 최단 거리는 Shortest Path Algorithms가 따로 다룬다.

탐색: BFS와 DFS

BFS (너비우선탐색)

BFS(V)
	큐 생성
	방문관리 배열 생성
	시작 정점 V를 큐에 삽입
	정점 V를 방문한 것으로 표시
	while (큐가 비어 있지 않은 경우) {
		t <- 큐의 첫 번째 원소 반환
		for (t와 연결된 모든 간선에 대해) {
			u <- t의 인접 정점
			u가 방문되지 않은 곳이면,
			u를 큐에 넣고, 방문한 것으로 표시
		}
	}

DFS (깊이우선탐색)

void dfs(int v)
{
	nodePointer w;
	visited[v]  = true;
	printf("%d",v);
	for(w = graph[v]; w; w=w->link) 
		if(!visited[w->vertex]) dfs(w->vertex);
}
1. 시작 정점(= v) 결정
2. stack의 top(= w)읽기
3. 방문하지 않은 w가 존재 => push(w) & visite 표시
4. 방문하지 않은 w가 없음 => pop()
5. 스택이 공백이 될 때 까지 2~4번 반복 수행
int x[4] = {0, 0, 1, -1};
int y[4] = {1, -1, 0, 0};
// ...
while (!s.empty())
{
    a = s.top();
    s.pop();
    if (u[a.first][a.second] == 1 && !v[a.first][a.second])
    {
        v[a.first][a.second] = true;
        // 4방향 탐색
        for (int k = 0; k < 4; ++k)
        {
            int nx = a.first + x[k];
            int ny = a.second + y[k];
            // 경계 확인 및 조건 확인
            if (nx >= 0 && nx < n && ny >= 0 && ny < n &&
                u[nx][ny] == 1 && !v[nx][ny])
            {
                s.push({nx, ny});
            }
        }
    }
}

BFS와 DFS 비교

항목 BFS DFS
넓혀 가는 방식 시작점의 인접 정점을 모두 방문한 뒤, 그 정점들을 시작점으로 다시 넓힌다 스택 top의 미방문 인접 정점으로 내려가고, 없으면 pop해 되돌아간다(반복 의사코드 기준)
자료구조 큐(선입선출) 재귀 호출 또는 스택
방문 표시 시점 큐에 넣을 때(의사코드) push할 때(의사코드) / pop한 뒤(원문 C++ 코드)

백트래킹과의 관계

위상 정렬 (Topological Sort)

Kahn 알고리즘

int[] inDegree = new int[V + 1];
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i <= V; i++) graph.add(new ArrayList<>());

// 간선 입력
for (int[] edge : edges) {
    graph.get(edge[0]).add(edge[1]);
    inDegree[edge[1]]++;
}

Queue<Integer> queue = new LinkedList<>();
for (int i = 1; i <= V; i++)
    if (inDegree[i] == 0) queue.offer(i);

List<Integer> result = new ArrayList<>();
while (!queue.isEmpty()) {
    int node = queue.poll();
    result.add(node);
    for (int next : graph.get(node)) {
        inDegree[next]--;
        if (inDegree[next] == 0) queue.offer(next);
    }
}

if (result.size() != V) {
    System.out.println("사이클 존재");
}

결과가 유일하지 않은 이유

flowchart LR
    n5((5)) --> n2((2)) --> n3((3)) --> n1((1))
    n5 --> n0((0))
    n4((4)) --> n0
    n4 --> n1

사이클 판별

활용과 DFS 기반 방식

상황 설명
선수 과목 이수 특정 강의를 듣기 위해 먼저 들어야 할 강의 순서 결정
빌드 시스템 의존성 있는 모듈의 컴파일 순서 결정
작업 스케줄링 선후 관계가 있는 작업들의 실행 순서 결정
패키지 설치 npm/pip 등 의존성 패키지 설치 순서
항목 Kahn (BFS 기반) DFS 기반
구현 In-degree + Queue 재귀 DFS + Stack
사이클 탐지 result.size() != V 방문 상태 3가지 관리
직관성 높음 낮음
실전 선호 일반적 드묾

최소 비용 연결: MST와 Kruskal

최단 경로와의 경계

관련

테스트 질문

출처


  1. _Algorithm.md 24-39행 — 분류 제목 "정렬·순서", "탐색 기법", "최소 신장 트리 (MST)"와 항목 설명 "DAG 선형 나열, In-degree 기반", "너비우선탐색, 큐", "깊이우선탐색, 재귀/스택", "MST 개념", "크루스칼, 간선 정렬". '방문 순서'와 '가중치 합' 두 갈래로 다시 묶은 것은 이 Wiki의 재구성이다. ↩︎

  2. BFS.md 8-9행 — "너비우선탐색은 탐색 시작점의 인접한 정점을 먼저 모두 차례로 방문한 후에 방문했던 정점을 시작점으로 하여 다시 인접한 정점들을 차례로 방문하는 방식", "선입선출 형태의 자료구조인 큐를 활용함." ↩︎ ↩︎ ↩︎

  3. BFS.md 11-25행 의사코드 — "시작 정점 V를 큐에 삽입", "정점 V를 방문한 것으로 표시", "u를 큐에 넣고, 방문한 것으로 표시". 코드 블록의 언어 표시는 원문에서 pascal이다. ↩︎ ↩︎

  4. DFS.md 12-13·45행 — "recursive(visit flag)", "iterative(stack + visit flag)"; _Algorithm.md 29행 — "깊이우선탐색, 재귀/스택". ↩︎ ↩︎

  5. DFS.md 15-43행 — 인접 행렬 코드의 "if (arr[v][i] == 1 && visited[i] == 0) // edge가 있고 방문하지 않은 노드일 경우", "for (int i = 0; i < N; i++)", "if (i == N) // 방문하지 않은 노드가 없을 경우"와, node 코드 "for(w = graph[v]; w; w=w->link)", "if(!visited[w->vertex]) dfs(w->vertex);". 28-29행이 실행되지 않는다는 판독은 원문에 없고 이 Wiki가 코드를 읽어 덧붙였다. ↩︎ ↩︎ ↩︎

  6. DFS.md 46-53행 의사코드 — "stack의 top(= w)읽기", "방문하지 않은 w가 존재 => push(w) & visite 표시", "방문하지 않은 w가 없음 => pop()", "스택이 공백이 될 때 까지 2~4번 반복 수행". 원문 표기 "visite"는 그대로 두었다. ↩︎ ↩︎ ↩︎

  7. DFS.md 55-190행 C++ 코드 — "int x[4] = {0, 0, 1, -1};", "int y[4] = {1, -1, 0, 0};", "s.pop();", "v[a.first][a.second] = true;", "// 4방향 탐색", "// 경계 확인 및 조건 확인", "if (u[i][j] == 1 && !v[i][j]) // 시작 노드", "s.push({nx, ny});". 원문 코드는 줄마다 빈 줄이 끼어 있어 이 페이지에서는 빈 줄을 빼고 들여쓰기를 줄였다. 출력·집계가 없다는 점과 중복 push 분석은 이 Wiki가 코드를 읽고 덧붙였다. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  8. 구현 유형 접근법.md 32-43행 — "방향 벡터 (상하좌우)", "int[] dx = {-1, 1, 0, 0};", "boolean inRange(int x, int y, int N, int M) {". 두 노트의 도구가 같은 역할이라는 연결은 이 Wiki의 해석이다. ↩︎

  9. DFS.md 9-11행 — "유일하게 결정X", "why? 순서가 없기 때문에" ↩︎

  10. Backtracking.md 11-14행 — "현재 경로가 해가 될 수 없다고 판단되면 이전 단계로 되돌아가(Backtrack) 다른 경로를 탐색하는 기법.", "깊이 우선 탐색(DFS)의 최적화 형태."; _Algorithm.md 30행 — "DFS + 가지치기, 상태 공간 트리". ↩︎

  11. Topological Sort.md 11-13행 — "순서가 정해진 작업들을 차례대로 수행하기 위해", "방향 그래프의 정점을 선형으로 나열", "선후 관계를 만족하는 순서를 찾는 것." ↩︎

  12. Topological Sort.md 15-19행 — "사이클이 없는 방향 그래프(DAG, Directed Acyclic Graph)에서만 유효.", "선후 관계가 순환되어", "결정 불가능". 원문은 이 상태를 "교착 상태(Deadlock)"라고 비유하지만, OS의 교착 상태와 혼동될 수 있어 이 페이지에서는 그 비유를 쓰지 않았다. ↩︎

  13. Topological Sort.md 21-28행 — "어떤 노드로 들어오는 간선의 수.", "지금 당장 처리 가능", "먼저 처리되어야 할 노드가 아직 남아있음", "새롭게 0이 되는 노드를 이어서 처리하는 방식으로 동작한다." ↩︎ ↩︎

  14. Topological Sort.md 30-37행 — "모든 노드의 진입 차수 계산", "진입 차수 = 0인 노드를 큐에 삽입", "큐에서 노드 꺼내 결과 리스트에 추가", "해당 노드에서 출발하는 간선 제거 (인접 노드의 진입 차수 1 감소)", "새로 진입 차수 = 0이 된 노드를 큐에 삽입", "큐가 빌 때까지 반복"; 74-102행 Java 코드("for (int i = 1; i <= V; i++)"). ↩︎ ↩︎

  15. Topological Sort.md 124행 — "O(V + E) — 모든 정점과 간선을 한 번씩 처리." ↩︎

  16. Topological Sort.md 41-46·58-63행 — 간선 목록 "5 → 2 → 3 → 1", "5 → 0", "4 → 0", "4 → 1"과 "진입 차수 0인 노드가 동시에 여러 개라면 어느 것을 먼저 처리하느냐에 따라 결과가 달라진다.", "위 예시에서 초기 큐에 4와 5가 동시에 들어있으므로", "사전순으로 가장 빠른 순서를 요구하는 문제라면". 원문 48-56행의 예시 trace 표는 같은 노트의 간선 목록과 맞지 않는다(4를 처리한 뒤에도 0과 1의 진입 차수가 1인데 큐에 넣었고, 최종 결과가 간선 3 → 1을 어긴다). 그래서 이 페이지에 옮기지 않았다. ↩︎ ↩︎ ↩︎

  17. Topological Sort.md 67-70·99-101행 — "결과 리스트의 크기 < V → 큐가 일찍 비었음 → 그래프에 사이클 존재", "서로가 서로의 진입 차수를 유지시키기 때문에 절대 0이 될 수 없다.", "큐에 한 번도 들어가지 못하고 result에서 누락된다.", "if (result.size() != V) {" ↩︎ ↩︎

  18. Topological Sort.md 104-111행 — "특정 강의를 듣기 위해 먼저 들어야 할 강의 순서 결정", "의존성 있는 모듈의 컴파일 순서 결정", "선후 관계가 있는 작업들의 실행 순서 결정", "npm/pip 등 의존성 패키지 설치 순서" ↩︎

  19. Topological Sort.md 113-120행 — "Kahn's (BFS 기반)", "In-degree + Queue", "재귀 DFS + Stack", "방문 상태 3가지 관리", "| 직관성 | 높음 | 낮음 |", "| 실전 선호 | 일반적 | 드묾 |" ↩︎

  20. MST.md 12-16행 — "n개의 정점으로 이루어진 무향 그래프에서 n개의 정점과 n-1개의 간선으로 이루어진 트리", "무향 가중치 그래프에서 신장 트리를 구성하는 간선들의 가중치의 합이 최소인 신장 트리" ↩︎ ↩︎

  21. KRUSKAL.md 8-12행 — "간선을 하나씩 선택해서", "최초, 모든 간선을 가중치에 따라 오름차순으로 정렬", "가중치가 가장 낮은 간선부터 선택하면서 트리를 증가시킴", "사이클이 존재하면 남아 있는 간선 중 그 다음으로 가중치가 낮은 간선 선택", "n-1 개의 간선이 선택될 때까지 2를 반복" ↩︎

  22. MST.md 8-10행 — "그래프에서 최소 비용 문제", "모든 정점을 연결하는 간선들의 가중치의 합이 최소가 되는 트리", "두 정점 사이의 최소 비용의 경로 찾기" ↩︎