본문 바로가기
코딩테스트1

[코딩 테스트] 12일차: 그래프 알고리즘

by cogito21_js 2024. 9. 12.
반응형

그래프 알고리즘

그래프 알고리즘은 노드(정점)와 노드를 연결하는 에지(간선)로 구성된 자료구조인 그래프를 탐색하거나 분석하는 알고리즘입니다. 이번 글에서는 깊이 우선 탐색(DFS), 너비 우선 탐색(BFS), 그리고 최단 경로 알고리즘(Dijkstra, Floyd-Warshall)에 대해 알아보겠습니다.

깊이 우선 탐색(DFS)

깊이 우선 탐색(DFS)은 그래프의 모든 정점을 깊게 탐색하는 알고리즘입니다. 스택(또는 재귀)을 사용하여 구현할 수 있습니다.

예제: DFS 구현

JavaScript

class Graph {
  constructor() {
    this.adjList = new Map();
  }

  addVertex(v) {
    if (!this.adjList.has(v)) {
      this.adjList.set(v, []);
    }
  }

  addEdge(v, w) {
    this.adjList.get(v).push(w);
    this.adjList.get(w).push(v);
  }

  dfs(startingNode) {
    let visited = new Set();
    this._dfsUtil(startingNode, visited);
  }

  _dfsUtil(v, visited) {
    visited.add(v);
    console.log(v);

    let neighbors = this.adjList.get(v);
    for (let neighbor of neighbors) {
      if (!visited.has(neighbor)) {
        this._dfsUtil(neighbor, visited);
      }
    }
  }
}

let g = new Graph();
let vertices = ['A', 'B', 'C', 'D', 'E', 'F'];

for (let i = 0; i < vertices.length; i++) {
  g.addVertex(vertices[i]);
}

g.addEdge('A', 'B');
g.addEdge('A', 'D');
g.addEdge('A', 'E');
g.addEdge('B', 'C');
g.addEdge('D', 'E');
g.addEdge('E', 'F');
g.addEdge('E', 'C');
g.addEdge('C', 'F');

console.log("DFS:");
g.dfs('A');

Python

class Graph:
    def __init__(self):
        self.adj_list = {}

    def add_vertex(self, v):
        if v not in self.adj_list:
            self.adj_list[v] = []

    def add_edge(self, v, w):
        if v in self.adj_list and w in self.adj_list:
            self.adj_list[v].append(w)
            self.adj_list[w].append(v)

    def dfs(self, starting_node):
        visited = set()
        self._dfs_util(starting_node, visited)

    def _dfs_util(self, v, visited):
        visited.add(v)
        print(v, end=' ')

        for neighbor in self.adj_list[v]:
            if neighbor not in visited:
                self._dfs_util(neighbor, visited)

g = Graph()
vertices = ['A', 'B', 'C', 'D', 'E', 'F']

for vertex in vertices:
    g.add_vertex(vertex)

g.add_edge('A', 'B')
g.add_edge('A', 'D')
g.add_edge('A', 'E')
g.add_edge('B', 'C')
g.add_edge('D', 'E')
g.add_edge('E', 'F')
g.add_edge('E', 'C')
g.add_edge('C', 'F')

print("DFS:")
g.dfs('A') # A B C E F D

너비 우선 탐색(BFS)

너비 우선 탐색(BFS)은 그래프의 모든 정점을 너비 우선으로 탐색하는 알고리즘입니다. 큐를 사용하여 구현할 수 있습니다.

예제: BFS 구현

JavaScript

class Graph {
  constructor() {
    this.adjList = new Map();
  }

  addVertex(v) {
    if (!this.adjList.has(v)) {
      this.adjList.set(v, []);
    }
  }

  addEdge(v, w) {
    this.adjList.get(v).push(w);
    this.adjList.get(w).push(v);
  }

  bfs(startingNode) {
    let visited = new Set();
    let queue = [startingNode];

    visited.add(startingNode);

    while (queue.length > 0) {
      let v = queue.shift();
      console.log(v);

      let neighbors = this.adjList.get(v);
      for (let neighbor of neighbors) {
        if (!visited.has(neighbor)) {
          visited.add(neighbor);
          queue.push(neighbor);
        }
      }
    }
  }
}

