카테고리 없음

코딩테스트에 자주 나오는 알고리즘 정리

태태개발자 2023. 11. 11. 15:19
반응형
  • DFS/BFS
  • 정렬
  • 이진 탐색
  • 최단 경로
  • 최소비용

 

최소비용

 - 크루스칼 알고리즘(최소 비용 신장 트리) : O(E log E + E)

class DisjointSet {
  constructor() {
    this.parent = {};
    this.rank = {};
  }

  makeSet(vertex) {
    this.parent[vertex] = vertex;
    this.rank[vertex] = 0;
  }

  find(vertex) {
    if (this.parent[vertex] !== vertex) {
      this.parent[vertex] = this.find(this.parent[vertex]);
    }
    return this.parent[vertex];
  }

  union(vertex1, vertex2) {
    const root1 = this.find(vertex1);
    const root2 = this.find(vertex2);

    if (root1 !== root2) {
      if (this.rank[root1] < this.rank[root2]) {
        this.parent[root1] = root2;
      } else if (this.rank[root1] > this.rank[root2]) {
        this.parent[root2] = root1;
      } else {
        this.parent[root2] = root1;
        this.rank[root1]++;
      }
    }
  }
}

function kruskal(graph) {
  const edges = [];
  const minimumSpanningTree = [];

  // 모든 간선 추출
  for (let vertex in graph) {
    for (let neighbor in graph[vertex]) {
      edges.push([vertex, neighbor, graph[vertex][neighbor]]);
    }
  }

  // 간선 가중치를 기준으로 정렬
  edges.sort((a, b) => a[2] - b[2]);

  const disjointSet = new DisjointSet();

  // 각 정점에 대해 독립적인 집합 생성
  for (let vertex in graph) {
    disjointSet.makeSet(vertex);
  }

  // 가장 작은 가중치 간선부터 선택
  for (let edge of edges) {
    const [vertex1, vertex2, weight] = edge;

    // 사이클이 생성되지 않는 경우에만 간선 선택
    if (disjointSet.find(vertex1) !== disjointSet.find(vertex2)) {
      disjointSet.union(vertex1, vertex2);
      minimumSpanningTree.push(edge);
    }
  }

  return minimumSpanningTree;
}

// 그래프 정의
const graph = {
  A: { B: 7, D: 5 },
  B: { A: 7, D: 9, C: 8, E: 7 },
  C: { B: 8, E: 5 },
  D: { A: 5, B: 9, E: 15, F: 6 },
  E: { B: 7, C: 5, D: 15, F: 8, G: 9 },
  F: { D: 6, E: 8, G: 11 },
  G: { E: 9, F: 11 },
};

// 테스트
const minimumSpanningTree = kruskal(graph);
console.log(minimumSpanningTree);

 

 

 

  - 개선된 프림 알고리즘(최소 비용 신장 트리) : O(E log E + V) 

          * 기존 우선순위 큐를 힙 자료구조를 사용하여 가장 작은 가중치의 간선을 선택하여 속도를 개선

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

  addVertex(vertex) {
    this.adjacencyList.set(vertex, new Map());
  }

  addEdge(v1, v2, weight) {
    this.adjacencyList.get(v1).set(v2, weight);
    this.adjacencyList.get(v2).set(v1, weight);
  }

  prim(startVertex) {
    const minimumSpanningTree = new Map();
    const visited = new Set();
    const heap = new Heap();

    // 시작 정점을 방문 처리
    visited.add(startVertex);

    // 시작 정점과 연결된 간선들을 힙에 삽입
    const edges = this.adjacencyList.get(startVertex);
    for (let [vertex, weight] of edges.entries()) {
      heap.insert({ vertex, weight });
    }

    while (!heap.isEmpty()) {
      const { vertex, weight } = heap.deleteMin();

      // 이미 방문한 정점이면 건너뛰기
      if (visited.has(vertex)) {
        continue;
      }

      // 정점을 방문 처리하고, 최소 비용 간선을 minimumSpanningTree에 추가
      visited.add(vertex);
      minimumSpanningTree.set(vertex, weight);

      // 현재 정점과 연결된 간선들을 힙에 삽입
      const edges = this.adjacencyList.get(vertex);
      for (let [v, w] of edges.entries()) {
        if (!visited.has(v)) {
          heap.insert({ vertex: v, weight: w });
        }
      }
    }

    return minimumSpanningTree;
  }
}

// 힙(Heap) 구현
class Heap {
  constructor() {
    this.heap = [];
  }

  insert(element) {
    this.heap.push(element);
    this.heapifyUp(this.heap.length - 1);
  }

  deleteMin() {
    const min = this.heap[0];
    const last = this.heap.pop();

    if (this.heap.length > 0) {
      this.heap[0] = last;
      this.heapifyDown(0);
    }

    return min;
  }

