백준 15686: 치킨 배달 [Gold 2] / Java
백준 15686: 치킨 배달https://www.acmicpc.net/problem/15686   문제 설명크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸, 왼쪽에서부터 c번째 칸을 의미한다. r과 c는 1부터 시작한다.이 도시에 사는 사람들은 치킨을 매우 좋아한다. 따라서, 사람들은 "치킨 거리"라는 말을 주로 사용한다. 치킨 거리는 집과 가장 가까운 치킨집 사이의 거리이다. 즉, 치킨 거리는 집을 기준으로 정해지며, 각각의 집은 치킨 거리를 가지고 있다. 도시의 치킨 거리는 모든 집의 치킨 거리의 합이다.임의의 두 칸 (r1, c1)과 (..
2024.10.16
알고리즘 : KMP (패턴 매칭) 알고리즘 (Java)
KMP 알고리즘 패턴 매칭 알고리즘의 하나로, 주어진 문자열에서 패턴과 일치하는 부분 문자열이 있는지 찾는 알고리즘일반적인 반복문을 통한 해결방법은 O(N^M)의 시간복잡도를 가지지만, 해당 알고리즘은 O(N+M)의 시간복잡도로 매우 효율적이다.패턴에서 접두사와 접미사를 이용해 중복된 검색을 줄이는 방식임    동작 원리1. 패턴 문자열에서 0부터 i번째 문자열로 잘랐을 때 접미사와 접두사가 같은 최대 길이를 기록한 배열(LPS)을 생성한다. 2. 텍스트에서 패턴을 비교하고, 검색 중 불일치가 발생하면 LPS배열을 참고해 패턴의 다음 탐색  시작 위치를 결정한다.    예시 텍스트: "ABC ABCDAB ABCDABCDABDE"패턴: "ABCDABD" 1. LPS (부분 일치 테이블) 생성0부터 i번째 ..
2024.10.15
Cookie, Session, HTTP
HTTP란?HyperText Transfer Protocol웹에서 데이터를 주고 받을 때 지켜야 할 통신 규약!대표적 HTTP 메서드 : GET, POST, PUT, DELETEURL : 요청을 보낼 때, 어디로 요청을 보낼지에 대한 정보.헤더(Header): 요청 보낼 때 필요한 추가 정보 (ex. 브라우저 종류, 언어 설정 등본문(Body) : POST의 경우, 서버에 보낼 데이터를 본문에 포함시킬 수 있음.비연결성 : 요청과 응답이 종료되면 서로의 상태를 기억하지 않는다!무 상태: 서버가 클라이언트 상태를 저장하지 않는다. => 쿠키와 세션이 필요     Cookie 쿠키란?웹 서버가 만들어 클라이언트의 브라우저에 저장하는 작은 데이터 조각클라이언트 요청 시 상황에 따라 서버로 같이 전송브라우저가 ..
2024.10.14
JSP 특징, 장단점, Scriptlet 등
JSP란?서버 측에서 동적으로 HTML, XML등의 문서를 생성하기 위해 사용하는 자바 기반 기술HTML 문서에서 Java코드를 작성할 수 있도록 혼합JSTL등의 태그 라이브러리가 지원되어 편의성 향상작성된 후 서블릿으로 변환된 후 실행      JSP 구성 요소지시자 페이지 설정 관련 정보 지정을 위해 사용스크립트 요소(스크립트릿, 표현식, 선언부)문서 내용을 동적으로 생성하기 위해 사용기본객체요청과 응답을 읽고 처리하기 위해 기본으로 생성되어 있음표현언어(Expression Language)JSP를 더 간편하게 작성하기 위해 사용       JSP 태그스크립트릿 : 자바 코드를 작성하는 구간 (메서드 영역) 선언: 변수와 메서드를 선언하는 구간(클래스 영역)표현식: 특정 값이나 결과물을 문자열로 출력..
2024.10.08
백준 11053: 가장 긴 증가하는 부분 수열 11053: 가장 긴 증가하는 부분 수열 [Silver 2]
백준 11053 : 가장 긴 증가하는 부분 수열https://www.acmicpc.net/problem/11053  문제 설명수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다.  입력첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000)이 주어진다.둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000)   출력첫째 줄에 수열 A의 가장 긴 증가하는 부분 수열의 길이를 출력한다.   내 답 1. 수열(arr) 배열, 길이를 저장할 배열(tmp) 각각 선언 2...
2024.10.07
Java Servlet 특징, 생명주기, 처리방식 등
Servlet?Server + Applet의 합성어Servlet은 웹 애플리케이션 개발에서 동적 웹페이지 생성클라이언트의 요청을 해석하고 응답을 생성하는 서버측 요소자바 코드 안에 HTML을 포함주로 HTTP 프로토콜을 사용해 클라이언트와 상호작용Apache Tomcat과 같은 웹서버에서 실행된다!      Servlet 생명주기0. Servlet 인스턴스가 없을 경우, Servlet 클래스를 불러오고, 웹컨테이너에 의해 Servlet 인스턴스를 초기화, 생성함.  1. init()Servlet이 처음 로드될 때 한 번만 호출된다. 따라서 여러 변수나 조건들을 초기화하는 기능을 한다.  2. service()클라이언트의 요청을 처리할 때마다 호출되며, 요청 메서드(GET, POST 등)에 따라 적절한 처..
2024.10.06
알고리즘 : 동적계획법(다이나믹 프로그래밍) 알고리즘 (Java)
동적계획법(Dynamic Programming) 알고리즘 복잡한 문제를 풀기 위해 작은 하위 문제로 나누고, 이전 계산 결과를 저장하여 반복 계산을 줄이는 방법피보나치, DFS, BFS, 완전 탐색 등의 알고리즘으로는 반복되는 작업이 많을 때 최적화를 노리는 알고리즘    동작 원리- 문제를 최정 부분 구조로 나누고, 중복되는 하위 문제를 해결. - 문제를 재귀적으로 풀 때 같은 문제를 여러번 계산하지 않고 한 번 계산한 결과를 저장해 다시 사용     장점하위 문제들의 결과를 저장해 반복을 줄임최적화, 그래프탐색 등 중복되는 연산이 있는 다양한 분야에 적용 가능 단점추가적 메모리가 필요할 수 있어 공간복잡도에서 불리할 수 있음중복 부분 문제를 만족하지 않는 문제에는 적용이 어려움   활용 사례1. 피보..
2024.10.05
알고리즘 : 플로이드-워셜 알고리즘 (Java)
플로이드-워셜 알고리즘 모든 정점 쌍 간 최단 경로를 찾는 알고리즘3개의 중첩 for문을 사용하는 매우 단순한 알고리즘!그래프의 두 정점 사이의 직접 연결된 경로가 있거나, 다른 정점 k를 경유하는 경로가 있을 경우, 둘 중 더 짧은 경로를 선택하는 방식시간복잡도 : O(N^3)   동작 원리1.  인접 행렬을 기반으로 모든 정점 간 최단 거리를 초기화. 인접하지 않은 경우는 무한대로 설정 2. 각 정점을 중간 노드로 설정해 두 정점 간 경로 갱신 3. 최종적으로 가장 짧은 경로를 도출    장점메모리를 비교적 덜 차지함음수 가중치가 있어도 적용할 수 있음간단하고 직관적임   단점시간복잡도가 급격히 증가하므로, 정점 수가 적을 때만 적용할 수 있음.음수 사이클이 존재하는 경우엔 적용 불가  구현 impo..
2024.10.03
no image
백준 11727: 2×n 타일링 2 [Silver 3]
성능 요약메모리: 12876 KB, 시간: 92 ms  문제 설명2×n 직사각형을 1×2, 2×1과 2×2 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오. 입력첫째 줄에 n이 주어진다. (1 ≤ n ≤ 1,000)출력첫째 줄에 2×n 크기의 직사각형을 채우는 방법의 수를 10,007로 나눈 나머지를 출력한다.  내 답    import java.util.Scanner;/* * 동적프로그래밍 문제. 길이가 2*n인 타일은 * (1) 길이가 n-2인 타일에서 길이 2짜리 타일을 붙이는 경우 * (2) 길이가 n-1인 타일에서 길이 1짜리 타일을 붙이는 경우 * 2가지 경우를 합친 것과 같다. * * 여기서 길이가 2인 타일을 붙이는 경우에서 세로막대 2개(|||) * 가 붙은 경우 길..
2024.10.02
알고리즘 : 다익스트라 알고리즘 (Java)
다익스트라 알고리즘 한 정점에서 다른 모든 정점까지의 최단 경로를 구하는 알고리즘겨올탐색, 네트워크 라우팅 등 분야에서 활용될 수 있음그리디 기법    동작 원리1. 출발 정점에서 시작. 인접 정점 중 가장 짧은 경로를 갖는 정점을 선택 2. 선택 정점에서 다른 인접 정점까지의 거리를 계산하고, 이전에 계산된 거리와 비교해 더 짧은 경로가 발견되면 해당 경로로 업데이트. 3. 같은 방법으로 최단 경로를 찾을 때까지 진행     장점탐색속도: 모든 간선이 음수가 아닐 때 빠른 탐색 속도로 경로 탐색 가능우선순위 큐를 활용해 시간 복잡도 절감 가능 단점음수 가중치가 있는 그래프에서는 작동 X배열을 활용한 방법의 경우 간선이 많을수록 성능 저하될 수 있음   구현1.방문체크배열(visited), 인접배열(ad..
2024.09.28

 

백준 15686: 치킨 배달
 
 

문제 설명

크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸, 왼쪽에서부터 c번째 칸을 의미한다. r과 c는 1부터 시작한다.

이 도시에 사는 사람들은 치킨을 매우 좋아한다. 따라서, 사람들은 "치킨 거리"라는 말을 주로 사용한다. 치킨 거리는 집과 가장 가까운 치킨집 사이의 거리이다. 즉, 치킨 거리는 집을 기준으로 정해지며, 각각의 집은 치킨 거리를 가지고 있다. 도시의 치킨 거리는 모든 집의 치킨 거리의 합이다.

임의의 두 칸 (r1, c1)과 (r2, c2) 사이의 거리는 |r1-r2| + |c1-c2|로 구한다.

예를 들어, 아래와 같은 지도를 갖는 도시를 살펴보자.

 

0 2 0 1 0
1 0 1 0 0
0 0 0 0 0
0 0 0 1 1
0 0 0 1 2

 

0은 빈 칸, 1은 집, 2는 치킨집이다.

(2, 1)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |2-1| + |1-2| = 2, (5, 5)에 있는 치킨집과의 거리는 |2-5| + |1-5| = 7이다. 따라서, (2, 1)에 있는 집의 치킨 거리는 2이다.

(5, 4)에 있는 집과 (1, 2)에 있는 치킨집과의 거리는 |5-1| + |4-2| = 6, (5, 5)에 있는 치킨집과의 거리는 |5-5| + |4-5| = 1이다. 따라서, (5, 4)에 있는 집의 치킨 거리는 1이다.

이 도시에 있는 치킨집은 모두 같은 프랜차이즈이다. 프렌차이즈 본사에서는 수익을 증가시키기 위해 일부 치킨집을 폐업시키려고 한다. 오랜 연구 끝에 이 도시에서 가장 수익을 많이 낼 수 있는 치킨집의 개수는 최대 M개라는 사실을 알아내었다.

도시에 있는 치킨집 중에서 최대 M개를 고르고, 나머지 치킨집은 모두 폐업시켜야 한다. 어떻게 고르면, 도시의 치킨 거리가 가장 작게 될지 구하는 프로그램을 작성하시오.

 

 

입력

첫째 줄에 N(2 ≤ N ≤ 50)과 M(1 ≤ M ≤ 13)이 주어진다.

둘째 줄부터 N개의 줄에는 도시의 정보가 주어진다.

도시의 정보는 0, 1, 2로 이루어져 있고, 0은 빈 칸, 1은 집, 2는 치킨집을 의미한다. 집의 개수는 2N개를 넘지 않으며, 적어도 1개는 존재한다. 치킨집의 개수는 M보다 크거나 같고, 13보다 작거나 같다.

 

 

 

출력

첫째 줄에 폐업시키지 않을 치킨집을 최대 M개를 골랐을 때, 도시의 치킨 거리의 최솟값을 출력한다.

 

 

 

내 답

1차 시도 (오답)

1. 도시 상황을 2차원 배열에 입력, 치킨집 좌표를 chickens 리스트에 저장

 

2. 모든 치킨집 중 M개의 치킨집을 선택하는 조합 메서드를 재귀함수를 이용해 구현. pick 배열에 저장

 

3. map을 탐색하며 집일 경우 BFS를 통해 최단거리를 구하고, 최소값을 선택.

 

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class G5_치킨배달 {
	static class Chicken {
		int r, c;

		public Chicken() {
		}

		public Chicken(int r, int c) {
			super();
			this.r = r;
			this.c = c;
		}

		@Override
		public String toString() {
			return "Chicken [r=" + r + ", c=" + c + "]";
		}
	}

	static int N, M, min;
	static int[][] map;
	static int[][] tmpMap;
	static boolean[][] visited;
	static ArrayList<Chicken> chickens;
	static Chicken[] pick;
	static int[] dr = {-1, 1, 0, 0};
	static int[] dc = {0, 0, -1, 1};

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);

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

		min = Integer.MAX_VALUE; // 치킨 거리의 최소값을 저장할 변수

		map = new int[N][N];
		tmpMap = new int[N][N];
		visited = new boolean[N][N];
		chickens = new ArrayList<>(); // 치킨집 좌표를 담을 리스트
		pick = new Chicken[M]; // M개를 뽑은 후 저장할 배열

		// 맵 입력 및 치킨집 탐색
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				map[r][c] = sc.nextInt();
				if (map[r][c] == 2) {
					chickens.add(new Chicken(r, c));
					map[r][c] = 0; // 지도상으로는 0으로 초기화
				}
			}
		}
		pick(0, 0);
		
		System.out.println(min);
	}

	private static void pick(int n, int r) {
		// 모두 골랐으면 탐색 시작
		if (r >= M) {
			editMap();
			int tmpDist = search();
			if (min > tmpDist) {
				min = tmpDist;
			}
			return;
		}
		// 남은 치킨집 수보다 골라야 할 치킨집 수가 많으면 부적절하므로 종료
		if (n >= chickens.size()) {
			return;
		}

		// 해당 치킨집을 안고른다
		pick(n + 1, r);
		// 해당 치킨집을 고른다
		pick[r] = chickens.get(n);
		pick(n + 1, r + 1);
	}

	// 선택한 치킨집만 지도상에 2로 표시
	private static void editMap() {
		for (int r=0; r<N; r++) {
			tmpMap[r] = map[r].clone();
		}
		for (Chicken chicken : pick) {
			tmpMap[chicken.r][chicken.c] = 2;
		}
	}

	// 지도 탐색하여 집일 경우 BFS탐색하여 거리 계산
	private static int search() {
		int sum = 0;
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				if (tmpMap[r][c] == 1) {
					int dist = BFS(r, c);
					sum += dist;
				}
			}
		}
		return sum;
	}

	private static int BFS(int r, int c) {
		Queue<int[]> q = new LinkedList<>();

		int dist = 0;
		int size = 1;
		visited = new boolean[N][N];

		visited[r][c] = true; // 시작지점 방문체크
		q.add(new int[] { r, c }); // 큐에 삽입

		while (!q.isEmpty()) {

			for (int i = 0; i < size; i++) {
				int[] curr = q.poll();
				// 치킨집이면 탐색 종료
				if (tmpMap[curr[0]][curr[1]] == 2) {
					return dist;
				}
				// 4방탐색
				for (int dir = 0; dir<4; dir++) {
					int nr = curr[0] + dr[dir];
					int nc = curr[1] + dc[dir];
					
					if (nr<0 || nr>=N || nc<0 || nc>=N || visited[nr][nc]) continue;
					
					q.add(new int[] {nr, nc});
					visited[nr][nc] = true;
				}
			}
			size = q.size();
			dist += 1;
		}
		
		return dist;
	}
}

 

 

2차 시도 (정답)

이전 시도에서 최단거리를 구하기 위한 방법으로 BFS를 선택했으나,

 

BFS는 사방을 탐색해가며 최단거리를 구할 때 배열 크기가 커지고 집과 치킨집 간 거리가 멀어질수록

 

급격히 비효율적으로 변함을 느꼈다.

 

따라서 미리 집과 치킨집의 좌표를 저장해두고, 일일히 계산하는 방법으로 코드를 수정했다 !

 

 

 

1. 도시 상황을 2차원 배열에 입력, 치킨집과 집의 좌표를 각각 chickens, houses 리스트에 저장

 

2. 모든 치킨집 중 M개의 치킨집을 선택하는 조합 메서드를 재귀함수를 이용해 구현. pick 배열에 저장

 

3. 집과 치킨집 간 거리를 모두 계산한 후 최소값을 선택한다.

public class G5_치킨배달 {
	static class Chicken {
		int r, c;

		public Chicken() {
		}

		public Chicken(int r, int c) {
			super();
			this.r = r;
			this.c = c;
		}

		@Override
		public String toString() {
			return "Chicken [r=" + r + ", c=" + c + "]";
		}
	}
	static class House {
		int r, c;
		public House() {
			// TODO Auto-generated constructor stub
		}
		public House(int r, int c) {
			super();
			this.r = r;
			this.c = c;
		}
		@Override
		public String toString() {
			return "House [r=" + r + ", c=" + c + "]";
		}
	}
	static int N, M, min;
	static int[][] map;
	static ArrayList<Chicken> chickens;
	static ArrayList<House> houses;
	static Chicken[] pick;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);

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

		min = Integer.MAX_VALUE; // 치킨 거리의 최소값을 저장할 변수

		map = new int[N][N];
		chickens = new ArrayList<>(); // 치킨집 좌표를 담을 리스트
		houses = new ArrayList<>(); // 집 좌표를 담을 리스트
		pick = new Chicken[M]; // M개를 뽑은 후 저장할 배열

		// 맵 입력 및 치킨집 탐색
		for (int r = 0; r < N; r++) {
			for (int c = 0; c < N; c++) {
				map[r][c] = sc.nextInt();
				if (map[r][c] == 2) {
					chickens.add(new Chicken(r, c));
					map[r][c] = 0; // 지도상으로는 0으로 초기화
				} else if (map[r][c] == 1) {
					houses.add(new House(r, c));
				}
			}
		}
		pick(0, 0);
		
		System.out.println(min);
	}

	private static void pick(int n, int r) {
		// 모두 골랐으면 탐색 시작
		if (r >= M) {
			int tmpDist = search();
			if (min > tmpDist) {
				min = tmpDist;
			}
			return;
		}
		// 남은 치킨집 수보다 골라야 할 치킨집 수가 많으면 부적절하므로 종료
		if (n >= chickens.size()) {
			return;
		}

		// 해당 치킨집을 안고른다
		pick(n + 1, r);
		// 해당 치킨집을 고른다
		pick[r] = chickens.get(n);
		pick(n + 1, r + 1);
	}

	// 모든 집 탐색하며 선택한 각 치킨집과의 최소 거리를 구해 마을의 총 치킨거리 계산
	private static int search() {
		int sum = 0;
		for (House h : houses) {
			int tmpMin = Integer.MAX_VALUE;
			for (Chicken c : pick) {
				tmpMin = Math.min(Math.abs(h.r-c.r)+Math.abs(h.c-c.c), tmpMin);
			}
			sum += tmpMin;
		}
		return sum;
	}
}

 

복습 사항

배열 깊은 복사(deep copy)

깊은복사는 배열의 주소값이 아닌 실제 값을 복사하는 것을 말함

 

일반적으로 copyArr = originArr 의 방식으로 복사하면 원본 배열의 주소값이 복사되기 떄문에,

 

copyArr의 값을 수정하면 originArr의 값도 같이 변한다!

 

따라서 깊은 복사가 필요할 땐 아래와 같이 할 것

int[][] newMap = new int[N][N];
for (int i = 0; i < N; i++) {
    newMap[i] = map[i].clone();  // 각 행을 개별적으로 복사
}

 

KMP 알고리즘

 

  • 패턴 매칭 알고리즘의 하나로, 주어진 문자열에서 패턴과 일치하는 부분 문자열이 있는지 찾는 알고리즘

  • 일반적인 반복문을 통한 해결방법은 O(N^M)의 시간복잡도를 가지지만, 해당 알고리즘은 O(N+M)의 시간복잡도로 매우 효율적이다.

  • 패턴에서 접두사와 접미사를 이용해 중복된 검색을 줄이는 방식임

 

 

 

 

동작 원리

1. 패턴 문자열에서 0부터 i번째 문자열로 잘랐을 때 접미사와 접두사가 같은 최대 길이를 기록한 배열(LPS)을 생성한다.

 

2. 텍스트에서 패턴을 비교하고, 검색 중 불일치가 발생하면 LPS배열을 참고해 패턴의 다음 탐색  시작 위치를 결정한다.

 

 

 

 

예시

 

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

 

1. LPS (부분 일치 테이블) 생성

0부터 i번째 문자열로 잘랐을 때 접미사와 접두사가 같은 최대 길이를 기록한 배열(LPS)을 생성한다.

 

문자 A B C D A B D
인덱스 0 1 2 3 4 5 6
LPS 값 0 0 0 0 1 2 0

 

 

2. 문자열 검색 시작

텍스트와 패턴이 일치하지 않을 경우, LPS 테이블을 사용하여 패턴 내에서 최대한 불필요한 재비교를 피함

1) 처음 비교 시작 (i = 0, j = 0)

텍스트의 첫 번째 문자 A와 패턴의 첫 번째 문자 A를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

두 문자가 일치하므로 ij를 각각 1씩 증가시킵니다.

 

 

2) 다음 문자 비교 (i = 1, j = 1)

다음으로 텍스트의 두 번째 문자 B와 패턴의 두 번째 문자 B를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

일치하므로 ij를 다시 1씩 증가

 

 

3) 계속되는 비교 (i = 2, j = 2)

텍스트의 세 번째 문자 C와 패턴의 세 번째 문자 C도 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

일치하므로 i = 3j = 3으로 이동

 

 

4) 불일치 발생 (i = 3, j = 3)

네 번째 문자에서 텍스트의 문자 (공백)과 패턴의 문자 D가 일치하지 않음

  • 텍스트: "ABC ** **ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

불일치 시, LPS 테이블을 참고하여 패턴의 어느 위치로 돌아갈지 결정함. 현재 j = 3이고, LPS 테이블에서 LPS[3] = 0이므로, 패턴의 시작 부분으로 돌아야 함(j = 0). 하지만 텍스트에서 이미 비교한 문자는 건너뛰지 않고 계속 비교하므로 i는 4로 증가

 

5) 패턴 계속 비교 (i = 4, j = 0)

패턴을 다시 처음부터 시작하면서, 텍스트의 4번째 문자 A와 패턴의 첫 번째 문자 A를 비교

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

문자가 일치하므로 i = 5j = 1로 이동

 

6) 패턴 계속 비교 (i = 5 ~ 7, j = 1 ~ 3)

텍스트의 문자 B, C, D와 패턴의 문자 B, C, D가 차례로 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"
  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"
  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

i = 8j = 4로 이동

 

7) 다시 불일치 발생 (i = 8, j = 4)

여기서 텍스트의 문자 A와 패턴의 문자 A가 일치

  • 텍스트: "ABC ABCDAB ABCDABCDABDE"
  • 패턴: "ABCDABD"

다시 일치하므로 i = 9j = 5로 이동

 

 

8) 최종 불일치 (i = 10, j = 6)

그러나 그다음 문자에서 텍스트의 문자 B와 패턴의 문자 B가 일치

  • 텍스트: "ABC ABCDABB ABCDABCDABDE"
  • 패턴: "ABCDABBD"

마지막으로 D가 일치

 

 

 

장점

  • 시간 복잡도를 효과적으로 줄일 수 있음

 

단점

  • 과정이 복잡해 이해하기 어려움

  • 초기 LPS 배열을 만드는 단계가 있어 패턴 크기가 작으면 오히려 비효율적일 수 있음

 

 

 

 

 

 

코드 구현

 

DP를 이용한 코드

 

>> 이전 항의 값을 별도 배열에 저장!

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class KMP알고리즘 {
	static char[] text;
	static char[] pattern;
	static int[] pi;
	static int max;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		text = br.readLine().toCharArray();
		pattern = br.readLine().toCharArray();
		
		KMP();
	}

	private static void KMP() {
		getPi();
		int len = text.length;
		
		int j = 0;
		max = 0;
		for (int i=0; i<len; i++) {
			while (j>0 && text[i] != pattern[j]) {
				if (max < j) {
					max = j;
				}
				j = pi[j-1];
			}
			
			if (text[i] == pattern[j]) {
				// j 패턴 끝까지 도달했으면 종료
				if (j == pattern.length-1) {
					System.out.println(pattern.length);
					return;
				} else {
					j++;
				}
			}
		}
		System.out.println(max);
	}
	
	// 패턴 문자배열을 0부터 i번 인덱스까지 잘랐을 때 
	// 접두사와 접미사가 같은 최대 개수를 담은 배열을 생성하는 메서드
	private static void getPi() {
		int len = pattern.length;
		pi = new int[len];
		
		int j=0;
		for (int i=1; i<len; i++) {
			// i위치와 j위치 값이 다를 때
			while (j>0 && pattern[i] != pattern[j]) {
				j=pi[j-1];
			}
			// 같을 때
			if (pattern[i] == pattern[j]) {
				pi[i] = ++j;
			}
		}
	}
}

 

Cookie, Session, HTTP

친환경 개발자
|2024. 10. 14. 23:59

HTTP란?

  • HyperText Transfer Protocol

  • 웹에서 데이터를 주고 받을 때 지켜야 할 통신 규약!

  • 대표적 HTTP 메서드 :
    GET, POST, PUT, DELETE

  • URL : 요청을 보낼 때, 어디로 요청을 보낼지에 대한 정보.

  • 헤더(Header): 요청 보낼 때 필요한 추가 정보 (ex. 브라우저 종류, 언어 설정 등
  • 본문(Body) : POST의 경우, 서버에 보낼 데이터를 본문에 포함시킬 수 있음.

  • 비연결성 : 요청과 응답이 종료되면 서로의 상태를 기억하지 않는다!

  • 무 상태: 서버가 클라이언트 상태를 저장하지 않는다.
     => 쿠키와 세션이 필요

 

 

 

 

 

Cookie

 

쿠키란?

  • 웹 서버가 만들어 클라이언트의 브라우저에 저장하는 작은 데이터 조각

  • 클라이언트 요청 시 상황에 따라 서버로 같이 전송

  • 브라우저가 바뀌면 쿠키는 남아있지 않음

 

목적

  • 클라이언트의 정보를 저장하여 활용할 필요가 있을 때
  • 사용자의 패턴 분석을 위해

  • 맞춤 광고, 장바구 등

특징

  • 서버가 아닌 사용자 브라우저에 저장됨

  • 하나의 쿠키는 약 4K
  • 유효기간을 설정할 수 있음

  • 클라이언트 측에 저장되므로 보안에 상대적으로 취약

 

Session

 

세션이란?

  • 사용자가 웹 서버에 접속한 상태를 하나의 단위로 봄
  • 서버 측에서 사용자 상태를 관리하는 방법
  • sessionId를 통해 각 세션을 구분하며, 이는 보통 쿠키로 관리됨

  • 이론상으로는 메모리의 제한이 없이 생성이 가능함

 

목적

  • 사용자 인증 상태 유지(로그인 상태 유지 등)



특징

  • 서버 측에 저장됨

  • 유효기간을 설정할 수 있으며, 만료되면 데이터 사라짐.

  • 서버의 메모리를 사용하므로, 사용자가 늘어나면 부담이 될 수 있음

  • 쿠키에 비해 보안성이 좋음

 

 

 

 

요청과 응답 과정

 

1. 사용자가 웹사이트에 방문

 

2. 서버에 세션 생성, 쿠키 생성하여 클라이언트에 응답 시 돌려줌

 

3. 클라이언트에 쿠키가 저장되며 세션ID가 함께 저장

 

4. 다음 요청 시 가지고 있던 쿠키를 함께 전달

 

5. 세션을 통한 상태 유지 (장바구니, 로그인 상태 등을 계속 유지함)

JSP 특징, 장단점, Scriptlet 등

친환경 개발자
|2024. 10. 8. 23:52

JSP란?

  • 서버 측에서 동적으로 HTML, XML등의 문서를 생성하기 위해 사용하는 자바 기반 기술


  • HTML 문서에서 Java코드를 작성할 수 있도록 혼합


  • JSTL등의 태그 라이브러리가 지원되어 편의성 향상


  • 작성된 후 서블릿으로 변환된 후 실행

 

 

 

 

 

 

JSP 구성 요소

  • 지시자 
    페이지 설정 관련 정보 지정을 위해 사용


  • 스크립트 요소(스크립트릿, 표현식, 선언부)
    문서 내용을 동적으로 생성하기 위해 사용


  • 기본객체
    요청과 응답을 읽고 처리하기 위해 기본으로 생성되어 있음

  • 표현언어(Expression Language)
    JSP를 더 간편하게 작성하기 위해 사용

 

 

 

 

 

 

 

JSP 태그

  • 스크립트릿 : 자바 코드를 작성하는 구간 (메서드 영역) 
    <% %>

  • 선언: 변수와 메서드를 선언하는 구간(클래스 영역)
    <%! %>

  • 표현식: 특정 값이나 결과물을 문자열로 출력하는 태그
    <%= %>

  • 주석 : 주석 작성 구간
    <%--   --%>

  • 지시자 : JSP 페이지에 대한 설정들을 지정할 수 있는 구간
    <%@ %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>제목</title>
</head>
<body>
	HTML 본문 구간
	<br>
	<%!
    // 변수, 메서드 선언 !!
	int a = 1;

	static void method(int a) {
		a = a + 1;
	}
	%>

	<%
	System.out.println("자바 코드 작성 가능한 영역");
	%>

	<%="문자열 출력 가능"%>

	<%-- 주석 작성 구간 --%>


</body>
</html>

 

 

 

 

 

 

지시자

 

  • page 지시자: JSP 페이지에 대한 전반적 속성을 정의한다
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.*" %>
  • include 지시자: 다른 JSP페이지나 파일을 현재 JSP 페이지에 삽입할 때 사용
<%@ include file="header.jsp" %>
  • taglib 지시자: JSTL등과 같은 태그 라이브러리 사용시 선언해야 함
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
 
백준 11053 : 가장 긴 증가하는 부분 수열
 
 

문제 설명

수열 A가 주어졌을 때, 가장 긴 증가하는 부분 수열을 구하는 프로그램을 작성하시오.

예를 들어, 수열 A = {10, 20, 10, 30, 20, 50} 인 경우에 가장 긴 증가하는 부분 수열은 A = {10, 20, 10, 30, 20, 50} 이고, 길이는 4이다.

 

 

입력

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000)이 주어진다.

둘째 줄에는 수열 A를 이루고 있는 Ai가 주어진다. (1 ≤ Ai ≤ 1,000)

 

 

 

출력

첫째 줄에 수열 A의 가장 긴 증가하는 부분 수열의 길이를 출력한다.

 

 

 

내 답

 

1. 수열(arr) 배열, 길이를 저장할 배열(tmp) 각각 선언

 

2. LIS 계산

   수열을 첫번째 배열부터 돌면서, 이전의 값들과 비교하며 LIS를 업데이트

   현재 값이 이전값과 같으면, 같은 순서로 저장.

   다를 경우 이전 항들을 순회하며 현재 값보다 작은 수 중 가장 높은 순서의 다음 순서를 저장한다.

import java.util.Arrays;
import java.util.Scanner;

public class S2_가장긴증가하는부분수열 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int N = sc.nextInt();
        int[] arr = new int[N];
        int[] tmp = new int[N];

        for (int i = 0; i < N; i++) {
            arr[i] = sc.nextInt();
        }
        Arrays.fill(tmp, 1);
        int max = 1;

        for (int i = 1; i < N; i++) {
            // 같은 값이면 이전 값과 같은 LIS 길이 유지
            if (arr[i] == arr[i - 1]) {
                tmp[i] = tmp[i - 1];
            } else {
                // 이전 값들을 탐색하여 LIS 갱신
                for (int j = 0; j < i; j++) {
                    if (arr[j] >= arr[i]) {
                        continue;
                    } else {
                        // 현재 값보다 작은 값들 중 최대 LIS 길이를 갱신
                        if (tmp[i] <= tmp[j]) {
                            tmp[i] = tmp[j] + 1;
                        }
                    }
                }
            }
            // 최대 LIS 길이 갱신
            max = Math.max(max, tmp[i]);
        }

        System.out.println(max);
    }
}

 

 

모법 답

 1. 이분 탐색을 활용하여 시간복잡도 O(N^2)에서 O(NlogN)으로 줄일 수 있다.

 

2. 리스트를 사용해 증가 부분 수열을 저장

 

3. 이분탐색으로 리스트에 삽입할 위치를 찾음

 

import java.util.ArrayList;
import java.util.Scanner;

public class LIS_이분탐색 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int[] arr = new int[N];

        for (int i = 0; i < N; i++) {
            arr[i] = sc.nextInt();
        }

        ArrayList<Integer> lis = new ArrayList<>(); // LIS를 저장할 리스트

        for (int num : arr) {
            // num이 LIS의 마지막 값보다 클 경우, LIS에 추가
            if (lis.isEmpty() || lis.get(lis.size() - 1) < num) {
                lis.add(num);
            } else {
                // num이 들어갈 위치를 이분 탐색으로 찾음
                int idx = binarySearch(lis, num);
                lis.set(idx, num); // LIS 갱신
            }
        }

        // LIS 리스트의 길이가 최종 결과
        System.out.println(lis.size());
    }

    // 이분 탐색을 통해 LIS에 들어갈 자리 찾기
    private static int binarySearch(ArrayList<Integer> lis, int num) {
        int left = 0;
        int right = lis.size() - 1;

        while (left < right) {
            int mid = (left + right) / 2;
            if (lis.get(mid) < num) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }

        return left;
    }
}

Servlet?

  • Server + Applet의 합성어

  • Servlet은 웹 애플리케이션 개발에서 동적 웹페이지 생성

  • 클라이언트의 요청을 해석하고 응답을 생성하는 서버측 요소

  • 자바 코드 안에 HTML을 포함

  • 주로 HTTP 프로토콜을 사용해 클라이언트와 상호작용

  • Apache Tomcat과 같은 웹서버에서 실행된다!

 

 

 

 

 

 

Servlet 생명주기

0. Servlet 인스턴스가 없을 경우, Servlet 클래스를 불러오고, 웹컨테이너에 의해 Servlet 인스턴스를 초기화, 생성함.

 

 

1. init()

Servlet이 처음 로드될 때 한 번만 호출된다. 따라서 여러 변수나 조건들을 초기화하는 기능을 한다.

 

 

2. service()

클라이언트의 요청을 처리할 때마다 호출되며, 요청 메서드(GET, POST 등)에 따라 적절한 처리를 담당.

doGet(), doPost, doRegist() 등의 메소드가 호출된다!

 

 

3. destroy()

서버 종료되거나 Servlet이 더 이상 사용되지 않을 때 호출되고, 리소스 정리 등 종료 작업을 처리

 

 

 

 

 

 

GET vs POST 요청의 차이

항목 GET POST
용도 서버에서 데이터를 가져올 때 사용 서버에 데이터를 보낼 때 사용
파라미터 전송 방식 URL에 쿼리스트링 형태로 전송 요청 body에 포함하여 전송
보안 데이터가 URL에 노출됨 본문에 데이터가 포함되어 비교적 안전
데이터 크기 제한적 (대략 2048자 내외) 대용량 데이터 전송 가능
캐싱 브라우저에 의해 캐싱될 수 있음 일반적으로 캐싱되지 않음

 

 

 

요청과 응답 처리

 

요청(HttpServletRequest 객체)

  • 클라이언트로부터 전달된 요청 정보를 읽어들이는 역할.
  • 요청 파라미터, 헤더, 쿠키 등의 정보를 받을 수 있다.

 

응답(HttpServletResponse 개체)

  • 요청을 처리한 후 클라이언트에 전송할 응답 정보를 작성하는 데 사용.
  • HTML, JSON, 상태 코드, 헤더, 쿠키 등을 포함할 수 있다.
// GET 요청 처리
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String parameter = request.getParameter("name");
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<h1>안녕하세요 " + parameter + "</h1>");
}

// POST 요청 처리
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String parameter = request.getParameter("name");
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println("<h1>안녕하세요 " + parameter + " (POST)</h1>");
}

 

 

 

 

 

- 포워드 방식

  • 서버 내부에서 요청을 처리한 후 알아서 새로운 웹페이지로 전달
  • 따라서 URL의 변화가 없고, 클라이언트는 이를 인지할 수 없음.
response.sendRedirect("newPage.jsp");

 

 

-리다이렉트 방식

  • 요청을 받은 후 처리를 완료하고 클라이언트에게 또다시 해당 URL로 요청하도록 응답을 보냄
  • 클라이언트는 해당 응답을 받고 그에 맞는 URL을 재요청함.
  • 따라서 URL의 변화가 있다.
RequestDispatcher dispatcher = request.getRequestDispatcher("nextServlet");
dispatcher.forward(request, response);

동적계획법(Dynamic Programming) 알고리즘

 

  • 복잡한 문제를 풀기 위해 작은 하위 문제로 나누고, 이전 계산 결과를 저장하여 반복 계산을 줄이는 방법
  • 피보나치, DFS, BFS, 완전 탐색 등의 알고리즘으로는 반복되는 작업이 많을 때 최적화를 노리는 알고리즘

 

 

 

 

동작 원리

- 문제를 최정 부분 구조로 나누고, 중복되는 하위 문제를 해결.

 

- 문제를 재귀적으로 풀 때 같은 문제를 여러번 계산하지 않고 한 번 계산한 결과를 저장해 다시 사용

 

 

 

 

 

장점

  • 하위 문제들의 결과를 저장해 반복을 줄임
  • 최적화, 그래프탐색 등 중복되는 연산이 있는 다양한 분야에 적용 가능

 

단점

  • 추가적 메모리가 필요할 수 있어 공간복잡도에서 불리할 수 있음
  • 중복 부분 문제를 만족하지 않는 문제에는 적용이 어려움

 

 

 

활용 사례

1. 피보나치 수열 계산 : 이전 2개 항의 합을 저장하기에 중복 계산이 많음. 이를 미리 저장해두어 중복 계산을 피함

 

2. 최장 증가 부분 수열 : 두 문자열에서 공통으로 존재하는 부분 수열 중 가장 긴 것을 찾는 문제

 

3. 배낭 문제 : 제한된 무게 내에서 가장 큰 가치를 찾는 문제

 

4. 동전 거스름돈 문제 : 주어진 금액을 만들기 위해 필요한 최소의 동전 개수를 찾는 문제

 

 

 

 

 

코드 구현 (피보나치 수열)

 

DP를 이용한 코드

 

>> 이전 항의 값을 별도 배열에 저장!

public class FibonacciDP {

    // 피보나치 수열을 동적 계획법으로 계산하는 메서드
    public static int fibonacci(int n) {
        if (n <= 1) {
            return n; // n이 0 또는 1이면 그 값을 반환
        }

        // 피보나치 수를 저장할 배열 선언
        int[] fib = new int[n + 1];
        fib[0] = 0; // F(0) 초기화
        fib[1] = 1; // F(1) 초기화

        // Bottom-Up 방식으로 피보나치 수열 계산
        for (int i = 2; i <= n; i++) {
            fib[i] = fib[i - 1] + fib[i - 2];
        }

        return fib[n]; // n번째 피보나치 수 반환
    }

    public static void main(String[] args) {
        int n = 10; // 예시로 n = 10
        System.out.println("피보나치 수열의 " + n + "번째 값: " + fibonacci(n));
    }
}

 

 

일반 코드

 

public class Fibonacci {

    // 피보나치 수열을 동적 계획법으로 계산하는 메서드
    public static int fibonacci(int n) {
    	if (n <= 0) {
    		return 0;
    	}
        if (n <= 2) {
        	return 1;
        }
       return fibonacci(n-1) + fibonacci(n-2);
    }

    public static void main(String[] args) {
        int n = 10; // 예시로 n = 10
        System.out.println("피보나치 수열의 " + n + "번째 값: " + fibonacci(n));
    }
}

플로이드-워셜 알고리즘

 

  • 모든 정점 쌍 간 최단 경로를 찾는 알고리즘

  • 3개의 중첩 for문을 사용하는 매우 단순한 알고리즘!

  • 그래프의 두 정점 사이의 직접 연결된 경로가 있거나, 다른 정점 k를 경유하는 경로가 있을 경우, 둘 중 더 짧은 경로를 선택하는 방식

  • 시간복잡도 : O(N^3)

 

 

 

동작 원리

1.  인접 행렬을 기반으로 모든 정점 간 최단 거리를 초기화. 인접하지 않은 경우는 무한대로 설정

 

2. 각 정점을 중간 노드로 설정해 두 정점 간 경로 갱신

 

3. 최종적으로 가장 짧은 경로를 도출

 

 

 

 

장점

  • 메모리를 비교적 덜 차지함

  • 음수 가중치가 있어도 적용할 수 있음

  • 간단하고 직관적임

 

 

 

단점

  • 시간복잡도가 급격히 증가하므로, 정점 수가 적을 때만 적용할 수 있음.

  • 음수 사이클이 존재하는 경우엔 적용 불가

 

 

구현

 

import java.util.Arrays;

public class FloydWarshall {

    // 무한대를 나타내는 상수
    static final int INF = Integer.MAX_VALUE;
    
    // 플로이드-워셜 알고리즘을 실행하는 메서드
    public static void floydWarshall(int[][] graph) {
        int V = graph.length;
        // 최단 거리를 저장할 배열 생성
        int[][] dist = new int[V][V];

        // 그래프 초기화: 초기 거리를 입력
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                dist[i][j] = graph[i][j];
            }
        }
		
        // 각 정점 k를 중간 정점으로 사용하여 경로를 최적화
        for (int k = 0; k < V; k++) {
            for (int i = 0; i < V; i++) {
                for (int j = 0; j < V; j++) {
                    // i에서 j로 가는 경로가 k를 거쳐 더 짧으면 업데이트
                    if (dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
            // 단계별로 배열 상태 출력
            System.out.println("경유 정점 " + k + "을(를) 거친 후 거리 배열:");
            printSolution(dist);
        }

        // 최종 결과 출력
        printSolution(dist);
    }

    // 최단 경로 배열을 출력하는 메서드
    public static void printSolution(int[][] dist) {
        int V = dist.length;
        System.out.println("정점 쌍 간의 최단 거리:");
        for (int i = 0; i < V; i++) {
            for (int j = 0; j < V; j++) {
                if (dist[i][j] == INF)
                    System.out.print("INF ");
                else
                    System.out.print(dist[i][j] + "   ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        // 그래프의 인접 행렬 표현 (0은 자기 자신으로 가는 경로)
        int[][] graph = { 
            { 0, 5, INF, 10 }, 
            { INF, 0, 3, INF }, 
            { INF, INF, 0, 1 }, 
            { INF, INF, INF, 0 }
        };

        // 플로이드-워셜 알고리즘 실행
        floydWarshall(graph);
    }
}

성능 요약

메모리: 12876 KB, 시간: 92 ms

 
 

문제 설명

2×n 직사각형을 1×2, 2×1과 2×2 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오.

 

입력

첫째 줄에 n이 주어진다. (1 ≤ n ≤ 1,000)

출력

첫째 줄에 2×n 크기의 직사각형을 채우는 방법의 수를 10,007로 나눈 나머지를 출력한다.

 

 

내 답

 

 

 

 

import java.util.Scanner;

/*
 *  동적프로그래밍 문제. 길이가 2*n인 타일은
 *  (1) 길이가 n-2인 타일에서 길이 2짜리 타일을 붙이는 경우
 *  (2) 길이가 n-1인 타일에서 길이 1짜리 타일을 붙이는 경우
 *  2가지 경우를 합친 것과 같다.
 *  
 *  여기서 길이가 2인 타일을 붙이는 경우에서 세로막대 2개(|||)
 *  가 붙은 경우 길이 1짜리 타일을 붙이는 경우와 중복되는 경우가 있으므로
 *  해당 중복을 제외해준다.
 *  
 *  따라서
 *  dp[N] = dp[N-2] * dp[2] + dp[N-1] * dp[1] - dp[N-2]
 *  	  = 2*dp[N-2] + dp[N-1]
 */

public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		int N = sc.nextInt();
		long[] dp = new long[N+1];
		// N이 1일 땐 1을 출력하고 종료
		if (N==1) {
			System.out.println(1);
			return;
		}
		// 길이 1일 때와 2일 때를 미리 저장
		dp[1] = 1;
		dp[2] = 3;
		// 길이 3부터 계산
		for (int i=3; i<=N; i++) {
			dp[i] = (dp[i-2]*2 + dp[i-1]);
			dp[i] = dp[i]%10007;
		}
		
		System.out.println(dp[N]);
	}
}

