[이것이 코딩테스트다] 9. 그래프 이론

2022. 2. 11. 22:11· Algorithm
반응형

이것이 취업을 위한 코딩테스트다.

 

이 포스팅에서 작성하는 내용은 이것이 취업을 위한 코딩테스트다 (나동빈 지음) 에서 발췌하였습니다.

(원본 소스코드 : https://github.com/ndb796/python-for-coding-test/)

 

 

1. 이전에 공부했던 그래프 복습

1) 그래프(Graph) : 노드와 노드 사이에 연결된 간선 정보를 가지는 자료구조

(서로 다른 개체가 연결 or 여러 도시가 연결 과 같은 경우는 그래프 알고리즘 의심)

 

- 다익스트라 알고리즘 : 시작점에서부터 다른 각각의 노드로 가는 최단 경로를 구하는 알고리즘. (우선순위 큐를 이용하면 효율적)

 

- 워셜-플로이드 알고리즘 : 모든 노드에서 각각의 다른 노드까지 최단 경로를 구하는 알고리즘. (인접 행렬을 이용, 노드의 갯수가 적으면 유용)

 

- 그래프 표현 방법

인접 행렬 : 2차원 배열을 사용하여 그래프 표현

인접 리스트 : 리스트를 사용하는 방식

 

2) 그래프 vs 트리

  그래프 트리
방향성 방향 그래프 혹은 무방향 그래프 방향 그래프
순환성 순환 및 비순환 비순환
루트 노드 존재 여부 X O
노드 간 관계성 X O
모델의 종류 네트워크 모델 계층 모델

 

 

2. 서로소 집합(Disjoint Sets)

- 공통 원소가 없는 두 집합

ex1 ) {1, 3}, {4, 6} → 서로소 관계이다.

ex2 ) {1, 3}, {3, 6} → 서로소 관계가 아니다. (3이라는 원소가 공통적으로 포함)

 

1) 서로소 집합 자료구조(union-find합치기 찾기): 서로소 부분 집합들로 나누어진 원소들의 데이터를 처리하기 위한 자료구조

- union과 find 이 2개의 연산으로 조작

- union(합집합) : 2개의 원소가 포함된 집합을 하나의 집합으로 합치는 연산

- find(찾기) : 특정한 원소가 속한 집합이 어떤 집합인 지 알려주는 연산

 

- 트리 자료구조를 이용해 표현

- 서로소 집합을 그림으로 표현 시, 번호가 큰 노드가 번호가 작은 노드를 간선으로 가리키도록 한다.

 

import java.util.Scanner;

public class Main {

	// V : 노드의 갯수
	// M : 간선의 갯수 
    public static int V, M;
    
    // 노드의 개수는 최대 100,000개라고 가정
    // 부모 노드 테이블
    public static int[] parent = new int[100001];

    // 특정 원소가 속한 집합을 찾기
    public static int findParent(int x) {
        // 노드의 부모노드가 루트 노드일 경우, 
        if (x == parent[x]) {
        	// 반환
        	return x;
        }
        // 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
        return 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();
        M = sc.nextInt();

        // 부모 테이블에서, 부모 노드를 자신으로 초기화
        for (int i = 1; i <= V; i++) {
            parent[i] = i;
        }

        // Union 연산 수행
        for (int i = 0; i < M; 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();
    }

}

 

 

- 위의 소스코드는 최악의 경우 부모 노드를 확인하는 데 모든 노드를 확인할 수도 있다.

아래의 표 처럼 5번 노드의 루트 노드를 확인하는데 4>3>2>1>1을 확인하게 된다.

노드 번호 1 2 3 4 5
부모 노드 번호 1 1 2 3 4

 

- 위의 경우 경로 압축 기법을 적용하여 시간 복잡도를 개선시킬 수 있다.

 

2) 경로 압축(path Compression) : find 함수를 재귀적으로 호출한 뒤에 부모 테이블을 갱신하는 기법

