728x90

69번 문자열 집합


내가 떠올린 풀이 해설

집합 S에 속해있는 단어들을 이용해 트라이 구조를 생성하고, 트라이 검색을 이용해 문자열 M개의 포함 여부를 카운트하는 전형적인 트라이 자료구조 문제이다. 트라이 자료구조를 생성하고 현재 문자열을 가리키는 위치의 노드가 공백 상태라면 신규 노드를 생성하고 아니라면 이동한다. 문자열 마지막에 도달하면 리프 노드라고 표시한다. 트라이 자료구조 검색으로 집합 S에 포함된 문자열을 센다. 부모-자식 관계를 이용해 대상 문자열을 검색했을 때 문자열이 끝날 때까지 공백 상태가 없고, 현재 문자의 마지막 노드가 트라이의 리프 노드라면 이 문자를 집합 S에 포함된 문자열로 센다.


정확한 풀이

import java.io.*;
import java.util.*;
public class Baek14425 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());
		
		dNode root = new dNode();
		while(n > 0) { // 트라이 자료구조 구축하기 
			String text = br.readLine();
			dNode now = root;
			for(int i = 0; i < text.length(); i++) {
				char c = text.charAt(i);
				// 26개 알파벳의 위치를 배열 index로 나타내기 위해 -'a' 수행하기 
				if(now.next[c - 'a'] == null) {
					now.next[c - 'a'] = new dNode();
 				}
				now = now.next[c - 'a'];
				if(i == text.length() - 1) {
					now.isEnd = true;
				}
			}
			n--;
		}
		int count = 0;
		while(m > 0) {    // 트라이 자료구조 검색하기 
			String text = br.readLine();
			dNode now = root;
			for(int i = 0; i < text.length(); i++) {
				char c = text.charAt(i);
				if(now.next[c - 'a'] == null) { // 공백 노드라면 이 문자열을 포함하지 않음 
					break;
				}
				now = now.next[c - 'a'];
				if(i == text.length() - 1 && now.isEnd) { // 문자열의 끝이고 현재까지 모두 일치하면 
					count++;   // S 집합에 포함되는 문자열 
				}
			}
			m--;
		}
		System.out.println(count);
	}

}
class dNode {
	dNode[] next = new dNode[26];
	boolean isEnd;  // 문자열의 마지막 유무를 표시하기 
}

오늘의 회고

오늘은 트라이 자료구조를 배우고 트라이 문제를 풀었습니다. 1문제 밖에 풀지 못했습니다. 장마라 밖에 비도 많이 오고 날씨도 우중충한데 퍼지지 않고 공부하겠습니다.

728x90
728x90

67번 트리의 부모 찾기


내가 떠올린 풀이 해설

인접 리스트 자료구조를 사용하면 간편하게 데이터를 저장할 수 있다. 트리의 루트가 1이라고 지정돼 있기 때문에 1번 노드부터 DFS로 탐색하면서 부모 노드를 찾아 주면 문제를 쉽게 풀 수 있다. 출력은 정답 배열의 2번 인덱스부터 값을 차례대로 출력한다.


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek11725 {
	static ArrayList<Integer>[] arr;
	static int[] answer;
	static boolean[] visit;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine());
		
		arr = new ArrayList[n + 1];
		visit = new boolean[n + 1];
		answer = new int[n + 1];
		
		for(int i = 0; i <= n; i++) {
			arr[i] = new ArrayList<>();
		}
		
		for(int i = 1; i < n; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			int s = Integer.parseInt(st.nextToken());
			int e = Integer.parseInt(st.nextToken());
			arr[s].add(e);
			arr[e].add(s);
		}
		DFS(1);
		for(int i = 2; i <= n; i++) {
			System.out.println(answer[i]);
		}
	}
	private static void DFS(int i) {
		visit[i] = true;
		for(int k : arr[i]) {
			if(!visit[k]) {
				answer[k] = i;
				DFS(k);
			}
		}
	}
}

68번 트리


내가 떠올린 풀이 해설

이 문제의 핵심은 리프 노드를 어떻게 제거하는지 이다. 리프 노드를 탐색하는 탐색 알고리즘을 수행할 때나 제거하는 노드가 나왔을 때 탐색을 종료하는 아이디어를 적용하면 실제 리프 노드를 제거하는 효과를 낼 수 있다. 인접 리스트로 트리 데이터를 구현한다. DFS를 탐색하면서 리프 노드의 개수를 센다. 제거 대상 노드를 만났을 때 그 아래 자식 노드들과 관련된 탐색은 중지한다.