다익스트라 알고리즘

 

  • 한 정점에서 다른 모든 정점까지의 최단 경로를 구하는 알고리즘

  • 겨올탐색, 네트워크 라우팅 등 분야에서 활용될 수 있음

  • 그리디 기법

 

 

 

 

동작 원리

1. 출발 정점에서 시작. 인접 정점 중 가장 짧은 경로를 갖는 정점을 선택

 

2. 선택 정점에서 다른 인접 정점까지의 거리를 계산하고, 이전에 계산된 거리와 비교해 더 짧은 경로가 발견되면 해당 경로로 업데이트.

 

3. 같은 방법으로 최단 경로를 찾을 때까지 진행

 

 

 

 

 

장점

  • 탐색속도: 모든 간선이 음수가 아닐 때 빠른 탐색 속도로 경로 탐색 가능

  • 우선순위 큐를 활용해 시간 복잡도 절감 가능

 

단점

  • 음수 가중치가 있는 그래프에서는 작동 X

  • 배열을 활용한 방법의 경우 간선이 많을수록 성능 저하될 수 있음

 

 

 

구현

1.방문체크배열(visited), 인접배열(adjArr), 시작점으로부터의 거리(dist)를 저장할 배열이 필요

 

2. dist배열을 모두 무한대로 설정하고, 탐색 시작할 정점만 0으로 변경

 

