728x90

플로이드-위셜

그래프에서 최단 거리를 구하는 알고리즘으로, 주요 특징은 음수 가중치가 있어도 수행할 수 있고, 동적 계획법의 원리를 이용해 알고리즘에 접근한다.

 

시간 복잡도(노드수 : V)

O(V^3)

 

플로이드-위셜 핵심 이론

플로이드-위셜 알고리즘을 도출하는 가장 핵심적인 원리는 A 노드에서 B 노드까지 최단 경로를 구했다고 가정했을 때 최단 경로 위에 K 노드가 존재한다면 그것을 이루는 부분 경로 역시 최단 경로라는 것이다.

 

플로이드 위셜 점화식

D[S][E] = Math.min(D[S][E], D[S][K] + D[K][E])

 

1. 배열을 선언하고 초기화하기

D[S][E]는 노드 S에서 노드 E까지의 최단 거리를 저장하는 배열이라 정의한다. S와 E의 값이 같은 같은 0, 다른 칸은 무한으로 초기화한다. 여기에서 S==E 는 자기 자신에게 가는 데 걸리는 최단 경로값을 의미하기 때문이다.

 

2. 최단 거리 배열에 그래프 데이터 저장하기

출발 노드는 S, 도착 노드는 E, 이 에지의 가중치는 W라고 했을 때 D[S][E] = W로 에지의 정보를 배열에 입력한다. 이로써 플로이드-위셜 알고리즘은 그래프를 인접 행렬로 표현한다는 것을 알 수 있다.

 

3. 점화식으로 배열 업데이트하기

기존에 구했던 점화식을 3중 for문 형태로 반복하면서 배열의 값을 업데이트한다.

 

플로이드-위셜 알고리즘 로직

for 경유지 K에 관해 (1 ~ N)

   for 출발 노드 S에 관해 (1 ~ N)

      for 도착 노드 E에 관해 (1 ~ N)

        D[S][E] = Math.min(D[S][E], D[S][K] + D[K][E]) 

 

플로이드-위셜 알고리즘은 모든 노드 간의 최단 거리를 확인해 주기 때문에 시간 복잡도가 빠르지 않은 편이다. 플로이드-위셜 알고리즘을 사용해야 하는 문제가 나오면 일반적으로 노드의 개수의 범위가 다른 그래프에 적게 나타난다는 것을 알 수 있다.

 

코드로 표현

static int N,M;
static int distance[][];

distance = new int[n + 1][n + 1];

for(int i = 1; i <= N; i++) { // 인접 행렬 초기화
	for(int j = 1; j <= N; j++) {
    	if(i == j) {
        	distance[i][j] = 0;
        }
        else {
        	distance[i][j] = 1000001; // 충분히 큰 수로 저장
        }
    }
}
for(int i = 0; i < M; i++) {
	int s, e, v 입력받기
    if(distance[s][e] > v) {
    	distance[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(distance[i][j] > distance[i][k] + distance[k][j])
            	distance[i][j] = distance[i][k] + distance[k][j];
            }
        }
    }
}
출력 부분

 

728x90

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

조합  (0) 2022.06.28
최소 신장 트리  (0) 2022.06.23
벨만 - 포드  (0) 2022.06.21
다익스트라  (0) 2022.06.21
위상 정렬  (0) 2022.06.21
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

32번 동전 0


내가 떠올린 풀이 해설

동전 값이 큰 동전부터 탐색을 하면서 k와 작거나 같은 동전이 나올 때까지 탐색하고 count 값은 k / (동전 값)으로 추가해주고 k % 동전 값으로 k의 값을 바꿔준다. 끝으로 count를 출력한다.


정확한 풀이

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

	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 k = Integer.parseInt(st.nextToken());
		
		int []arr = new int[n];
		for(int i = 0; i < n; i++) {
			arr[i] = Integer.parseInt(br.readLine());
		}
		int div = 0;
		int cnt = 0;
		int max = 0;
		for(int i = n - 1; i >= 0; i--) {
			if(k >= arr[i]) {
				div = arr[i];
				cnt += k / div;
				k = k % div;
			}
		
		}
		System.out.println(cnt);
	}
}