- 위의 소스코드에서 findParent 함수를 아래 코드와 같이 수정하여 경로압축 기법을 사용한다.

public static int findParent(int x) {
        // x의 부모노드가 루트노드일 경우 x 반환
        if (x == parent[x]){
        	return x;
        }
        
        // 아니면 루트노드 찾은 후에 parent 배열에 넣고 반환
        return parent[x] = findParent(parent[x]);
    }

 

 

3) 서로소 집합을 활용한 사이클 판별

- 서로소 집합은 무방향 그래프 내에서의 사이클을 판별할 때 사용할 수 있다.

- union연산은 그래프에서의 간선으료 표현될 수 있으며, 간선을 하나씩 확인하면서 두 노드가 포함되어 있는 집합을 합치는 과정을 반복하면 사이클 판별 가능

  1. 각 간선을 확인하며 두 노드의 루트 노드 확인
    • 루트 노드가 서로 다르다면 두 노드에 대해 union 연산 진행
    • 루트 노드가 서로 같다면 사이클(Cycle)인 것으로 확인
  2. 그래프에 포함되어 있는 모든 간선에 대하여 1번의 과정 반복
import java.util.*;

public class Main {

	// V : 노드의 갯수
	// M : 간선의 갯수 
    public static int V, M;
	    
    public static int[] parent = new int[100001]; // 부모 테이블 초기화하기

    // 특정 원소가 속한 집합을 찾기
    public static int findParent(int x) {
        // x가 루트노드 일 경우 x 반환
        if (x == parent[x]) {
        	return x;
        }
        
        // 아닐 경우, 루트노드를 찾기위해 재귀 호출 후 이 값을 반환 및 parent 배열에 저장
        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();
        M = sc.nextInt();

        // 부모 테이블상에서, 부모를 자기 자신으로 초기화
        for (int i = 1; i <= V; i++) {
            parent[i] = i;
        }

        boolean cycle = false; // 사이클 발생 여부

        for (int i = 0; i < M; 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("사이클 없음");
        }
    }
}
입력 출력
3 3
1 2
2 1
2 3
사이클 있음

 

 

 

3. 신장 트리

1) 신장 트리(Spanning Tree)

- 하나의 그래프가 있을 때 모든 노드를 포함하면서 사이클이 존재하지 않는 부분 그래프

 

2) 크루스칼 알고리즘(Kruskal Algorithm)

- 신장 트리 중에서 최소 비용으로 만들 수 있는 트리를 찾는 알고리즘 (최소 신장 트리 알고리즘)

- 그리디 알고리즘으로 분류

- 가장 거리가 짧은 간선부터 차례대로 집합에 추가하는 방식

- 간선의 갯수 = 노드의 갯수 -1

 

그래프 예시

 

ex) 위의 A, B, C 가 모두 연결되기 위한 최소의 비용(최소 신장 트리)은 아래와 같다.

 ▶ 2+3 (최소 비용)

 ▶ 3+5

 ▶ 2+5

위의 그래프에서의 최소 신장 트리

 

3) 크루스칼 알고리즘 동작 과정

  1. 간선 데이터를 비용에 따라 오름차순 정렬
  2. 간선을 하나씩 확인하며 현재의 간선이 사이클을 발생시키는 지 확인
    1. 사이클 발생 시, 최소 신장 트리에 포함 X
    2. 사이클 발생 하지 않는 경우, 최소 신장 트리에 포함
  3. 모든 간선에 대하여 2번 과정을 반복
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

public class Main {
	
	// Edge 클래스 선언
	public static class Edge implements Comparable<Edge>{
		private int nodeA;
		private int nodeB;
		private int distance;
		
		public Edge(int nodeA, int nodeB, int distance) {
			this.nodeA = nodeA;
			this.nodeB = nodeB;
			this.distance = distance;
		}

		public int getNodeA() {
			return nodeA;
		}

		public int getNodeB() {
			return nodeB;
		}
		
		public int getDistance() {
			return distance;
		}