  heapifyUp(index) {
    const parentIndex = Math.floor((index - 1) / 2);

    if (parentIndex >= 0 && this.heap[parentIndex].weight > this.heap[index].weight) {
      [this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
      this.heapifyUp(parentIndex);
    }
  }

  heapifyDown(index) {
    const leftChildIndex = 2 * index + 1;
    const rightChildIndex = 2 * index + 2;
    let smallestIndex = index;

    if (leftChildIndex < this.heap.length && this.heap[leftChildIndex].weight < this.heap[smallestIndex].weight) {
      smallestIndex = leftChildIndex;
    }

    if (rightChildIndex < this.heap.length && this.heap[rightChildIndex].weight < this.heap[smallestIndex].weight) {
      smallestIndex = rightChildIndex;
    }

    if (smallestIndex !== index) {
      [this.heap[index], this.heap[smallestIndex]] = [this.heap[smallestIndex], this.heap[index]];
      this.heapifyDown(smallestIndex);
    }
  }

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

// 그래프 생성
const graph = new Graph();
graph.addVertex("A");
graph.addVertex("B");
graph.addVertex("C");
graph.addVertex("D");
graph.addVertex("E");

graph.addEdge("A", "B", 4);
graph.addEdge("A", "C", 2);
graph.addEdge("B", "D", 5);
graph.addEdge("C", "D", 1);
graph.addEdge("C", "E", 3);
graph.addEdge("D", "E", 7);

// 테스트
const minimumSpanningTree = graph.prim("A");
console.log(minimumSpanningTree);

 

 

최단 경로

- 다익스트라 알고리즘(최단경로) : O((V + E) log V),

한점에서 다른 점들까지의 최소 거리

시작 노드에서 다른 노드까지의 거리를 찾고
최단 거리 노드를 방문하며 시작 노드로부터의 경로를 최소화.
모든 노드를 방문할때까지 반복.

 

function Graph() {
  this.adjacencyList = {};
}

Graph.prototype.addVertex = function (vertex) {
  this.adjacencyList[vertex] = {};
};

Graph.prototype.addEdge = function (v1, v2, weight) {
  this.adjacencyList[v1][v2] = weight;
  this.adjacencyList[v2][v1] = weight;
};

Graph.prototype.dijkstra = function (startVertex) {
  var distances = {};
  var visited = {};
  var queue = new PriorityQueue();

  // 모든 정점까지의 거리를 Infinity로 초기화
  for (var vertex in this.adjacencyList) {
    distances[vertex] = Infinity;
  }

  distances[startVertex] = 0;
  queue.enqueue(startVertex, 0);

  while (!queue.isEmpty()) {
    var currentVertex = queue.dequeue().element;

    if (visited[currentVertex]) {
      continue;
    }

    visited[currentVertex] = true;

    var neighbors = this.adjacencyList[currentVertex];
    for (var neighbor in neighbors) {
      var weight = neighbors[neighbor];
      var distance = distances[currentVertex] + weight;
      if (distance < distances[neighbor]) {
        distances[neighbor] = distance;
        queue.enqueue(neighbor, distance);
      }
    }
  }

  return distances;
};

function PriorityQueue() {
  this.queue = [];
}

PriorityQueue.prototype.enqueue = function (element, priority) {
  this.queue.push({ element: element, priority: priority });
  this.sort();
};

PriorityQueue.prototype.dequeue = function () {
  return this.queue.shift();
};

PriorityQueue.prototype.sort = function () {
  this.queue.sort(function (a, b) {
    return a.priority - b.priority;
  });
};

PriorityQueue.prototype.isEmpty = function () {
  return this.queue.length === 0;
};

// 테스트
var graph = new Graph();
graph.addVertex("A");
graph.addVertex("B");
graph.addVertex("C");
graph.addVertex("D");
graph.addVertex("E");

graph.addEdge("A", "B", 4);
graph.addEdge("A", "C", 2);
graph.addEdge("B", "D", 5);
graph.addEdge("C", "D", 1);
graph.addEdge("C", "E", 3);
graph.addEdge("D", "E", 7);

console.log(graph.dijkstra("A"));

 

 

이진탐색 

-  O(log n) : 정렬 되어 있을 경우 빠르게 탐색할수 있음.

function binarySearch(arr, target) {
  let left = 0;
  let right = arr.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);

    if (arr[mid] === target) {
      return mid; // 타겟을 찾았을 때 인덱스 반환
    } else if (arr[mid] < target) {
      left = mid + 1; // 중간값보다 큰 범위를 탐색하기 위해 왼쪽 범위를 업데이트
    } else {
      right = mid - 1; // 중간값보다 작은 범위를 탐색하기 위해 오른쪽 범위를 업데이트
    }
  }