정확한 풀이

import java.io.*;
import java.util.*;
public class Baek1068 {
	static ArrayList<Integer>[] arr;
	static boolean[] visit;
	static int r;
	static int answer;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		int n = Integer.parseInt(br.readLine());
		
		arr = new ArrayList[n];
		visit = new boolean[n];
		
		for(int i = 0; i < n; i++) {
			arr[i] = new ArrayList<>();
		}
		int root = 0;
		StringTokenizer st = new StringTokenizer(br.readLine());
		for(int i = 0; i < n; i++) {
			int s = Integer.parseInt(st.nextToken());
			if(s != -1) {
				arr[s].add(i);
				arr[i].add(s);
			}
			else {
				root = i;
			}
		}
		r = Integer.parseInt(br.readLine());
		if(r == root) {
			System.out.println(0);
		}
		else {
			DFS(root);
			System.out.println(answer);
		}
	}
	private static void DFS(int root) {
		visit[root] = true;
		int c = 0;
		for(int v : arr[root]) {
			if(!visit[v] && v != r) { // 삭제 노드가 아닐 떼도 탐색 중지 
				c++;
				DFS(v);
			}
		}
		if(c == 0) {
			answer++; // 자식 노드가 아닐 때 리프 노드로 간주하고 정답값 증가 
		}
	}
}

오늘의 회고

오늘은 트리와 관련된 문제 2문제를 풀었습니다. 알고리즘 개념에 대해 복습하는 시간을 가지면서 초반보다 문제를 푸는 개수가 많이 줄어들어 시간도 부족하고 알고리즘을 끝낼 수 있을까란 조바심도 들지만 천천히 조바심 가지지 않고 완전히 내 것으로 만들면서 나아가겠습니다.

728x90
728x90

64번 최소 스패닝 트리


내가 떠올린 풀이 해설

에지 리스트에 에지 정보를 저장한 후 부모 노드를 초기화한다. 사이클 생성 유무를 판단하기 위한 파인드용 부모 노드도 초기화한다. 크루스칼 알고리즘을 수행한다. 현재 미사용 에지 중 가중치가 가장 작은 에지를 선택하고, 이 에지를 연결했을 때 사이클의 발생 유무를 판단한다. 사이클이 발생하면 생략하고, 발생하지 않으면 에지 값을 더한다. 에지를 더한 횟수가 V(노드 개수) - 1 이 될 때까지 반복하고, 반복이 끝나면 에지의 가중치를 모두 더한 값을 출력한다.


정확한 풀이

import java.io.*;
import java.util.*;
public class Baek1197 {
	static PriorityQueue<pEdge> que;
	static int[] parent;
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());
		parent = new int[n + 1];
		que = new PriorityQueue<>();
		for(int i = 0; i < n; i++) {
			parent[i] = i;
		}
		for(int i = 0; i < m; i++) {
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			int c = Integer.parseInt(st.nextToken());
			que.add(new pEdge(a, b, c));
		}
		int useEdge = 0;
		int result = 0;
		while(useEdge < n - 1) {
			pEdge now = que.poll();
			if(find(now.a) != find(now.b)) {   // 같은 부모가 아니라면 연결해도 사이클이 생기지 않음 
				union(now.a, now.b);
				result = result + now.c;
				useEdge++;
			}
		}
		System.out.println(result);
	}
	private static void union(int a, int b) {
		a = find(a);
		b = find(b);
		if(a != b) {
			parent[b] = a;
		}
	}
	private static int find(int a) {
		if(a == parent[a]) {
			return a;
		}
		else {
			return parent[a] = find(parent[a]);
		}
	}
}
class pEdge implements Comparable<pEdge>{
	int a;
	int b;
	int c;
	public pEdge(int a, int b, int c) {
		this.a = a;
		this.b = b;
		this.c = c;
	}
	@Override
	public int compareTo(pEdge o) {
		return this.c - o.c;
	}
}

66번 블우이웃 돕기


내가 떠올린 풀이 해설