3. 현재 방문하지 않은 정점 중 가장 짧은 거리를 가진 정점을 선택

 

4. 인접배열을 통해 탐색하고자 하는 정점과 인접한 정점들의 거리를 더 짧은 거리로 업데이트

 

 

 

 

 

<배열을 활용한 방법>

 

import java.util.Arrays;

public class DijkstraArray {

    // 정점의 개수 V, 무한대 값으로 사용할 상수
    static final int V = 5;
    static final int INF = Integer.MAX_VALUE;

    // 다익스트라 알고리즘을 배열 기반으로 구현
    public static void dijkstra(int[][] adjMatrix, int start) {
        // 각 정점까지의 최소 거리를 저장하는 배열
        int[] dist = new int[V];
        // 방문 여부를 저장하는 배열
        boolean[] visited = new boolean[V];

        // 초기화: 모든 거리를 무한대로 설정하고, 시작 정점의 거리를 0으로 설정
        Arrays.fill(dist, INF);
        dist[start] = 0;

        // 모든 정점을 하나씩 처리
        for (int i = 0; i < V - 1; i++) {
            // 방문하지 않은 정점 중 가장 거리가 짧은 정점을 선택
            int u = selectMinVertex(dist, visited);

            // 선택된 정점을 방문 처리
            visited[u] = true;

            // 인접한 정점들의 거리를 업데이트
            for (int v = 0; v < V; v++) {
                if (!visited[v] && adjMatrix[u][v] != 0 && dist[u] != INF && dist[u] + adjMatrix[u][v] < dist[v]) {
                    dist[v] = dist[u] + adjMatrix[u][v];
                }
            }
        }

        // 최종 최단 경로 출력
        printSolution(dist);
    }