  return -1; // 타겟을 찾지 못한 경우 -1 반환
}

// 테스트용 배열
const arr = [11, 22, 34, 45, 56, 67, 78, 89, 90];
const target = 45;
console.log("배열:", arr);
console.log(`타겟 ${target}의 인덱스:`, binarySearch(arr, target));

 

 

BFS/DFS

 v : 정점, e : 간선
  - 너비우선탐색(BFS) : O(v+e), queue 사용, 같은 레벨 먼저 탐색, 모든 경우 체크하며 감.

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

  addVertex(vertex) {
    this.adjacencyList.set(vertex, []);
  }

  addEdge(v1, v2) {
    this.adjacencyList.get(v1).push(v2);
    this.adjacencyList.get(v2).push(v1);
  }

  bfs(startVertex) {
    const visited = new Set();
    const queue = [];

    visited.add(startVertex);
    queue.push(startVertex);

    while (queue.length > 0) {
      const currentVertex = queue.shift();
      console.log(currentVertex);

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

// 테스트
const graph = new Graph();
graph.addVertex("A");
graph.addVertex("B");
graph.addVertex("C");
graph.addVertex("D");
graph.addVertex("E");

graph.addEdge("A", "B");
graph.addEdge("A", "C");
graph.addEdge("B", "D");
graph.addEdge("C", "E");

graph.bfs("A");

 

 

- 깊이우선탐색(DFS) : O(v+e), stack 사용, 자식 노드 먼저 탐색, 끝까지 갔다옴.   

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

  addVertex(vertex) {
    this.adjacencyList.set(vertex, []);
  }

  addEdge(v1, v2) {
    this.adjacencyList.get(v1).push(v2);
    this.adjacencyList.get(v2).push(v1);
  }

  dfsRecursive(startVertex) {
    const visited = new Set();

    this.dfsRecursiveHelper(startVertex, visited);
  }

  dfsRecursiveHelper(vertex, visited) {
    visited.add(vertex);
    console.log(vertex);

    const neighbors = this.adjacencyList.get(vertex);
    for (let neighbor of neighbors) {
      if (!visited.has(neighbor)) {
        this.dfsRecursiveHelper(neighbor, visited);
      }
    }
  }

  dfsIterative(startVertex) {
    const visited = new Set();
    const stack = [];

    stack.push(startVertex);

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

      if (!visited.has(currentVertex)) {
        visited.add(currentVertex);
        console.log(currentVertex);

        const neighbors = this.adjacencyList.get(currentVertex);
        for (let neighbor of neighbors) {
          stack.push(neighbor);
        }
      }
    }
  }
}

// 테스트
const graph = new Graph();
graph.addVertex("A");
graph.addVertex("B");
graph.addVertex("C");
graph.addVertex("D");
graph.addVertex("E");

graph.addEdge("A", "B");
graph.addEdge("A", "C");
graph.addEdge("B", "D");
graph.addEdge("C", "E");

graph.dfsRecursive("A");
// 또는
// graph.dfsIterative("A");

 

 

정렬

- 삽입정렬 : O(n^2)

function insertionSort(arr) {
  const length = arr.length;
  
  for (let i = 1; i < length; i++) {
    const current = arr[i];
    let j = i - 1;
    
    // 현재 요소를 이미 정렬된 부분의 적절한 위치에 삽입합니다.
    while (j >= 0 && arr[j] > current) {
      arr[j + 1] = arr[j];
      j--;
    }
    
    arr[j + 1] = current;
  }
  
  return arr;
}

// 테스트용 배열
const arr = [64, 34, 25, 12, 22, 11, 90];
console.log("정렬 전:", arr);
console.log("정렬 후:", insertionSort(arr));

 

 - 병합정렬 : O(n log n)

function mergeSort(arr) {
  if (arr.length <= 1) {
    return arr;
  }
  
  const mid = Math.floor(arr.length / 2);
  const left = arr.slice(0, mid);
  const right = arr.slice(mid);
  
  return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right) {
  let result = [];
  let i = 0;
  let j = 0;
  
  while (i < left.length && j < right.length) {
    if (left[i] < right[j]) {
      result.push(left[i]);
      i++;
    } else {
      result.push(right[j]);
      j++;
    }
  }
  
  while (i < left.length) {
    result.push(left[i]);
    i++;
  }
  
  while (j < right.length) {
    result.push(right[j]);
    j++;
  }
  
  return result;
}

// 테스트용 배열
const arr = [64, 34, 25, 12, 22, 11, 90];
console.log("정렬 전:", arr);
console.log("정렬 후:", mergeSort(arr));
반응형