인접 행렬의 형태로 데이터가 들어오기 때문에 이 부분을 최소 신장 트리가 가능한 형태로 변형하는 것이 이 문제의 핵심이다. 먼저 문자열로 주어진 랜선의 길이를 숫자로 변형해 랜선의 총합을 저장한다. 이때 i와 j가 같은 곳의 값은 같은 컴퓨터를 연결한다는 의미이므로 별도 에지로 저장하지 않고 나머지 위치의 값들을 i -> j로 가는 에지로 생성하고, 에지 리스트에 저장하면 최소 신장 트리로 변형할 수 있다.

입력 데이터의 정보를 배열에 저장한다. 먼저 입력으로 주어진 문자열을 숫자로 변환해 총합으로 저장한다. 소문자는 현재 문자 - 'a' + 1, 대문자는 현재 문자 - 'A' + 27 변환한다. i와 j가 다른 경우에는 다른 컴퓨터를 연결하는 랜선이므로 에지리스트에 저장한다. 저장된 에지 리스트를 바탕으로 최소 신장 트리 알고리즘을 수행한다. 최소 신장 트리의 결괏값이 다솜이가 최소한으로 필요한 렌선의 길이이므로 처음 저장해 둔 랜선의 총합에서 최소 신장 트리의 결괏값을 뺀 값을 정답으로 출력한다. 최소 신장 트리에서 사용한 에지 개수가 n - 1이 아닌 경우에는 모든 컴퓨터를 연결할 수 없다는 의미이므로 -1을 출력한다.


정확한 풀이

import java.io.*;
import java.util.*;
public class Baek1414 {
	static int n, sum;
	static PriorityQueue<lEdge> que;
	static int[] parent;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		n = Integer.parseInt(br.readLine());
		que = new PriorityQueue<>();
		
		for(int i = 0; i < n; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			char[] tmpc = st.nextToken().toCharArray();
			for(int j = 0; j < n; j++) {
				int tmp = 0;
				if(tmpc[j] >= 'a' && tmpc[j] <= 'z') {
					tmp = tmpc[j] - 'a' + 1;
				}
				else if(tmpc[j] >= 'A' && tmpc[j] <= 'Z') {
					tmp = tmpc[j] - 'A' + 27;
				}
				sum = sum + tmp;
				if(i != j && tmp != 0) {
					que.add(new lEdge(i, j, tmp));
				}
			}
		}
		parent = new int[n];
		for(int i = 0; i < n; i++) {
			parent[i] = i;
		}
		int useEdge = 0;
		int result = 0;
		while(!que.isEmpty()) {
			lEdge now = que.poll();
			if(find(now.s) != find(now.e)) {
				union(now.s, now.e);
				result = result + now.v;
				useEdge++;
			}
		}
		if(useEdge == n - 1) {
			System.out.println(sum - result);
		}
		else {
			System.out.println(-1);
		}
	}
	private static void union(int s, int e) {
		s = find(s);
		e = find(e);
		if(s != e) {
			parent[e] = s;
		}
	}
	private static int find(int s) {
		if(s == parent[s]) {
			return s;
		}
		else {
			return parent[s] = find(parent[s]);
		}
	}
}
class lEdge implements Comparable<lEdge> {
	int s;
	int e;
	int v;
	public lEdge(int s, int e, int v) {
		this.s = s;
		this.e = e;
		this.v = v;
	}
	@Override
	public int compareTo(lEdge o) {
		return this.v - o.v;
	}
	
}

오늘의 회고

오늘은 최소 신장 트리 문제 2문제를 풀었습니다. 코드를 구현하는데 어려웠습니다. 반복해서 제가 직접 구현할 수 있게 만들겠습니다. 오늘 날씨가 너무 더운데 모든 취준생분들 화이팅입니다. 열심히 나아가겠습니다. 모두 화이팅입니다.

728x90
728x90

백준 1157번 단어 공부


내가 떠올린 풀이 해설

정답 출력을 다 대문자로 진행하기 때문에 문자를 다 대문자로 바꾸어 준 후 알파벳 개수만큼 배열을 생성한다. for 문에 i = 0부터 i < str.length()까지 하나씩 탐색하면서 입력받은 문자에서 - 65를 해준다. (arr [str.charAt(i) - 65]++ ) max값을 arr [str.charAt(i) - 65]로 바꾸어준다. 또한 ch에 그 단어를 저장해준다. 만약 max값이랑 arr [str.charAt(i)]값이랑 같으면 ch에 '?'를 저장해준다. for문을 끝내고 ch를 출력해준다.