33번 카드 정렬하기


내가 떠올린 풀이 해설

우선순위 큐를 활용해서 풀어야하는 문제이다. 2개의 값을 합해서 비교해야 한다. 문제의 조건대로 연산을 하면 (10+20) + (30 + 40)이다. 앞의 2개를 더한 값을 바로 사용하므로 더한 값을 큐에 계속 넣어줘야 한다. 그냥 큐를 사용하면 30은 맨 뒤로 들어가 곧바로 연산에 수행될 수 없다. 최소한의 비교를 하기 위해서는 낮은 값부터 먼저 더해야 하므로 오름차순으로 정렬된 상태에서 값을 더하기 위해 우선순위 큐를 사용해야 한다.


정확한 풀이

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

	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		int n = Integer.parseInt(br.readLine());
		PriorityQueue<Integer> pq = new PriorityQueue<>();
		
		for(int i = 0; i < n; i++) {
			int data = Integer.parseInt(br.readLine());
			pq.add(data);
		}
		int data1 = 0;
		int data2 = 0;
		int sum = 0;
		
		while(pq.size() != 1) {
			data1 = pq.remove();
			data2 = pq.remove();
			sum += data1 + data2;
			pq.add(data1 + data2);
		}
		System.out.println(sum);
	}
}

34번 수 묶기


내가 떠올린 풀이 해설

가능한 큰 수들 끼리 묶어야 결괏값이 커진다. 또한 음수끼리 곱하면 양수로 되는 성질을 고려해야 된다. 양수 우선순위 큐와 음수 우선순위 큐와 1의 개수 0의 개수를 카운트하는 변수를 만든다. 데이터를 4개 그룹에 분리해 저장 4개 그룹은 1보다 큰 양수, 1의 개수, 0의 개수, 음수이다. while문을 양수 우선순위 큐 크기가 2보다 작을 때까지 반복하는데 수 2개를 큐에서 뽑고 2 개수를 곱한 값을 결괏값에 더한다.

양수 우선순위 큐에 데이터가 남아있으면 이 데이터를 결과값에 더한다. 음수도 위의 양수와 같이 반복한다 만약 음수 우선순위 큐에 데이터가 남아 있고, 데이터 0이 1개도 없으면 이 데이터를 결과 값에 더한다. 마지막으로 숫자 1의 개수를 결괏값에 더함


정확한 풀이

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

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		int n = Integer.parseInt(br.readLine());
		PriorityQueue<Integer> plusPq = new PriorityQueue<>(Collections.reverseOrder());
		PriorityQueue<Integer> minusPq = new PriorityQueue<>();
		
		int one = 0;
		int zero = 0;
		
		for(int i = 0; i < n; i++) {
			int data = Integer.parseInt(br.readLine());
			if(data > 1) {
				plusPq.add(data);
			}
			else if(data == 1) {
				one++;
			} 
			else if(data == 0) {
				zero++;
			}
			else {
				minusPq.add(data);
			}
		}
		int sum = 0;
		while(plusPq.size() > 1) {
			int first = plusPq.remove();
			int second = plusPq.remove();
			sum = sum + first * second;
		}
		if(!plusPq.isEmpty()) {
			sum = sum + plusPq.remove();				
		}
		while(minusPq.size() > 1) {
			int first = minusPq.remove();
			int second = minusPq.remove();
			sum = sum + first * second;
		}
		if(!minusPq.isEmpty()) {
			if(zero == 0) {
				sum = sum + minusPq.remove();				
			}
		}
		sum = sum + one;
		System.out.println(sum);
	}
}

35번 회의실 배정


내가 떠올린 풀이

예전에 풀었던 문제와 비슷한 문제여서 어렵지 않게 풀었다. 끝나는 시간으로 정렬을 하고 시작시간으로 정렬을 한다. 끝나는 시간을 기준으로 겹치지 않는 회의실을 선택한다. 