let g = new Graph();
let vertices = ['A', 'B', 'C', 'D', 'E', 'F'];

for (let i = 0; i < vertices.length; i++) {
  g.addVertex(vertices[i]);
}

g.addEdge('A', 'B');
g.addEdge('A', 'D');
g.addEdge('A', 'E');
g.addEdge('B', 'C');
g.addEdge('D', 'E');
g.addEdge('E', 'F');
g.addEdge('E', 'C');
g.addEdge('C', 'F');

console.log("BFS:");
g.bfs('A');

Python

class Graph:
    def __init__(self):
        self.adj_list = {}

    def add_vertex(self, v):
        if v not in self.adj_list:
            self.adj_list[v] = []

    def add_edge(self, v, w):
        if v in self.adj_list and w in self.adj_list:
            self.adj_list[v].append(w)
            self.adj_list[w].append(v)

    def bfs(self, starting_node):
        visited = set()
        queue = [starting_node]

        visited.add(starting_node)

        while queue:
            v = queue.pop(0)
            print(v, end=' ')

            for neighbor in self.adj_list[v]:
                if neighbor not in visited:
                    visited.add(neighbor)
                    queue.append(neighbor)

g = Graph()
vertices = ['A', 'B', 'C', 'D', 'E', 'F']

for vertex in vertices:
    g.add_vertex(vertex)

g.add_edge('A', 'B')
g.add_edge('A', 'D')
g.add_edge('A', 'E')
g.add_edge('B', 'C')
g.add_edge('D', 'E')
g.add_edge('E', 'F')
g.add_edge('E', 'C')
g.add_edge('C', 'F')

print("BFS:")
g.bfs('A') # A B D E C F

최단 경로 알고리즘

Dijkstra 알고리즘

Dijkstra 알고리즘은 그래프에서 한 정점에서 다른 정점까지의 최단 경로를 찾는 알고리즘입니다. 가중치가 있는 그래프에서 사용되며, 우선순위 큐를 이용해 구현할 수 있습니다.

 

예제: Dijkstra 알고리즘 구현

JavaScript

class PriorityQueue {
  constructor() {
    this.items = [];
  }

  enqueue(element, priority) {
    let queueElement = { element, priority };
    let added = false;

    for (let i = 0; i < this.items.length; i++) {
      if (queueElement.priority < this.items[i].priority) {
        this.items.splice(i, 0, queueElement);
        added = true;
        break;
      }
    }

    if (!added) {
      this.items.push(queueElement);
    }
  }

  dequeue() {
    return this.items.shift();
  }

  isEmpty() {
    return this.items.length === 0;
  }
}

class Graph {
  constructor() {
    this.adjList = new Map();
  }

  addVertex(v) {
    if (!this.adjList.has(v)) {
      this.adjList.set(v, []);
    }
  }

  addEdge(v, w, weight) {
    this.adjList.get(v).push({ node: w, weight });
    this.adjList.get(w).push({ node: v, weight });
  }

  dijkstra(start) {
    let distances = {};
    let pq = new PriorityQueue();
    let prev = {};

    this.adjList.forEach((_, vertex) => {
      distances[vertex] = Infinity;
      prev[vertex] = null;
    });

    distances[start] = 0;
    pq.enqueue(start, 0);

    while (!pq.isEmpty()) {
      let { element: current } = pq.dequeue();

      this.adjList.get(current).forEach(neighbor => {
        let distance = distances[current] + neighbor.weight;

        if (distance < distances[neighbor.node]) {
          distances[neighbor.node] = distance;
          prev[neighbor.node] = current;
          pq.enqueue(neighbor.node, distance);
        }
      });
    }

    return distances;
  }
}

let g = new Graph();
g.addVertex('A');
g.addVertex('B');
g.addVertex('C');
g.addVertex('D');
g.addVertex('E');


g.addVertex('F');