정확한 풀이

import java.util.*;
import java.io.*;

public class Baek1157 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str = br.readLine();
		str = str.toUpperCase();
		int[] arr = new int[26];
		int max = -1;
		char ch = '?';
		for(int i = 0; i < str.length(); i++) {
			arr[str.charAt(i) - 65]++;
			if(max < arr[str.charAt(i) - 65]) {
				max = arr[str.charAt(i) - 65];
				ch = str.charAt(i);
			}
			else if(max == arr[str.charAt(i) - 65]) {
				ch = '?';
			}
		}
		System.out.println(ch);
	}
}

오늘의 회고

프로젝트 리펙토링을 진행하다가 댓글 화면이 안 나오는데 일주일 넘게 이를 잡고 있습니다. 오늘 해결해 보려고 했는데 오늘도 실패해서 하고 싶은 생각이 안 들어 머리를 좀 식힐 겸 알고리즘 쉬운 문제를 한 문제를 풀었습니다. 하지만 아이디어를 떠올리는데 시간이 걸렸습니다. 알파벳만큼 배열을 만들어 문자 - 65를 하면 그 문자의 위치에 더해준다는 것을 못 떠올렸습니다. ㅠㅠ 아직 갈길이 먼 것 같습니다.

728x90
728x90

백준 1620번 나는야 포켓몬 마스터 이다솜


내가 떠올린 풀이 해설

Hash 맵을 이용 해서 푸는 문제이다. HashMap을 이용해서 <String, String>으로 받아 key와 vlaue를 두 개를 저장을 한다. 하나는 i를 String으로 바꿔서 key, 포켓몬 이름은 value를 넣어주고 다른 하나는 두 개를 바꿔서 저장을 한다. 답의 출력을 받을 때 받은 것이 숫자인지 문자인지 판별하는 메서드를 만들어서 true, false로 반환한다. 그리고 StringBuilder를 이용하여 정답을 출력한다. 


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek1620 {
	static String str;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());
		
		HashMap<String, String> map = new HashMap<>();
		for(int i = 1; i <= n; i++) {
			String ss = br.readLine();
			String num = Integer.toString(i);
			map.put(num, ss);
			map.put(ss, num);
		}
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < m; i++) {
			str = br.readLine();
			if(isInteger()) {
				sb.append(map.get(str) + "\\n");
			}
			else {
				sb.append(map.get(str) + "\\n");
			}
		}
		System.out.println(sb.toString());
	}
	private static boolean isInteger() {
		for(int j = 0; j < str.length(); j++) {
			if(Character.isDigit(str.charAt(j))) {
				return true;
			}
		}
		return false;
	}
}

문제점

실버 4의 문제이고 쉬운 문제라고 생각했다. 문제를 푸는데는 어려움이 없었지만 계속 시간 초과가 발생했다. 숫자인지 판별해주는 메소드를 만들지 않았고 문제를 풀 때 map에 key, value 하나만 저장해 vlaue를 이용해 key값을 가져오려고 하였다. 여기서 시간 초과가 발생했다. 방법이 떠오르지 않아 이를 key, value를 바꿔서 하나 더 저장하는 아이디어를 검색을 해서 찾았습니다. 


오늘의 회고

오늘은 쉬운 알고리즘 문제 한문제를 풀고 그동안 풀었던 알고리즘 개념을 다시 정리하면서 블로그에 작성하려고 합니다. 반복적인 복습과 꾸준한 학습으로 점점 더 발전하는 개발자가 되겠습니다. 

728x90
728x90

62번 경로 찾기


내가 떠올린 풀이 해설

어제 배운 플로이드-위셜 기본 문제이다. 모든 노드 쌍에 관해 경로가 있는지 여부를 확인하는 방법은 플로이드-위셜 알고리즘을 수행해 결과 배열을 그대로 출력하면 된다. 입력 데이터를 인접 행렬에 저장한다. s와 e가 모든 중간 경로(k) 중 1개라도 연결돼 있다면 s와 e는 연결 노드로 저장한다. 변경된 인접 행렬을 출력한다.


정확한 풀이