정확한 풀이

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

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine());
		ArrayList<Meet> list = new ArrayList<>();
		
		for(int i = 0; i < n; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());	
			int x = Integer.parseInt(st.nextToken());
			int y = Integer.parseInt(st.nextToken());
			list.add(new Meet(x, y));
		}
		int cnt = 0;
		Collections.sort(list);
		int et = 0;
		for(Meet b : list) {
			if(b.x > et) {
				cnt++;
				et = b.y;
			}
		}
		System.out.println(cnt);
	}
}
class Meet implements Comparable<Meet> {
	int x;
	int y;
	Meet(int x, int y) {
		this.x = x;
		this.y = y;
	}
	@Override
	public int compareTo(Meet o) {
		if(this.x == o.x) {
			return this.y - o.y;
		}
		return this.x - o.x;
	}
}

오늘의 회고

오늘은 그리디 알고리즘에 배워보는 시간이였습니다. 오늘 문제들도 쉽지 않은 문제들로 구성이 돼있어 힘들었습니다. 알고리즘을 풀면서 고민해도 답을 못 풀 경우에 답을 보고 다시 풀어야 되나 아니면 계속 고민해야 되나 라는 고민이 생기는데 계속 고민해 보는 것이 가장 좋은 방법이라고 생각하는데 저는 시간의 제약 때문에 답을 보고 다시 풀고 복습하고 공부하는 식으로 내 것으로 만들고 있습니다. 아직 풀 수 있는 문제가 많진 않지만 계속해서 공부하겠습니다.

728x90
728x90

11번 문제 스택으로 오름차순 수열 만들기


잘못된 풀이

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

	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];
		for(int i = 0; i < n; i++) {
			arr[i] = Integer.parseInt(br.readLine());
		}
		int [] arr2 = arr.clone();
		
		Arrays.sort(arr2);
		
		Stack<Integer> stack = new Stack<>();
		int i = 0;
		int j = 0;
		
		
		//System.out.println(stack);
		//stack = 1
		while(j != n) {
			if(stack.isEmpty()) {
				stack.push(arr2[i]);
				System.out.println("+");
			}
			if(arr[j] != stack.peek()) {  //i 가 [0] 4 != 1 true
				stack.push(arr2[i + 1]);  // arr2[1] = 2 i = 1
				System.out.println("+");
				i++;
			}
			else if(arr[j] == stack.peek()) {
				stack.pop();
				System.out.println("-");
				j++;
			}
			else {
				System.out.println("NO");
			}
		}
	}
}

내가 떠올린 풀이 해설

배열을 하나 더 만들어서 복사해서 정렬한 다음 배열 인덱스 탐색하는 i와 j를 만들어서 문제를 풀려고 했다.

peek으로 스택 최상단에 있는 수와 비교했을 때 복사한 배열의 수와 다르면 push 하고 같으면 pop을 하려고 했다.


정확한 풀이

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

	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];
		for(int i = 0; i < n; i++) {
			arr[i] = Integer.parseInt(br.readLine());
		}
		int num = 1;
		boolean result = true;
		
		Stack<Integer> stack = new Stack<>();
		StringBuffer bf = new StringBuffer();
		
		for(int i = 0; i < n; i++) {
			int su = arr[i];
			if(su >= num) {
				while(su >= num) {
					stack.push(num++);
					bf.append("+\\n");
				}
				stack.pop();
				bf.append("-\\n");
			}
			else {
				int k = stack.pop();
				if(k < su) {
					System.out.println("NO");
					result = false;
					break;
				}
				else {

					bf.append("-\\n");
				}
			}
		}
		if(result) {
			System.out.println(bf.toString());
		}
	}
}

문제점

예제 출력 1번은 내가 짠 코드로 가능했지만 예제 출력 2번은 구현하지 못했다.

인덱스로 배열을 탐색했을 때 인덱스가 다음으로 이동했을 때 비교하기 전 인덱스로 다시 돌아가는 것을 구현하지 못했다.


12번 문제 오큰수 구하기


