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

그리디

그리디 알고리즘은 현재 상태에서 볼 수 있는 선택지 중에 최선의 선택을 하는 알고리즘이다. 그리디 알고리즘은 동적 계획법보다 구현하기 쉽고 시간 복잡도가 우수하다. 하지만 항상 최적의 해를 보장하지 못한다는 단점도 있다.

 

그리디 알고리즘 핵심 이론

  1. 해 선택 : 현재 상태에서 가장 최선이라고 생각되는 해를 선택한다.
  2. 적절성 검사 : 현재 선택한 해가 전체 문제의 제약 조건에 벗어나지 않는지 검사한다.
  3. 해 검사 : 현재까지 선택한 해 집합이 전체 문제를 해결할 수 있는지 검사한다. 전체 문제를 해결하지 못한다면 1로 돌아가 같은 과정을 반복한다.

 

728x90

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

위상 정렬  (0) 2022.06.21
유니온 파인드  (0) 2022.06.21
이진 탐색  (0) 2022.06.20
BFS  (0) 2022.06.20
DFS  (0) 2022.06.20
728x90

이진 탐색

데이터가 정렬돼 있는 상태에서 원하는 값을 찾아내는 알고리즘이다. 대상 데이터의 중앙값과 찾고자 하는 값을 비교해 데이터의 크기를 절반씩 줄이면서 대상을 찾는다.

 

이진 탐색 핵심 이론

  1.  현재 데이터셋의 중앙값을 선택한다.
  2. 중앙값 > 타깃 데이터일 때 중앙값 기준으로 왼쪽 데이터셋을 선택한다.
  3. 중앙값 < 타깃 데이터일 때 중앙값 기준으로 오른쪽 데이터셋을 선택한다.
  4. 과정 1 ~ 3을 반복하다가 중앙값 == 타깃 데이터일 때 탐색을 종료한다.

시간복잡도

O(logN)

 

코드 예시

int start = 0;
int end = arr.length - 1;
while(start <= end) {
	int mid = (start + end) / 2;
    int midv = arr[mid];
    if(midv > target) {
    	end = mid - 1;   // 핵심 : 중간값이 찾고자하는 값보다 크면 end를 바꿈
    } 
    else if (midv < target) {
    	start = mid + 1;  // 핵심 : 중간값이 찾고자하는 값보다 작으면 start를 바꿈
    }
}
728x90

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

유니온 파인드  (0) 2022.06.21
그리디  (0) 2022.06.20
BFS  (0) 2022.06.20
DFS  (0) 2022.06.20
스택과 큐  (0) 2022.06.18
728x90

BFS

너비 우선 탐색으로 그래프를 완전 탐색하는 알고리즘 중 하나로, 시작 노드에서 출발해 시작 노드를 기준으로 가까운 노드를 먼저 방문하면서 탐색하는 알고리즘이다.

너비 우선 탐색은 선입선출 방식으로 탐색하므로 큐를 이용해 구현한다. 또한 너비 우선 탐색은 탐색 시작 노드와 가까운 노드를 우선하여 탐색하므로 목표 노드에 도착하는 경로가 여러 개일 때 최단 경로를 보장한다.

 

BFS 핵심 이론

1. BFS를 시작할 노드를 정한 후 사용할 자료구조 초기화하기

   BFS도 DFS와 마찬가지로 방문했던 노드는 다시 방문하지 않으므로 방문한 노드를 체크하기 위한 배열이 필요하다.

   그래프를 인접 리스트로 표현하는 것이 역시 DFS와 동일하다.

2. 큐에서 노드를 꺼낸후 꺼낸 노드의 인접 노드를 다시 큐에 삽입하기

   큐에서 노드를 꺼내면서 인접 노드를 큐에 삽입한다.

   이때 방문 배열을 체크하여 이미 방문한 노드는 큐에 삽입하지 않는다.

   또한 큐에서 꺼낸 노드는 탐색 순서에 기록한다.

3. 큐 자료구조에 값이 없을 때까지 반복하기

   큐에 노드가 없을 때까지 앞의 과정을 반복한다.

 

코드 예시

static boolean visit[];   // 방문 배열 선언
static ArrayList<Integer>[] A;  // 인접리스트 선언