    // 방문하지 않은 정점 중 최소 거리를 가진 정점을 선택
    public static int selectMinVertex(int[] dist, boolean[] visited) {
        int min = INF, minIndex = -1;

        for (int v = 0; v < V; v++) {
            if (!visited[v] && dist[v] < min) {
                min = dist[v];
                minIndex = v;
            }
        }
        return minIndex;
    }

    // 최단 경로를 출력하는 메서드
    public static void printSolution(int[] dist) {
        System.out.println("정점별 최단 거리:");
        for (int i = 0; i < V; i++) {
            System.out.println(i + " -> " + dist[i]);
        }
    }

    public static void main(String[] args) {
        // 그래프의 인접 행렬 표현
        int[][] adjMatrix = {
            { 0, 10, 0, 30, 100 },
            { 10, 0, 50, 0, 0 },
            { 0, 50, 0, 20, 10 },
            { 30, 0, 20, 0, 60 },
            { 100, 0, 10, 60, 0 }
        };

        // 다익스트라 알고리즘 실행 (시작 정점: 0)
        dijkstra(adjMatrix, 0);
    }
}

 

 

<우선순위큐를 활용한 방법>

import java.util.*;

public class DijkstraPriorityQueue {

    static final int V = 5;
    static final int INF = Integer.MAX_VALUE;