import java.io.*;
import java.util.*;
public class Baek11403 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		int n = Integer.parseInt(br.readLine());
		int[][] arr = new int[n + 1][n + 1];
		
		for(int i = 1; i <= n; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			for(int j = 1; j <= n; j++) {
				arr[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		for(int k = 1; k <=n; k++) {
			for(int i = 1; i <= n; i++) {
				for(int j = 1; j <= n; j++) {
					if(arr[i][k] == 1 && arr[k][j] == 1) {
						arr[i][j] = 1;
                       // k를 거치는 모든 경로 중 1개라도 연결돼 있는 경로가 있다면 i와 j는 연결 노드로 취급
					}
				}
			}
		}
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= n; j++) {
				System.out.print(arr[i][j] + " ");
			}
			System.out.println();
		}
	}
}

63번 케빈 베이컨의 6단계 법칙


내가 떠올린 풀이 해설

BFS를 이용해서도 풀 수 있는 문제이다. 이 문제를 풀기 위해 몇가지 아이디어가 필요하다. 1번째로 사람들이 직접적인 친구 관계를 맺은 상태를 비용 1로 계산하는 것이다. 즉 가중치를 1로 정한 후 인접 행렬에 저장한다는 의미이다. 또한 플로이드-위셜은 모든 쌍과 관련된 최단 경로이므로 한 row의 배열 값은 이 row의 index값에서 다른 모든 노드와 관련된 최단 경로를 나타낸다고 볼 수 있다. 즉 i번째 row의 합이 i번째 사람의 케빈 베이컨의 수가 된다는 뜻이다.

먼저 인접 행렬을 생성한 후, 자기 자신이면 0, 아니면 충분히 큰 수로 인접 행렬의 값을 초기화한다. 그리고 친구 관계 정보를 인접 행렬에 저장한다. i와 j가 친구라면 arr[i][j] = 1, arr [j][i] = 1로 값을 업데이트한다. 플로이드-위셜 3중 for문으로 모든 중간 경로를 탐색한다. 케빈 베이컨의 수를 비교해 가장 작은 수가 나온 행 번호를 정답으로 출력한다. 같은 수가 있을 때는 더 작은 행 번호를 출력한다.


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek1389 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());
		int[][] arr = new int[n + 1][n + 1];
		
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= n; j++) {
				if(i == j) {
					arr[i][j] = 0;
				}
				else {
					arr[i][j] = 1000001;
				}
			}
		}
		for(int i = 0; i < m; i++) {
			st = new StringTokenizer(br.readLine());
			int s = Integer.parseInt(st.nextToken());
			int e = Integer.parseInt(st.nextToken());
			arr[s][e] = 1;
			arr[e][s] = 1;
		}
		for(int k = 1; k <= n; k++) {
			for(int i = 1; i <= n; i++) {
				for(int j = 1; j <= n; j++) {
					arr[i][j] = Math.min(arr[i][j], arr[i][k] + arr[k][j]);
				}
			}
		}
		int sum = 0;
		int answer = Integer.MAX_VALUE;
		int index = 0;
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= n; j++) {
				sum += arr[i][j];
			}
			if(answer > sum) {
				answer = sum;
				index = i;
			}
			sum = 0;
		}
		System.out.println(index);
	}
}

오늘의 회고

오늘은 어제 배운 플로이드-위셜 문제를 풀고 최소 신장 트리(MST) 이론을 학습하였습니다. 최소 신장 트리를 코드로 나타내는 것에 어려움을 많이 느꼈습니다. 주말에는 지금까지 풀었던 문제 복습과 배웠었던 알고리즘 이론들을 까먹지 않고 반복적으로 보기 쉽게 블로그에 정리하려고 합니다. 오늘은 퇴사 후 1달 정도가 되었는데 처음 알고리즘을 공부하였을 때 브론즈 5였는데 오늘은 골드 5를 찍었습니다. 앞으로 더 열심히 하겠습니다.

 

728x90
728x90

61번 플로이드


내가 떠올린 풀이 해설

모든 도시에 쌍과 관련된 최솟값을 찾아야 하는 문제이다. 그래프에서 시작점을 지정하지 않고, 모든 노드와 관련된 최소 경로를 구하는 알고리즘인 플로이드-위셜을 사용해야 된다. 도시의 최대 개수가 100개로 매우 작은 편이므로 O(N^3)인 플로이드-위셜을 사용할 수 있다.