		// 우선순위 큐에서 사용
		// 거리가 낮을 수록 높은 우선순위를 가짐
		@Override
		public int compareTo(Edge other) {
			if(this.distance < other.distance)
				return -1;
			else
				return 1;
		}
	}
	
	// V : 노드의 갯수(최대 100000개로 가정)
	// M : 간선의 갯수
	public static int V, M;
	
	// 부모 테이블
	public static int[] parent = new int[100001]; 
	
	// 그래프를 저장할 변수
	public static ArrayList<Edge> graph = new ArrayList<>();
	
	// 결과를 저장할 변수
	public static int result = 0;
	
	// 특정 원소의 루트 노드 찾기
    public static int findParent(int x) {
        // 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) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);

        V = sc.nextInt();
        M = sc.nextInt();

        // 부모 테이블에서, 부모를 자기 자신으로 초기화
        // 0번 노드는 없다고 가정
        for (int i = 1; i <= V; i++) {
            parent[i] = i;
        }

        // 모든 간선에 대한 정보를 입력 받기
        for (int i = 0; i < M; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            int cost = sc.nextInt();
            graph.add(new Edge(a, b, cost));
        }

        // 간선을 비용순으로 정렬
        Collections.sort(graph);

        // 간선을 하나씩 확인하며
        for (int i = 0; i < graph.size(); i++) {
            int cost = graph.get(i).getDistance();
            int a = graph.get(i).getNodeA();
            int b = graph.get(i).getNodeB();
            
            // 사이클이 발생하지 않는 경우에만 최소 신장 트리에 포함
            // 사이클이 발생하지 않는다 = a와 b의 루트 노드가 같지 않다
            if (findParent(a) != findParent(b)) {
                unionParent(a, b);
                result += cost;
            }
        }

        // 결과 출력
        System.out.println(result);
	}

}

 

 

 

4. 위상 정렬

1) 위상 정렬(Topology Sort) : 순서가 정해져 있는 일련의 작업을 차례대로 수행해야 사용할 수 있는 알고리즘

- 방향 그래프의 모든 노드를 '방향성에 거스르지 않도록 순서대로 나열하는 것'

- 진입차수(Indegree) : 특정한 노드로 들어오는 간선의 개수

 

2) 위상 정렬의 동작 과정

  1. 진입차수가 0인 노드를 큐에 넣는다.
  2. 큐가 빌 때까지 아래의 과정을 반복한다.
    1. 큐에서 원소를 꺼내 해당 노드에서 출발하는 간선을 그래프에서 제거한다.
    2. 새롭게 진입차수가 0이 된 노드를 큐에 넣는다.
  3. 만약, 모든 원소를 방문하기 전에 큐가 비어버리면 사이클이 발생한 것이다. (위상 정렬 문제에서는 사이클이 존재하지 않는다고 명시하는 경우가 많음)

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	
	// N : 노드의 갯수
	// M : 간선의 갯수
	public static int N, M;
	
	// 그래프
	public static ArrayList<ArrayList<Integer>> graph = new ArrayList<ArrayList<Integer>>();
	
	// 각 노드 별 진입차수 체크
	public static int[] inde;
	
	public static void topologySort() {
		
		Queue<Integer> q = new LinkedList<>();
		
		ArrayList<Integer> result = new ArrayList<>();
		
		// 진입차수가 0인 노드를 큐에 넣기
		for(int i=1; i<=N; i++) {
			if(inde[i]==0)
				q.offer(i);
		}

		// 큐가 빌 때까지 반복
		while(!q.isEmpty()) {
			// 큐에서 꺼내고
			int now = q.poll();
			
			// 결과 리스트에 넣기
			result.add(now);
			
			// 현재 큐에서 이어지는 간선을 빼주기
			for(int i=0; i<graph.get(now).size(); i++) {
				inde[graph.get(now).get(i)] -= 1;
				
				// 그 때, 진입차수가 0이면 큐에 넣기
				if(inde[graph.get(now).get(i)] == 0)
					q.offer(graph.get(now).get(i));
			}
		}
		
		System.out.print("순서 : ");
		// 결과 출력
		for(int i=0;i<result.size();i++) {
			System.out.print(result.get(i)+" ");
		}
		
	}
	
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		// 노드의 갯수와 간선의 갯수 입력받기
		N = Integer.parseInt(st.nextToken());
		M = Integer.parseInt(st.nextToken());
		
		// 진입차수 배열 크기 할당
		inde = new int[N+1];
		
		// 그래프 설정
		for(int i=0;i<N+1; i++) {
			graph.add(new ArrayList<Integer>());
		}
		for(int i=0; i<M; i++) {
			st = new StringTokenizer(br.readLine());
			
			int from = Integer.parseInt(st.nextToken());
			int to = Integer.parseInt(st.nextToken());
			
			graph.get(from).add(to);
			
			// 진입차수 설정
			inde[to] += 1;
		}
		
		// 위상정렬 호출
		topologySort();
		
	}

}
입력 출력
7 8
1 2
1 5
2 3
2 6
3 4
4 7
5 6
6 4
순서 : 1 2 5 3 6 4 7 
반응형