g.addEdge('A', 'B', 4);
g.addEdge('A', 'C', 2);
g.addEdge('B', 'E', 3);
g.addEdge('C', 'D', 2);
g.addEdge('C', 'F', 4);
g.addEdge('D', 'E', 3);
g.addEdge('D', 'F', 1);
g.addEdge('E', 'F', 1);

console.log(g.dijkstra('A'));

Python

import heapq

class Graph:
    def __init__(self):
        self.adj_list = {}

    def add_vertex(self, v):
        if v not in self.adj_list:
            self.adj_list[v] = []

    def add_edge(self, v, w, weight):
        self.adj_list[v].append((w, weight))
        self.adj_list[w].append((v, weight))

    def dijkstra(self, start):
        distances = {vertex: float('infinity') for vertex in self.adj_list}
        distances[start] = 0
        pq = [(0, start)]
        prev = {vertex: None for vertex in self.adj_list}

        while pq:
            current_distance, current_vertex = heapq.heappop(pq)

            if current_distance > distances[current_vertex]:
                continue

            for neighbor, weight in self.adj_list[current_vertex]:
                distance = current_distance + weight

                if distance < distances[neighbor]:
                    distances[neighbor] = distance
                    prev[neighbor] = current_vertex
                    heapq.heappush(pq, (distance, neighbor))

        return distances

g = Graph()
g.add_vertex('A')
g.add_vertex('B')
g.add_vertex('C')
g.add_vertex('D')
g.add_vertex('E')
g.add_vertex('F')

g.add_edge('A', 'B', 4)
g.add_edge('A', 'C', 2)
g.add_edge('B', 'E', 3)
g.add_edge('C', 'D', 2)
g.add_edge('C', 'F', 4)
g.add_edge('D', 'E', 3)
g.add_edge('D', 'F', 1)
g.add_edge('E', 'F', 1)

print(g.dijkstra('A'))

Floyd-Warshall 알고리즘

Floyd-Warshall 알고리즘은 그래프에서 모든 정점 쌍 간의 최단 경로를 찾는 알고리즘입니다. 시간 복잡도는 O(n^3)입니다.

 

예제: Floyd-Warshall 알고리즘 구현

JavaScript

function floydWarshall(graph) {
  let dist = [];
  let size = graph.length;

  for (let i = 0; i < size; i++) {
    dist[i] = [];
    for (let j = 0; j < size; j++) {
      if (i === j) {
        dist[i][j] = 0;
      } else if (graph[i][j] !== 0) {
        dist[i][j] = graph[i][j];
      } else {
        dist[i][j] = Infinity;
      }
    }
  }

  for (let k = 0; k < size; k++) {
    for (let i = 0; i < size; i++) {
      for (let j = 0; j < size; j++) {
        if (dist[i][j] > dist[i][k] + dist[k][j]) {
          dist[i][j] = dist[i][k] + dist[k][j];
        }
      }
    }
  }

  return dist;
}

let graph = [
  [0, 3, 0, 0, 0, 0],
  [0, 0, 1, 0, 0, 0],
  [0, 0, 0, 7, 0, 2],
  [0, 0, 0, 0, 0, 3],
  [0, 0, 0, 2, 0, 0],
  [0, 0, 0, 0, 1, 0]
];

console.log(floydWarshall(graph));

Python

def floyd_warshall(graph):
    size = len(graph)
    dist = [[float('inf')] * size for _ in range(size)]

    for i in range(size):
        for j in range(size):
            if i == j:
                dist[i][j] = 0
            elif graph[i][j] != 0:
                dist[i][j] = graph[i][j]

    for k in range(size):
        for i in range(size):
            for j in range(size):
                if dist[i][j] > dist[i][k] + dist[k][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]

    return dist

graph = [
    [0, 3, 0, 0, 0, 0],
    [0, 0, 1, 0, 0, 0],
    [0, 0, 0, 7, 0, 2],
    [0, 0, 0, 0, 0, 3],
    [0, 0, 0, 2, 0, 0],
    [0, 0, 0, 0, 1, 0]
]

print(floyd_warshall(graph))

연습 문제

문제 1: 두 정점 간의 최단 경로 찾기