잘못된 풀이

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

	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];
		
		StringTokenizer st = new StringTokenizer(br.readLine());
		for(int i = 0; i < n; i++) {
			arr[i] = Integer.parseInt(st.nextToken());
		}
		Stack<Integer> stack = new Stack<>();
		//int i = 1;
		stack.push(arr[0]);
		for(int j = 1; j < n; j++) {
			for(int i = 1; i < n; i++) {
				if(stack.peek() < arr[i]) {   
					stack.push(arr[i]);       
					System.out.print(arr[i] + " ");  
					while(stack.size() != 1) {
						stack.pop();                          
					}
					stack.push(arr[i]);
					
				}
				else if(stack.peek() > arr[i]) {
					
				}
				else {
					System.out.print("-1 ");
					//i++;
				}
			}
		}
		br.close();
	}
}

내가 떠올린 풀이 해설

위에 풀었던 문제와 비슷한 방식을 떠올렸다. 이중 for문으로 for문이 돌면서 스택에 peek으로 젤 위에 값과 배열의 다음 값을 비교하는데 배열의 값이 크면 스택에 push 했다.


정확한 풀이

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

	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]; //수열배열 생성 
		int [] answer = new int [n]; //정답 배열 생성 
		StringTokenizer st = new StringTokenizer(br.readLine());
		for(int i = 0; i < n; i++) {
			arr[i] = Integer.parseInt(st.nextToken());
		}
		
		Stack<Integer> stack = new Stack<>();
		
		stack.push(0); // 처음에는 항상 스택이 비어 있으므로 최초 값을 push해 초기화 
		for(int i = 1; i < n; i++) {
			//스택이 비어있지 않고 현재 수열이 스택 top 인덱스가 가리키는 수열보다 클 경우 
			while(!stack.isEmpty() && arr[stack.peek()] < arr[i]) {
				answer[stack.pop()] = arr[i]; // 정답 배열에 오큰수를 현재 수열로 저장하기 
			}
			stack.push(i);  //신규 데이터 push 
		}
		while(!stack.isEmpty()) { //반복문을 다 돌고 나왔는데 스택이 비어 있지 않다면 빌 때까지 
			answer[stack.pop()] = -1; //스택에 쌓인 index에 -1을 넣고 
		}
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		for(int i = 0; i < n; i++) {
			bw.write(answer[i] + " ");
		}
		bw.close();
		br.close();
	}
}

문제점

위와 똑같은 문제가 있었다. 인덱스로 탐색을 해서 그 전으로 돌아와서 비교하는 법을 모르겠다.

그 전으로 돌아오지 못해 처음 5와 7을 잘 출력이 되는데 그 뒤로부터는 비교할 수가 없어서 -1, -1이 출력됩니다.

풀이를 보면 저번과 같이 BufferedWriter로 출력하는 것을 볼 수가 있는데 System.out.print로 출력을 할시 시간 초과가 발생합니다


13번 문제 카드 게임


정확한 풀이

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

	public static void main(String[] args) throws IOException{
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine());
		Queue<Integer> que = new LinkedList<>();
		
		for(int i = 1; i <= n; i++) {
			que.offer(i);
		}
		
		while(que.size() != 1) {
			que.poll();
			que.offer(que.poll());
		}
		System.out.println(que.poll());
	}
}

내가 떠올린 풀이 해설

큐에 대한 선입 선출의 개념을 알고 있으면 쉽게 풀 수 있는 문제라고 생각한다. 카드의 개수의 최대가 500,000이므로 시간 복잡도의 제약도 크지 않다.

 

 

오늘의 회고

오늘은 자료구조의 알고리즘을 풀어봤는데 조건에 따라 구현하는 방법이 어려웠습니다. 책에 있는 문제들이 기본 문제도 있지만 많이 어려운 문제도 있어 시간이 오래 걸리는 것 같습니다. 알고리즘 공부하면서 느끼는 점은 시간이 지날수록 알고리즘 공부에 대한 방향성이 조금씩은 잡혀가고 있는 것 같습니다. 내일은 오늘 풀었던 문제 복습과 스택, 큐, 덱에 대한 오늘보단 쉬운 5문제를 풀겠습니다.

728x90

+ Recent posts