버스 비용 정보를 인접 행렬에 저장한다. 연결 도시가 같으면 0, 아니면 큰 수 값으로 초기화한다. 그리고 주어진 버스 비용 데이터값을 인접 행렬에 저장한다. 플로이드-위셜 알고리즘을 수행한다. 3중 for 문으로 모든 중간 경로를 탐색한다. 알고리즘으로 변경된 인접 행렬을 출력한다. 문제의 요구사항에 따라 두 도시가 도달하지 못할 때는 0, 아닐 때는 배열의 값을 출력한다.

*플로이드 위셜-점화식

Math.min(arr[s][e], arr[s][k] + arr[k][e])
for(k -> n만큼 반복하기) { // 3중 for문의 순서가 중요함 k가 가장 바깥쪽
	for(i -> n만큼 반복하기) {
    	for(j -> n만큼 반복하기) {
        	arr[i][j]에 arr[i][k] + arr[k][j] 값들 중 최솟값 넣기
            i ~ j 사이에 가능한 모든 경로를 탐색하기
        }
    }
}

정확한 풀이

import java.io.*;
import java.util.*;

public class Baek11404 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine());
		int m = Integer.parseInt(br.readLine());
		int[][] arr = new int[n + 1][n + 1];
		
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= n; j++) {
				if(i == j) {
					arr[i][j] = 0;
				}
				else {
					arr[i][j] = 10000001;
				}
			}
		}
		
		for(int i = 0; i < m; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			int s = Integer.parseInt(st.nextToken());
			int e = Integer.parseInt(st.nextToken());
			int v = Integer.parseInt(st.nextToken());
			if(arr[s][e] > v) {
				arr[s][e] = v;
			}
		}
		for(int k = 1; k <= n; k++) {
			for(int i = 1; i <= n; i++) {
				for(int j = 1; j <= n; j++) {
					if(arr[i][j] > arr[i][k] + arr[k][j]) {
						arr[i][j] = arr[i][k] + arr[k][j];
					}
				}
			}
		}
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <=n; j++) {
				if(arr[i][j] == 10000001) {
					System.out.print("0 ");
				}
				else {
					System.out.print(arr[i][j] + " ");
				}
			}
			System.out.println();
		}
	}
}

오늘의 회고

오늘은 플로이드-위셜 알고리즘 개념을 배웠습니다. 플로이드-위셜은 코드가 많이 어렵지 않아 쉽게 이해할 수 있었습니다. 아직 배울 알고리즘이 많이 남았습니다. 천천히 꾸준히 배워 나아가겠습니다.

728x90
728x90

57번 최소비용 구하기


내가 떠올린 풀이 해설

버스의 비용의 범위가 음수가 아니기 때문에 이 문제는 다익스트라 알고리즘을 이용해 해결할 수 있다. 도시의 개수가 최대 1,000개 이므로 인접 행렬 방식으로 해결할 수 있지만 인접 리스트로 풀었다. 도시는 노드, 도시 간 버스 비용은 에지로 나타낸다.

도시 개수의 크기만큼 인접 리스트 배열의 크기를 설정한다. 이때 버스의 비용이 존재하므로 인접 리스트 배열의 자료형이 될 클래스를 선언한다. 그리고 버스 개수의 크기만큼 반복문을 돌면서 그래프를 리스트 배열에 저장한다. 다익스트라 알고리즘을 수행한다. 최단 거리를 완성되면 출력한다.


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek1916 {
	static int n, m;
	static ArrayList<Node>[] list;
	static boolean[] visit;
	static int[] distance;
	
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		StringTokenizer st;
		
		n = Integer.parseInt(br.readLine());
		m = Integer.parseInt(br.readLine());
		
		list = new ArrayList[n + 1];
		visit = new boolean[n + 1];
		distance = new int[n + 1];
		
		for(int i = 0; i <= n; i++) {
			list[i] = new ArrayList<Node>();
		}
		
		for(int i = 0; i <= n; i++) {
			distance[i] = Integer.MAX_VALUE;
		}
		
		for(int i = 0; i < m; i++) {
			st = new StringTokenizer(br.readLine());
			int s = Integer.parseInt(st.nextToken());
			int e = Integer.parseInt(st.nextToken());
			int v = Integer.parseInt(st.nextToken());
			list[s].add(new Node(e, v));
		}
		st = new StringTokenizer(br.readLine());
		int start = Integer.parseInt(st.nextToken());
		int end = Integer.parseInt(st.nextToken());
		dijkstra(start, end);
		
		System.out.println(distance[end]);
		
	}
	private static int dijkstra(int start, int end) {
		PriorityQueue<Node> que = new PriorityQueue<>();
		que.offer(new Node(start, 0));
		distance[start] = 0;
		while(!que.isEmpty()) {
			Node cur = que.poll();
			int c_v = cur.vertex;
			if(!visit[c_v]) {
				visit[c_v] = true;
				for(Node n : list[c_v]) {
					if(!visit[n.vertex] && distance[n.vertex] > distance[c_v] + n.value) {
						distance[n.vertex] = distance[c_v] + n.value;
						que.add(new Node(n.vertex, distance[n.vertex]));
					}
				}	
			}
		}
		return distance[end];
	}

}
class Node implements Comparable<Node>{
	int vertex;
	int value;
	public Node(int vertex, int value) {
		this.vertex = vertex;
		this.value = value;
	}
	@Override
	public int compareTo(Node o) {
		return value - o.value;
	}
}

