先요약
- 일반적인 그래프를 구성할때는 싸이클이 있는지를 확인한다 : findParent 메소드 이용
- 두개의 노드를 간선으로 잇는 작업은 unionParent 메소드를 이용한다.
- 신장트리는 싸이클이 없어야하며, 모든 노드가 연결되어있어야한다 : 크루스칼 알고리즘으로 만든다.
- 위상정렬은 싸이클이 없고 방향그래프(DAG)인 상황에, 방향성에 거스르지 않도록 순서대로 나열하는 것.
그래프의 기본적인 이론에 대하여 알아보겠다.
아래의 메소드는 그래프 문제를 풀때 구현해놓으면 좋은 메소드이다.
1. findParent(int x) 메소드
: 루트노드가 아니라면, 해당 루트 노드를 찾을때까지 재귀적으로 findParent메소드 재귀호출.
ex) findParent(3번노드) -> 재귀 findParent(2번노드) -> 1번노드==부모노드 return.
- 비개선 findParent(int x) : return findParent(parent[x]);
- 개선된 findParent(int x) : return parent[x] = findParent(parent[x]);
2. unionParent(int a, int b) 메소드
: 두 원소가 속한 집합 합치는 작업. 이때, 번호가 큰 노드의 parent 값을 변경해준다.
import java.util.*;
public class Main {
// 노드의 개수(V)와 간선(Union 연산)의 개수(E)
// 노드의 개수는 최대 100,000개라고 가정
public static int v, e;
public static int[] parent = new int[100001]; // 부모 테이블 초기화하기
// 특정 원소가 속한 집합을 찾기
public static int findParent(int x) {
// 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if (x == parent[x]) return x;
//개선된 경로 압축!! parent[x]에 재귀된 값을 업데이트 한다.
return parent[x] = findParent(parent[x]);//
}
// 두 원소가 속한 집합을 합치기
public static void unionParent(int a, int b) {
a = findParent(a);
b = findParent(b);
if (a < b) parent[b] = a;
else parent[a] = b;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
v = sc.nextInt();
e = sc.nextInt();
// 부모 테이블상에서, 부모를 자기 자신으로 초기화
for (int i = 1; i <= v; i++) {
parent[i] = i;
}
// Union 연산을 각각 수행
for (int i = 0; i < e; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
unionParent(a, b);
}
/*
// 각 원소가 속한 집합 출력하기
System.out.print("각 원소가 속한 집합: ");
for (int i = 1; i <= v; i++) {
System.out.print(findParent(i) + " ");
}
System.out.println();
// 부모 테이블 내용 출력하기
System.out.print("부모 테이블: ");
for (int i = 1; i <= v; i++) {
System.out.print(parent[i] + " ");
}
System.out.println();
*/
}
}
3. 싸이클 확인하기 : boolean cycle = false;
위와 같이 cycle 변수를 선언 및 초기화한다.
무방향 그래프 내에서의 사이클을 판별할때 서로소 집합을 활용하여 사이클을 판별한다.
참고로 방향그래프에서의 사이클 여부는 DFS를 이용하여 판별 가능하다.(code0xff.tistory.com/39)
사이클 찾기: DFS
유향 그래프에서 사이클이 있는지 판단하고, 사이클에 포함되는 노드의 개수가 몇 개인지 세는 알고리즘으로 백준 알고리즘 저지의 9466번: 텀 프로젝트 문제가 이에 해당한다. 9466번: 텀 프로젝
code0xff.tistory.com
아래는 무방향 그래프에서의 싸이클을 찾을때 main문에서
findParent값이 동일하면 cycle로 판별하여 cycle =true; break;을 반환한다.
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
v = sc.nextInt();
e = sc.nextInt();
// 부모 테이블상에서, 부모를 자기 자신으로 초기화
for (int i = 1; i <= v; i++) {
parent[i] = i;
}
boolean cycle = false; // 사이클 발생 여부
for (int i = 0; i < e; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
// 사이클이 발생한 경우 종료
if (findParent(a) == findParent(b)) {
cycle = true;
break;
}
// 사이클이 발생하지 않았다면 합집합(Union) 연산 수행
else {
unionParent(a, b);
}
}
if (cycle) {
System.out.println("사이클이 발생했습니다.");
}
else {
System.out.println("사이클이 발생하지 않았습니다.");
}
}
}
신장 트리란? Spanning Tree
1. 사이클이 존재하지 않음
2. 모든 노드가 연결 되어 있어야한다.
=> 몇개의 간선이 없어도 되는지! => 최소한의 비용으로 구성하는 신장트리를 찾는 문제
=> 최소신장트리Minimum Spanning Tree(모든것이 최소비용으로 연결된 최소사이클이 없는 그래프 구조)
=> 대표적인 최소신장트리 구현 알고리즘 : 크루스칼 알고리즘(Kruskal's Algorithm)
class Edge implements Comparable<Edge>
=> get메소드 Distance, NodeA, NodeB
=> compareTo 메소드 : distance가 작은것이 더 높은 우선순위를 가짐.
크루스칼 알고리즘 전체 코드는 아래와 같다.
import java.util.*;
class Edge implements Comparable<Edge> {
private int distance;
private int nodeA;
private int nodeB;
public Edge(int distance, int nodeA, int nodeB) {
this.distance = distance;
this.nodeA = nodeA;
this.nodeB = nodeB;
}
public int getDistance() {
return this.distance;
}
public int getNodeA() {
return this.nodeA;
}
public int getNodeB() {
return this.nodeB;
}
// 거리(비용)가 짧은 것이 높은 우선순위를 가지도록 설정
@Override
public int compareTo(Edge other) {
if (this.distance < other.distance) {
return -1;
}
return 1;
}
}
public class Main {
// 노드의 개수(V)와 간선(Union 연산)의 개수(E)
// 노드의 개수는 최대 100,000개라고 가정
public static int v, e;
public static int[] parent = new int[100001]; // 부모 테이블 초기화하기
// 모든 간선을 담을 리스트와, 최종 비용을 담을 변수
public static ArrayList<Edge> edges = new ArrayList<>();
public static int result = 0;
// 특정 원소가 속한 집합을 찾기
public static int findParent(int x) {
// 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
if (x == parent[x]) return x;
return parent[x] = findParent(parent[x]);
}
// 두 원소가 속한 집합을 합치기
public static void unionParent(int a, int b) {
a = findParent(a);
b = findParent(b);
if (a < b) parent[b] = a;
else parent[a] = b;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
v = sc.nextInt();
e = sc.nextInt();
// 부모 테이블상에서, 부모를 자기 자신으로 초기화
for (int i = 1; i <= v; i++) {
parent[i] = i;
}
// 모든 간선에 대한 정보를 입력 받기
for (int i = 0; i < e; i++) {
int a = sc.nextInt();
int b = sc.nextInt();
int cost = sc.nextInt();
edges.add(new Edge(cost, a, b));
}
// 간선을 비용순으로 정렬
Collections.sort(edges);
// 간선을 하나씩 확인하며
for (int i = 0; i < edges.size(); i++) {
int cost = edges.get(i).getDistance();
int a = edges.get(i).getNodeA();
int b = edges.get(i).getNodeB();
// 사이클이 발생하지 않는 경우에만 집합에 포함
if (findParent(a) != findParent(b)) {
unionParent(a, b);
result += cost;
}
}
System.out.println(result);
}
}
간선을 확인하며, cost 및 a, b 사이 사이클이 발생안하면 result에 cost값을 더해서
"최소비용(result)"에 값을 추가한다.
p.s. 겁먹지말고 원리를 순서대로 차근차근 읽어보자.
위상정렬이란? Topological Sort Algorithm
: 사이클이 없는 방향 그래프의 모든 노드를 방향성에 거스르지 않도록 순서대로 나열하는 것
아래의 예시를 보겠다.
고급 알고리즘을 듣고 알고리즘을 듣는 것은 불가능하다.
따라서 <자료구조-알고리즘-고급알고리즘>
혹은, <자료구조-고급알고리즘>
수업은 이러한 방식으로 수업을 들어야한다.
but, 모두 듣기위한 방법은 위의 한가지 방법뿐이다.
위상정렬은 큐를 이용하여 동작한다.
"진입차수" 란?
: 해당 노드로 들어오는 다른 노드의 개수.
1번 노드에서 가는 2번/5번 노드를 큐에 넣고 해당 노드들의 진입차수를 -1씩 한다.
이러한 방식으로 큐에서 차례대로 꺼내가면서 진행한다.
코드출처 : github.com/ndb796/python-for-coding-test/blob/master/10/6.java
ndb796/python-for-coding-test
[한빛미디어] "이것이 취업을 위한 코딩 테스트다 with 파이썬" 전체 소스코드 저장소입니다. - ndb796/python-for-coding-test
github.com
개념출처 : 나동빈 유튜브 www.youtube.com/watch?v=aOhhNFTIeFI&list=PLRx0vPvlEmdAghTr5mXQxGpHjWqSz0dgC&index=8
'코딩테스트 > 알고리즘 개념정리' 카테고리의 다른 글
백트래킹(BackTracking, DFS/BFS), N과M(1) (0) | 2021.03.30 |
---|---|
플로이드 워셜 알고리즘 (0) | 2020.12.01 |
DFS/BFS 개념정리 (0) | 2020.11.04 |
DP(=다이나믹 프로그래밍,Dynamic Programming) 심화 (0) | 2020.10.13 |
탐욕(Greedy)그리디알고리즘/동적(Dynamic)계획법 비교 (0) | 2020.10.08 |