public static void BFS(int Node) {
	Queue<Integer> que = new LinkedList<Integer>();
    que.add(Node);
    visit[Node] = true;
    
    while(!que.isEmpty()) {
    	int now = que.poll();
        for(int i : A[now]) {
        	if(!visit[i]) {
            	visit[i] = true;
                que.add(i);
            }
        }
    }
}

 

시간 복잡도(노드 수 : V, 에지 수 : E)

 - 인접 리스트 = O(V+E)

 - 인접 행렬 = O(V^2)

 
728x90

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

그리디  (0) 2022.06.20
이진 탐색  (0) 2022.06.20
DFS  (0) 2022.06.20
스택과 큐  (0) 2022.06.18
투 포인터  (0) 2022.06.18
728x90

DFS

깊이 우선 탐색은 그래프 완전 탐색 기법 중 하나이다. 깊이 우선 탐색은 그래프의 시작 노드에서 출발하여 탐색할 쪽 분기를 정하여 최대 깊이까지 탐색을 마친 후 다른 쪽 분기로 이동하여 다시 탐색을 수행하는 알고리즘이다.

깊이 우선 탐색을 응용하여 풀 수 있는 문제는 단절점 찾기, 단절선 찾기, 사이클 찾기, 위상 정렬 등이 있다.

 

DFS 핵심 이론

DFS는 한번 방문한 노드를 다시 방문하면 안 되므로 노드 방문 여부를 체크할 배열이 필요하며, 그래프는 인접 리스트로 표현한다.

1. DFS를 시작할 노드를 정한 후 사용할 자료구조 초기화

   DFS를 위해 필요한 초기 작업은 인접 리스트로 그래프 표현하기, 방문 배열 초기화하기, 시작 노드 스택에 삽입하기이다.

2. 스택에서 노드 꺼낸 후 꺼낸 노드의 인접 노드를 다시 스택에 삽입

   pop을 수행하여 노드를 꺼낸다.

   꺼낸 노드를 탐색 순서에 기입하고 인접 리스트의 인접 노드를 스택에 삽입하며 방문 배열을 체크한다.

3. 스택 자료구조에 값이 없을 때까지 반복

   앞의 과정을 스택 자료구조에 값이 없을 때까지 반복한다.

   이미 다녀간 노드는 방문 배열을 바탕으로 재삽입하지 않는다.

 

코드 예시

static boolean visit[] // 방문 배열 선언
static ArrayList<Integer>[] A // 인접 리스트 선언

public static void DFS(int Node) {  // DFS 구현하기
	visit[Node] = true;
    for(int i : A[Node]) {
    	if(!visited[i]) {
        	DFS(i);
        }
    }
}

 

시간 복잡도(노드 수 : V, 에지 수 : E)

 - 인접 리스트 = O(V+E)

 - 인접 행렬 = O(V^2)

728x90

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

그리디  (0) 2022.06.20
이진 탐색  (0) 2022.06.20
BFS  (0) 2022.06.20
스택과 큐  (0) 2022.06.18
투 포인터  (0) 2022.06.18
728x90

스택

스택은 삽입과 삭제 연산이 후입 선출 LIFO로 이뤄지는 자료구조이다. 후입 선출은 삽입과 삭제가 한쪽에서만 일어나는 특징이 있다.

 

스택 용어

위치

  • top : 삽입과 삭제가 일어나는 위치를 뜻한다.

연산

  • push : top 위치에 새로운 데이터를 삽입하는 연산
  • pop : top 위치에 현재 있는 데이터를 삭제하고 확인하는 연산
  • peek : top 위치에 현재 있는 데이터를 단순 확인하는 연산

스택의 중요성

스택은 깊이우선탐색, 백트래킹 종류의 알고리즘에 사용되므로 반드시 알아 두어야 한다.

후입 선출은 개념 자체가 재귀 함수 알고리즘 원리와 같다.

 


큐 

큐는 삽입과 삭제 연산이 선입선출 FIFO로 이뤄지는 자료구조이다. 스택과 다르게 먼저 들어온 데이터가 먼저 나간다. 그래서 삽입과 삭제가 양방향에서 이뤄진다.

 