'Algorithm' 카테고리의 다른 글

[이것이 코딩테스트다] 9. 그래프 - 실전 문제 풀이  (0) 2022.02.12
[BAEKJOON] 2665번 : 미로만들기 (JAVA)  (0) 2022.02.11
[BAEKJOON] 4485번 : 녹색 옷 입은 애가 젤다지? (JAVA)  (0) 2022.02.08
[BAEKJOON] 13549번 : 숨바꼭질 3 (JAVA)  (0) 2022.02.07
[BAEKJOON] 1916번 : 최소 비용 구하기 (JAVA)  (0) 2022.02.04
'Algorithm' 카테고리의 다른 글
  • [이것이 코딩테스트다] 9. 그래프 - 실전 문제 풀이
  • [BAEKJOON] 2665번 : 미로만들기 (JAVA)
  • [BAEKJOON] 4485번 : 녹색 옷 입은 애가 젤다지? (JAVA)
  • [BAEKJOON] 13549번 : 숨바꼭질 3 (JAVA)
멍목
멍목
개발 관련 새롭게 알게 된 지식이나 좋은 정보들을 메모하는 공간입니다.
반응형
멍목
김멍목의 개발블로그
멍목
전체
오늘
어제
  • 분류 전체보기 (514)
    • BE (190)
      • Spring (21)
      • Java (141)
      • Kotlin (6)
      • JPA (22)
    • FE (33)
      • Javascript (16)
      • Typescript (0)
      • React (5)
      • Vue.js (9)
      • JSP & JSTL (3)
    • DB (32)
      • Oracle (22)
      • MongoDB (10)
    • Algorithm (195)
    • Linux (8)
    • Git (6)
    • etc (42)
    • ---------------------------.. (0)
    • 회계 (4)
      • 전산회계 2급 (4)
    • 잡동사니 (2)

블로그 메뉴

  • 홈
  • 관리

공지사항

인기 글

태그

  • 더 자바 애플리케이션을 테스트하는 다양한 방법
  • 코테 공부
  • junit5
  • 알고리즘공부
  • 코틀린
  • Java to Kotlin
  • Effective Java
  • JPA
  • 자기공부
  • Oracle
  • 프로젝트로 배우는 Vue.js 3
  • 자바 공부
  • 자바 테스팅 프레임워크
  • 전산회계 2급 준비
  • MongoDB 공부
  • 이펙티브자바
  • 자바 개발자를 위한 코틀린 입문
  • 자기개발
  • MongoDB with Node.js
  • 자기 공부
  • vue3 공부
  • JPA 공부
  • 자기 개발
  • 자바공부
  • java 8
  • 더 자바 Java 8
  • 알고리즘 공부
  • MongoDB 기초부터 실무까지
  • 코테공부
  • 이펙티브 자바

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.0
멍목
[이것이 코딩테스트다] 9. 그래프 이론
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.