59번 타임머신


내가 떠올린 풀이

에지 리스트에 에지 데이터를 저장한 후 거리 배열을 초기화한다. 최초 시작점에 해당하는 거리 배열값은 0으로 초기화한다. 벨만-포드 알고리즘을 수행한다. 음수 사이클이 존재하면 -1, 존재하지 않으면 거리 배열의 값을 출력한다. 단, 거리 배열의 값이 INF일 경우에는 -1을 출력한다.

벨만-포드 수행과정

1. 모든 에지와 관련된 정보를 가져온 후 다음 조건에 따라 거리 배열의 값을 업데이트한다.

  • 출발 노드가 방문한 적이 없는 노드(출발 노드 거리 == INF)일 때 값을 업데이트하지 않는다.
  • 출발 노드의 거리 배열값 + 에지 가중치 < 종료 노드의 거리 배열 값일 때 종료 노드의 거리 배열 값을 업데이트한다.

2. 노드 개수 -1번만큼 1번을 반복한다.

3. 음수 사이클 유무를 알기 위해 모든 에지에 관해 다시 한번 1번을 수행한다. 이때 한 번이라도 값이 업데이트되면 음수 사이클이 존재한다고 판단한다. 


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek11657 {
	static int n, m;
	static long distance[];
	static Edge edges[];
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());
		
		edges = new Edge[m + 1];
		distance = new long[n + 1];
		for(int i = 0; i <= n; i++) {
			distance[i] = Integer.MAX_VALUE;
		}
		for(int i = 0; i < m; i++) {
			st = new StringTokenizer(br.readLine());
			int s = Integer.parseInt(st.nextToken());
			int e = Integer.parseInt(st.nextToken());
			int v = Integer.parseInt(st.nextToken());
			edges[i] = new Edge(s, e, v);
		}
		distance[1] = 0;
		for(int i = 1; i < n; i++) {
			for(int j = 0; j < m; j++) {
				Edge edge = edges[j];
				if(distance[edge.s] != Integer.MAX_VALUE
						&& distance[edge.e] > distance[edge.s] + edge.v) {
					distance[edge.e] = distance[edge.s] + edge.v;
				}
			}
		}
		boolean mCycle = false;
		for(int i = 0; i < m; i++) {
			Edge edge = edges[i];
			if(distance[edge.s] != Integer.MAX_VALUE
					&& distance[edge.e] > distance[edge.s] + edge.v) {
				mCycle = true;
			}
		}
		if(!mCycle) {
			for(int i = 2; i <= n; i++) {
				if(distance[i] == Integer.MAX_VALUE) {
					System.out.println("-1");
				}
				else {
					System.out.println(distance[i]);
				}
			}
		}
		else {
			System.out.println("-1");
		}
	}
}
class Edge {
	int s;
	int e;
	int v;
	public Edge(int s, int e, int v) {
		this.s = s;
		this.e = e;
		this.v = v;
	}
}

오늘의 회고

오늘은 다익스트라 한 문제와 벨만-포드 알고리즘 개념과 관련된 문제를 풀었습니다. 아직 다익스트라와 벨만-포드 구현에 대해 많이 부족한 것 같습니다. 내일도 이와 관련된 문제들을 풀고 다시 한번 개념에 대해 공부하겠습니다. 

728x90

+ Recent posts