큐 용어

  • rear : 큐에서 가장 끝 데이터를 가리키는 영역이다.
  • front : 큐에서 가장 앞의 데이터를 가리키는 영역이다.
  • add : rear 부분에 새로운 데이터를 삽입하는 연산이다.
  • poll : front 부분에 있는 데이터를 삭제하고 확인하는 연산이다.
  • peek : 큐의 맨 앞에 있는 데이터를 확인할 때 사용하는 연산이다.

큐는 너비 우선 탐색에서 자주 사용하므로 중요한 자료구조입니다.

 


우선순위 큐

우선순위 큐는 값이 들어간 순서와 상관없이 우선순위가 높은 데이터가 먼저 나오는 자료구조입니다.

큐 설정에 따라 front에 항상 최댓값 또는 최솟값이 위치한다. 우선순위 큐는 일반적으로 힙을 이용해 구현하는데 힙은 트리 종류 중 하나이다. 일반적으로 우선순위 큐는 최대 힙을 이용해 구현한다. 

 

최대 힙(Max Heap)이란?

  • 최대 힙(Max Heap)은 부모 노드가 자식 노드보다 값이 큰 완전 이진트리를 의미한다.
  • 최대 힙의 루트 노드는 전체 트리에서 가장 큰 값을 가지는 특징이 있다.
  • 항상 전체 크리가 최대 힙 구조를 유지하도록 자료구조를 설계해야 한다.
  • 최대 힙도 큐를 기반으로 동작하기 때문에 push, pop이라는 함수가 있다.

우선순위 큐와 큐의 차이점

  • 일반적인 큐: 선형적인 형태를 가지고 있다.
  • 우선순위 큐: 트리(Tree) 구조로 보는 것이 합리적이다.

시간 복잡도

  • 우선순위 큐의 삽입과 삭제는 𝑂(𝑙𝑜𝑔𝑁)의 시간 복잡도이다.
  • 우선순위 큐를 이용한 정렬은 𝑂(𝑁𝑙𝑜𝑔𝑁)의 시간 복잡도이다.
728x90

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

그리디  (0) 2022.06.20
이진 탐색  (0) 2022.06.20
BFS  (0) 2022.06.20
DFS  (0) 2022.06.20
투 포인터  (0) 2022.06.18
728x90

투 포인터

투 포인터는 2개의 포인터로 알고리즘의 시간 복잡도를 최적화한다.

  • 리스트에 순차적으로 접근해야 할 때 두 개의 점의 위치를 기록하면서 처리하는 알고리즘
  • 정렬되어있는 두 리스트의 합집합에도 사용된다.

시간 복잡도

투 포인터는 O(n)의 시간 복잡도를 가집니다.

 

1. sum에 누적을 시키면서 전체를 탐색하는 투 포인터 이동 원칙

투 포인터 이동 원칙을 활용해 배열의 끝까지 탐색하면서 합이 N이 될 경우의 수를 구한다.

start_index를 오른쪽으로 한 칸 이동하는 것은 연속된 자연수에서 왼쪽 값을 삭제하는 것과 효과가 같으며, end_index를 오른쪽으로 한 칸 이동하는 것은 연속된 자연수의 범위를 한 칸 더 확장한다는 의미이다. 같을 때는 1 증가시키고 end_index를 오른쪽으로 이동한다.

 

while(end_index != N) 일 때까지 반복

  • sum > N : sum = sum - start_index; start_index++;
  • sum < N : end_index++; sum = sum sum + end_index;
  • sum == N : end_index++; sum = sum + end_index; count++;

2. 2개를 가지고 전체를 탐색하는 투 포인터 이동 원칙

오름차순으로 정렬해야된다. 

while( i < j )

  • A[i] + A[j] > M : j--;   -> 번호의 합이 M보다 크므로 큰 번호 index를 내린다.
  • A[i] + A[j] < M : i++;  -> 번호의 합이 M보다 작으므로 작은 번호 index를 올린다.
  • A[i] + A[j] == M : i++; j--; count++;  -> 양쪽 포인터를 모두 이동시키고 count를 증가시킨다.
728x90

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

그리디  (0) 2022.06.20
이진 탐색  (0) 2022.06.20
BFS  (0) 2022.06.20
DFS  (0) 2022.06.20
스택과 큐  (0) 2022.06.18

+ Recent posts