    // 간선 클래스 (정점과 가중치)
    static class Edge implements Comparable<Edge> {
        int vertex, weight;

        Edge(int vertex, int weight) {
            this.vertex = vertex;
            this.weight = weight;
        }

        // 가중치 기준으로 우선순위 큐에서 정렬
        public int compareTo(Edge other) {
            return this.weight - other.weight;
        }
    }

    // 다익스트라 알고리즘을 우선순위 큐로 구현
    public static void dijkstra(int[][] adjMatrix, int start) {
        // 최소 거리를 저장하는 배열
        int[] dist = new int[V];
        // 우선순위 큐
        PriorityQueue<Edge> pq = new PriorityQueue<>();

        // 초기화: 모든 거리를 무한대로 설정하고, 시작 정점의 거리를 0으로 설정
        Arrays.fill(dist, INF);
        dist[start] = 0;

        // 시작 정점을 우선순위 큐에 삽입
        pq.add(new Edge(start, 0));

        // 우선순위 큐가 빌 때까지 반복
        while (!pq.isEmpty()) {
            Edge current = pq.poll();  // 현재 정점
            int u = current.vertex;

            // 인접한 정점들의 거리 업데이트
            for (int v = 0; v < V; v++) {
                if (adjMatrix[u][v] != 0 && dist[u] != INF && dist[u] + adjMatrix[u][v] < dist[v]) {
                    dist[v] = dist[u] + adjMatrix[u][v];
                    pq.add(new Edge(v, dist[v]));
                }
            }
        }

        // 최종 최단 경로 출력
        printSolution(dist);
    }

    // 최단 경로를 출력하는 메서드
    public static void printSolution(int[] dist) {
        System.out.println("정점별 최단 거리:");
        for (int i = 0; i < V; i++) {
            System.out.println(i + " -> " + dist[i]);
        }
    }

    public static void main(String[] args) {
        // 그래프의 인접 행렬 표현
        int[][] adjMatrix = {
            { 0, 10, 0, 30, 100 },
            { 10, 0, 50, 0, 0 },
            { 0, 50, 0, 20, 10 },
            { 30, 0, 20, 0, 60 },
            { 100, 0, 10, 60, 0 }
        };

        // 다익