주어진 그래프에서 두 정점 간의 최단 경로를 찾는 프로그램을 작성하세요.

JavaScript

class Graph {
  constructor() {
    this.adjList = new Map();
  }

  addVertex(v) {
    if (!this.adjList.has(v)) {
      this.adjList.set(v, []);
    }
  }

  addEdge(v, w, weight) {
    this.adjList.get(v).push({ node: w, weight });
    this.adjList.get(w).push({ node: v, weight });
  }

  dijkstra(start, end) {
    let distances = {};
    let pq = new PriorityQueue();
    let prev = {};

    this.adjList.forEach((_, vertex) => {
      distances[vertex] = Infinity;
      prev[vertex] = null;
    });

    distances[start] = 0;
    pq.enqueue(start, 0);

    while (!pq.isEmpty()) {
      let { element: current } = pq.dequeue();

      if (current === end) {
        let path = [];
        while (prev[current]) {
          path.push(current);
          current = prev[current];
        }
        path.push(start);
        return path.reverse();
      }

      this.adjList.get(current).forEach(neighbor => {
        let distance = distances[current] + neighbor.weight;

        if (distance < distances[neighbor.node]) {
          distances[neighbor.node] = distance;
          prev[neighbor.node] = current;
          pq.enqueue(neighbor.node, distance);
        }
      });
    }

    return [];
  }
}

let g = new Graph();
g.addVertex('A');
g.addVertex('B');
g.addVertex('C');
g.addVertex('D');
g.addVertex('E');
g.addVertex('F');

g.addEdge('A', 'B', 4);
g.addEdge('A', 'C', 2);
g.addEdge('B', 'E', 3);
g.addEdge('C', 'D', 2);
g.addEdge('C', 'F', 4);
g.addEdge('D', 'E', 3);
g.addEdge('D', 'F', 1);
g.addEdge('E', 'F', 1);

console.log(g.dijkstra('A', 'E'));

Python

import heapq

class Graph:
    def __init__(self):
        self.adj_list = {}

    def add_vertex(self, v):
        if v not in self.adj_list:
            self.adj_list[v] = []

    def add_edge(self, v, w, weight):
        self.adj_list[v].append((w, weight))
        self.adj_list[w].append((v, weight))

    def dijkstra(self, start, end):
        distances = {vertex: float('infinity') for vertex in self.adj_list}
        distances[start] = 0
        pq = [(0, start)]
        prev = {vertex: None for vertex in self.adj_list}

        while pq:
            current_distance, current_vertex = heapq.heappop(pq)

            if current_vertex == end:
                path = []
                while prev[current_vertex]:
                    path.append(current_vertex)
                    current_vertex = prev[current_vertex]
                path.append(start)
                return path[::-1]

            if current_distance > distances[current_vertex]:
                continue

            for neighbor, weight in self.adj_list[current_vertex]:
                distance = current_distance + weight

                if distance < distances[neighbor]:
                    distances[neighbor] = distance
                    prev[neighbor] = current_vertex
                    heapq.heappush(pq, (distance, neighbor))

        return []

g = Graph()
g.add_vertex('A')
g.add_vertex('B')
g.add_vertex('C')
g.add_vertex('D')
g.add_vertex('E')
g.add_vertex('F')

g.add_edge('A', 'B',

 4)
g.add_edge('A', 'C', 2)
g.add_edge('B', 'E', 3)
g.add_edge('C', 'D', 2)
g.add_edge('C', 'F', 4)
g.add_edge('D', 'E', 3)
g.add_edge('D', 'F', 1)
g.add_edge('E', 'F', 1)

print(g.dijkstra('A', 'E'))

결론

이번 글에서는 코딩 테스트 준비를 위해 그래프 알고리즘에 대해 배웠습니다. 이를 통해 깊이 우선 탐색(DFS), 너비 우선 탐색(BFS), 최단 경로 알고리즘(Dijkstra, Floyd-Warshall)의 개념과 구현 방법을 익힐 수 있었습니다. 다음 글에서는 동적 프로그래밍에 대해 알아보겠습니다.

다음 글에서 만나요!

 

반응형