백준 4963번: 섬의 개수[JAVA]

https://www.acmicpc.net/problem/4963

 

4963번: 섬의 개수

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스의 첫째 줄에는 지도의 너비 w와 높이 h가 주어진다. w와 h는 50보다 작거나 같은 양의 정수이다. 둘째 줄부터 h개 줄에는 지도

www.acmicpc.net

 

[정답코드]

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

public class Main {
    static int w;
    static int h;
    static int[][] map;
    static int[] dx = {0, 0, 1, -1, -1, 1, -1, 1};
    static int[] dy = {1, -1, 0, 0, -1, 1, 1, -1};
    static boolean[][] visit;
    static int count; // 섬의 개수
    public static void main(String[] args) throws Exception {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        while (true) { // w와 h가 0이 되면 종료
            st = new StringTokenizer(br.readLine(), " ");

            // w 지도의 너비, h 지도의 높이
            w = Integer.parseInt(st.nextToken());
            h = Integer.parseInt(st.nextToken());

            // 0이 되면 종료
            if (w == 0 && h == 0) {
                break;
            }

            //지도 배열 선언
            map = new int[h][w];
            //방문한 곳 배열 선언
            visit = new boolean[h][w];
            //지도 입력
            for (int i = 0; i < h; i++) {
                st = new StringTokenizer(br.readLine(), " ");
                for (int j = 0; j < w; j++) {
                    map[i][j] = Integer.parseInt(st.nextToken());
                }
            }

            count = 0;


            for (int i = 0; i < h; i++) {
                for (int j = 0; j < w; j++) {
                    if (map[i][j] == 1 && !visit[i][j]) { // 섬이 있고 방문하지 않았다면 bfs 함수실행
                        bfs(i, j);
                        count++;
                    }
                }
            }
            System.out.println(count);
        }
    }

    public static void bfs(int x, int y) {
        visit[x][y] = true;

        //8방탐색
        for (int i = 0; i < 8; i++) {
            int nx = x + dx[i];
            int ny = y + dy[i];

            //지도범위 안에 있을 경우
            if (nx >= 0 && ny >= 0 && nx < h && ny < w) {
                if (map[nx][ny] == 1 && !visit[nx][ny]) { //8방탐색한곳이 방문하지않고, 섬이 존재할경우
                    bfs(nx, ny);
                }
            }
        }
    }
}

 

[설명]

지도를 설정한 후 지도의 시작지점부터 탐색을 시작한다. 섬이있는 자리 이면서 visit가 false 일경우 bfs 함수를 실행한다.

bfs 함수는 섬이있는 자리이면서 visit가 false 인 자리를 기준으로 8방 탐색을 실행한다. 8방 탐색을 실행할때 탐색한자리에 에 또 섬이있고 방문한적이 없는 자리면 그 자리를 기준으로 다시 bfs함수를 실행한다. 

 

REST API란?

 

REST : Representational State Transfer 의 약자로 '네트워크에서 통신을 구성할 때 이런 구조로 설계하라는 지침' 정도로 볼 수 있다.

 

API : ApplicationProgrammingInterface 의 약자로 응용 프로그램 프로그래밍 인터페이스를 뜻한다. 프로그램을 작성하기 위한 일련의 부프로그램, 프로토콜 등을 정의하여 상호 작용을 하기위한 인터페이스 사양을 말한다.

 

REST API : API는 주로 프로그램 내부 단에서 이루어진다. 하지만 보다 다양한 분야에 쓰일수 있도록 '네트워크'와 '웹'에 맞춰진 API 통신 아키텍쳐가 등장했한것이 REST API이다. 주로 웹 API 쪽에서 사용된다. 웹 API = REST API 라고 봐도 볼 수있다. 현실적으로 네트워크의 99.99%는 인터넷이라부르는 HTTP 기반 네트워크 이므로 REST API라고 하면 HTTP에 쓰이는걸 의미하는 경우가 많다. 그냥 API 라고 말하면 REST API를 의미하는 경우도 많아졌다. 

 

RESTful API : REST를 잘 준수하는 API를 말한다. REST하지 않아도 RESTful API 라고 부르는 경우가 종종 있다. 엄밀히 따지면 REST 요소를 지키지 않은 HTTP API는 그냥 웹 API 혹은 HTTP API 라고 불러야한다.

 

 

[REST]

 

  • 자원(Resource) : HTTP URI (URI : Uniform Resource Identifier : 자원을 식별하기 위한 문자열의 구성)                                                                      ( ex : https://example.com/user/ABC    ' / ' 는 계층관계를 의미)
  • 행위(Verb) : HTTP Method (GET, POST, PUT, DELETE 등)
  • 표현(Representations) : HTTP Message, JSON, XML, RSS 등

 

 

REST의 특징

  • Client-Server(클라이언트-서버 구조) : 클라이언트와 서버로 분리되어야하며 서로 의존성이 없어야 한다.
  • = REST Server 는 API를 제공하고 비지니스 로직 처리 및 저장을 책임짐. Client는 사용자 인증이나 context(세션, 로그인 정보)등을 관리하고 책임짐

 

  • Stateless(무상태성) : 상태 정보를 따로 저장하지 않으며, 이용자가 누구인지 혹은 어디서 접근하는지와 관계 없이 결과가 무조건 동일해야한다. 따라서 REST API는 필연적으로 오픈될 수 밖에 없다.
  • = Client의 context를 Server에 저장하지 않음,  Server는 모든 요청을 완전히 별개의 요청으로 처리함

 

  • Cache(캐시 처리 기능) : HTTP를 비롯한 네트워크 프로토콜에서 제공하는 캐싱 기능을 적용할 수 있어야 한다.
  • = 대량의 요청을 효율적으로 처리 가능

 

  • Uniform Interface(인터페이스 일관성) : 데이터가 표준 형식으로 전송될 수 있도록 구성 요소 간 통합 인터페이스를 사용한다. REST API 태반이 HTTP를 사용하기 때문에 HTTP 표준인 URL과 응답코드, Request-Response Method 등을 사용한다.
  • = 특정 언어나 기술에 종속되지 않음

 

  • Layered System(계층 구조) : API는 REST 조건을 만족하면 필연적으로 오픈될 수 밖에 없기 때문에, 요청된 정보를 검색하는데 있어 계층 구조로 분리되어야 한다.

 

  • Self-descriptiveness(자체 표현) : API를 통해 전송되는 내용은 별도 문서 없이 쉽게 이해할 수 있도록 자체 표현 구조를 지녀야 한다. 마찬가지로 웹 표준인 JSON과 XML이 주로 사용된다.

 

[REST API]

 

REST의 특징을 기반으로한 웹 API

구글, 네이버, 카카오등 IT 회사라면 각 회사의 서비스를 활용할 수 있는 REST API를 지원한다.

 

[REST API의 특징]

각 요청이 어떤 동작이나 정보를 위한 것인지 요청 그 자체로 추론이 가능

 

[REST API의 작동 방식]

HTTP 요청을 통해 통신함으로써 리소스 내에서 레코드의 CRUD등을 수행함. (ex : GET 요청을 사용해 레코드를 검색, POST 요청으로 레코드 작성 등)

 

1. 클라이언트가 서버에 요청함. 클라이언트가 API 문서에 따라 서버가 이해하는 방식으로 요청 형식을 지정.

2. 서버가 클라이언트를 인증. 해당 요청을 수행할 수 있는 권한이 있는지 확인.

3. 서버가 요청을 수신. 내부적으로 처리.

4. 서버가 클라이언트에 응답을 반환. 응답에는 요청 성공여부 정보, 요청한 모든 정보 등이 포함됨.

 

[REST API 디자인 가이드]

1. URI는 정보의 자원을 표현해야 한다

2. 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표한한다.

 

[RESTful API]

RESTful 한 API는 아래 사진처럼 딱 봐도 어떠한 요청인지 이해할 수 있다.

 

 

 

벨만 포드 알고리즘(Bellman Ford Alogorithm)

벨만 포드 알고리즘은 다익스트라 알고리즘과 마찬가지로 최단 경로 알고리즘이다. 탐색을 하는 방식도 다익스트라와 유사하다. 다익스트라 알고리즘과의 차이점은

벨만 포드 알고리즘 다익스트라 알고리즘
음의 가중치가 간선에 있을때도 작동하며 음의 사이클도 감지함 음의 가중치가 간선에 있을경우 작동하지 않음
상대적으로 시간이 소요됨 벨만 포드의 알고리즘보다 시간이 소요됨
시간복잡도는 O(VE) 시간복잡도는 O(ElogV)

 

[작동방식]

1. 출발 노드를 설정한다

2. 최단 거리 배열을 초기화한다.

3. 인접한 노드로 가는 간선을  탐색후 최단거리 배열을 갱신한다.

4. 3번과정을 (V-1)번 반복한다.

5. 음수 사이클의 존재 유무를 판별하려면 한 번 더 실행한다. 이때 최단거리 배열이 갱신된다면 음수 사이클이 존재하는것이다.

 

[예시]

https://www.acmicpc.net/problem/11657

 

11657번: 타임머신

첫째 줄에 도시의 개수 N (1 ≤ N ≤ 500), 버스 노선의 개수 M (1 ≤ M ≤ 6,000)이 주어진다. 둘째 줄부터 M개의 줄에는 버스 노선의 정보 A, B, C (1 ≤ A, B ≤ N, -10,000 ≤ C ≤ 10,000)가 주어진다. 

www.acmicpc.net

문제 : 1번노드에서 각 노드(2번, 3번 노드)로 갈 수 있는 최단거리를 표시하고 음의 사이클이 존재하거나 갈 수 없다면 -1을 출력한다.

 

최단거리를 기록할 dist 배열 선언 및 초기화.

1 2 3
INF INF INF

노드가 3개이기 때문에 탐색하는 작업을 (V-1)번인 2번반복 후 마지막 음수 사이클 존재확인을 위해 1번 더 한다.

총 3번 반복한다.

 

1번째 반복

 

1번 노드기준 탐색

 

1번 노드 -> 2번 노드 = 1번 노드 가중치(0) + 간선의 가중치(4) = 4  <  INF

1번 노드 -> 3번 노드 = 1번 노드 가중치(0) + 간선의 가중치(3) = 3  <  INF

1 2 3
0 4 3

 

2번 노드 기준 탐색

 

2번 노드 -> 3번 노드 = 2번노드 가중치(4) + 간선의 가중치(-4) = 0  < 3번 노드의 가중치(3) 

3번 노드의 가중치를 더 적은 0으로 변경

1 2 3
0 4 0

 

3번 노드 기준  탐색

 

3번 노드 -> 1번 노드 =  3번의 가중치(0) + 간선의 가중치(-2) = -2  <  1번 노드의 가중치(0)

더 적은 가중치인 -2로 갱신

1 2 3
-2 4 0

 

이 과정을 2번 더 반복해주면 된다.

 

2번째 반복

1 2 3
-2 4 -4

 

3번째 반복

V번째 반복에서 V-1번째와의 결과와 다르다면 음의 사이클이 존재하다는 뜻이다.

1 2 3
-4 4 -8

2번 노드의 값은 변하지 않았지만 1, 3번노드는 음의 사이클이 존재하기 때문에 답 = -1

 

[전체 코드]

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

public class Main {

    static class Edge {
        int start, end, cost; // 시작 노드, 도착 노드, 가중치

        public Edge(int start, int end, int cost) {
            this.start = start;
            this.end = end;
            this.cost = cost;
        }
    }

    static int v, e;
    static List<Edge> edges;
    static long[] dist;

    public static void main(String[] args) throws Exception {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");
        StringBuilder sb = new StringBuilder();

        v = Integer.parseInt(st.nextToken()); // 노드의 수
        e = Integer.parseInt(st.nextToken()); // 간선의 수

        // 리스트 선언
        edges = new ArrayList<>();

        // 가중치를 담을 배열
        dist = new long[v + 1];
        // dist 배열의 전체내용을 Long.MAX_VALUE 로 초기화
        Arrays.fill(dist, Long.MAX_VALUE);

        // edges 초기화
        for (int i = 0; i < e; i++) {
            st = new StringTokenizer(br.readLine());
            int start = Integer.parseInt(st.nextToken());
            int end = Integer.parseInt(st.nextToken());
            int cost = Integer.parseInt(st.nextToken());
            edges.add(new Edge(start, end, cost));
        }

        // 출력
        if (BellmanFord()) {
            // i = 2인 이유, 문제에서 0번노드부터가아닌 1번노드부터 존재
            // 1번노드 제외한 나머지 노드를 출력이기 때문에
            for (int i = 2; i <= v; i++) {
                // 도달하지 못하는 장소일 경우 -1
                if (dist[i] == Long.MAX_VALUE) {
                    sb.append(-1).append("\n");
                    continue;
                }
                // 도달가능할 경우 최소거리 출력
                sb.append(dist[i]).append("\n");
            }
        } else { // 음의 사이클이 존재할 경우 -1 출력
            sb.append(-1);
        }


        bw.write(sb.toString());

        bw.flush();
        bw.close();
        br.close();
    }

    public static boolean BellmanFord() {
        dist[1] = 0;

        // n-1번의 반복작업과 마지막(n번째 탐색) 확인작업을 한번에 실행
        for (int i = 1; i <= v; i++) {
            for (Edge edge : edges) {
                // 한번도 방문하지 않았다면 패스
                if (dist[edge.start] == Long.MAX_VALUE) {
                    continue;
                }
                // 버스 도착점까지의 최소거리가 (시작점 + 비용) 보다 크면 갱신
                if (dist[edge.end] > dist[edge.start] + edge.cost) {
                    dist[edge.end] = dist[edge.start] + edge.cost;

                    // n번째 작업에서 값이 변경되면 Negative Cycle(음의 사이클)이 존재한다는 뜻
                    if (i == v) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

 

다익스트라 알고리즘(Dijkstra's algorithm) - 우선순위 큐

 

우선순위 큐 구현에는 전 게시물에서 사용한 인정 행렬 구현을 수정해 사용한다.

https://stdio-han.tistory.com/85

 

다익스트라 알고리즘(Dijkstra's algorithm) - 인접 행렬

다익스트라 알고리즘(Dijkstra's algorithm) 다익스트라 혹은 데이크스트라 알고리즘 이라고부르는 이것은 도로 교통망 같은 곳에서 나타날 수 있는 그래프에서 꼭짓점 간의 최단경로를 찾는 알고리

stdio-han.tistory.com

 

[설명]

이전에 사용한 인접행렬 방식과 다른 부분을 위주로 설명한다.

 

 

// (a,b) -> a[0] - b[0] 오름차순 정렬
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);

먼저 우선순위 큐를 선언해준다.

 

 

  // 시작노드값 초기화
        Dist[v] = 0;
        Check[v] = true;
        pq.add(new int[]{v, 0});

시작 노드값 초기화시 우선순위 큐 pq에 시작노드와 가중치를 선언해준다.

 

 

        // 인접한 노드 Dist 갱신
        for (int i = 0; i < N; i++) {
            if (!Check[i] && Graph[v][i] != INF) { // 방문하지 않은 노드 && INF 가 아닌경우
                Dist[i] = Graph[v][i]; //값 갱신
                pq.add(new int[]{Graph[v][i], i});
            }
        }

인접한 노드에 대한 정보도 큐에 입력해준다.

 

 

while (!pq.isEmpty()) {
            // 노드 최소값 찾기
            int[] curr = pq.poll();
            int min_index = curr[1];

            // 다른 노드를 거쳐 가는 것이 더 비용이 적은지 확인
            Check[min_index] = true; // 방문한 노드 true
            for (int j = 0; j < N; j++) {
                if (!Check[j] && Graph[min_index][j] != INF) {
                    if (Dist[min_index] + Graph[min_index][j] < Dist[j]) {
                        Dist[j] = Dist[min_index] + Graph[min_index][j];
                        pq.add(new int[]{Dist[j], j});
                    }
                }
            }

            // 결과값 출력
            for (int j = 0; j < N; j++) {
                if(Dist[j] == INF) System.out.print("INF" + "\t");
                else System.out.print(Dist[j]+"\t");
            }
            System.out.println("");

        }

최소 노드값을 찾을 때 우선순위 큐에서 꺼낸다.

min_index = curr[1] 로 선언해준다. 이때 curr[0] 은 노드의 가중치, curr[1] 은 노드번호이다.

 

 

[코드]

 

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

public class Main {

    static int INF = Integer.MAX_VALUE;
    static int N, E; // 노드의 개수, 간선의 개수
    static int[][] Graph; // 노드들간의 가중치를 저장할 변수
    static int[] Dist; // 최단 거리를 저장할 변수
    static boolean[] Check; // 해당 노드를 방문했는지 체크할 변수
    public static void main(String[] args) throws Exception {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        N = Integer.parseInt(st.nextToken()); // 노드의 개수
        E = Integer.parseInt(st.nextToken()); // 간선의 개수
        Graph = new int[N][N];
        Dist = new int[N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                // 인접행렬 모든 값 INF 로 초기화
                Graph[i][j] = INF;
            }
        }

        for (int i = 0; i < E; i++) {
            st = new StringTokenizer(br.readLine(), " ");
            int u = Integer.parseInt(st.nextToken());
            int v = Integer.parseInt(st.nextToken());
            int cost = Integer.parseInt(st.nextToken());
            Graph[u][v] = Graph[v][u] = cost; // 양방향 간선
        }

        dijkstra(0);

        bw.flush();
        bw.close();
        br.close();
    }

    public static void dijkstra(int v) {
        // (a,b) -> a[0] - b[0] 오름차순 정렬
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        Dist = new int[N];
        Check = new boolean[N];

        // Dist 값 초기화
        for (int i = 0; i < N; i++) {
            Dist[i] = INF;
        }

        // 시작노드값 초기화
        Dist[v] = 0;
        Check[v] = true;
        pq.add(new int[]{v, 0});

        // 결과값 출력
        for (int i = 0; i < N; i++) {
            if(Dist[i] == INF) System.out.print("INF" + "\t");
            else System.out.print(Dist[i]+"\t");
        }
        System.out.println("");

        // 인접한 노드 Dist 갱신
        for (int i = 0; i < N; i++) {
            if (!Check[i] && Graph[v][i] != INF) { // 방문하지 않은 노드 && INF 가 아닌경우
                Dist[i] = Graph[v][i]; //값 갱신
                pq.add(new int[]{Graph[v][i], i});
            }
        }

        // 결과값 출력
        for (int i = 0; i < N; i++) {
            if(Dist[i] == INF) System.out.print("INF" + "\t");
            else System.out.print(Dist[i]+"\t");
        }
        System.out.println("");

        while (!pq.isEmpty()) {
            // 노드 최소값 찾기
            int[] curr = pq.poll();
            int min_index = curr[1];

            // 다른 노드를 거쳐 가는 것이 더 비용이 적은지 확인
            Check[min_index] = true; // 방문한 노드 true
            for (int j = 0; j < N; j++) {
                if (!Check[j] && Graph[min_index][j] != INF) {
                    if (Dist[min_index] + Graph[min_index][j] < Dist[j]) {
                        Dist[j] = Dist[min_index] + Graph[min_index][j];
                        pq.add(new int[]{Dist[j], j});
                    }
                }
            }

            // 결과값 출력
            for (int j = 0; j < N; j++) {
                if(Dist[j] == INF) System.out.print("INF" + "\t");
                else System.out.print(Dist[j]+"\t");
            }
            System.out.println("");

        }
    }
}

 

[결과]

 

[정리]

입력값이 커져 그래프의 크기가 커지면 커질수록 우선순위 큐가 인접행렬 방식을 사용했을때보다 시간상 이점이 발생한다.

 

다익스트라 알고리즘(Dijkstra's algorithm)

 

 

다익스트라 혹은 데이크스트라 알고리즘 이라고부르는 이것은 도로 교통망 같은 곳에서 나타날 수 있는 그래프에서 꼭짓점 간의 최단경로를 찾는 알고리즘이다.

일반적인 다익스트라 알고리즘문제로는 한 꼭짓점을 "소스" 꼭짓점으로 고정하고 그래프의 다른 모든 꼭짓점까지의 최단경로를 찾는 알고리즘으로 최단 경로 트리를 만드는것이다. 그래프의 간선마다 가중치가 존재하고, 음의 가중치는 존재하지 않는다. 음의 가중치가 존재한다면 다익스트라를 사용할 수 없다.

구현하는 방법에는 인접행렬방식과 우선순위 큐 두가지가 있다. 먼저 다익스트라를 이해하기 위해 인정행렬이 더 간단하기 때문에 먼저 알아보자.

 

동작 과정을 간단하게 설명하자면 다음과 같다.

1. 출발 노드를 설정한다.

2. 출발노드에서 이웃한 노드중 거리가 가장 짧은순으로 선택해 가중치를  각 노드에 기록한다.

3. 기록한 노드중 거리가 가장 짧은 노드를 선택후 다시 방문하지 않은 이웃 노드를 탐색한다.

4. 재탐색하는 노드에는 기록된 가중치 + 간선 가중치와 기록되어있는 가중치중 작은 것을 기록한다.

5. 위 과정을 반복한다.

 

[예시]

 

먼저 위 그림을 예로 이차원 배열을 활용해 구현해보자. 

 

가중치를 가중치 인접 행렬(2차원 배열)에 저장한다. 연결되지 않은 노드는 INF를 기록한다.

  1 2 3 4 5 6
1 0 7 9 INF INF 14
2 7 0 10 15 INF INF
3 9 10 0 11 INF 2
4 INF 15 11 0 6 INF
5 INF INF INF 6 0 9
6 14 INF 2 INF 9 0

 

시작 노드인 1번 노드기준의 각 노드의 가중치를 기록한 배열이다.

1 2 3 4 5 6
0 7 9 INF INF 14

인정합 노드인 2, 3, 6번 노드순으로 탐색해 나가면 값을 수정해보자. 먼저 가장 짧은 노드인 2번노드를 먼저 탐색해보자

 

2번노드

 

1 2 3 4 5 6
0 7 9 < 17 -> 9 INF -> 22 INF 14

2번의 인접노드중 방문하지 않은 노드인 3번 과 4번 노드를 보자

먼저 3번 노드를 보면 2번노드의 가중치(7) + 간선의 가중치(10) = 17

3번 노드의 가중치 = 9

최솟값을 찾는 것이기 때문에 3번 노드의 가중치는 그대로 9이다.

4번 노드는 INF 였지만 2번노드의 가중치(7) +간선의 가중치(15) = 22가 되어 4번 노드의 가중치는 22가 기록된다.

탐색이 끝났다면 1번의 인접노드 2, 3, 6 중 그 다음 노드인 3번 노드로 넘어간다.

 

3번 노드

 

1 2 3 4 5 6
0 7 9 22 -> 20 INF 14 -> 11

3번노드의 인접노드중 방문 하지 않은 노드 4번 노드, 6번 노드를 보자

3번 노드의 가중치(9) + 간선의 가중치(11) = 20

3번 노드의 가중치(9) + 간선의 가중치(2) = 11

그다음 노드 6번 노드를 탐색해보자

 

6번 노드

 

1 2 3 4 5 6
0 7 9 20 INF -> 20 11

 

6번노드의 인접노드중 방문하지 않은 노드인 4번 노드를 보자

6번 노드의 가중치(11) + 간선의 가중치(9) = 20

원래 5번의 가중치는 INF 였기때문에 더 짧은 가중치인 20으로 수정된다.

마지막 방문하지 않은 노드인 4번 노드를 확인해보자

 

4번 노드

 

 

1 2 3 4 5 6
0 7 9 20 20 11

4번노드에서 방문하지 않은 인접노드 5번노드까지를 보면

4번 노드의 가중치(20) + 간선의 가중치(6) = 26 이므로

값을 수정하지 않고 원래의 가중치인 20으로 남겨둔다.

5번 노드는 인접한 모든 노드가 방문을 완료한 노드기 때문이 탐색을 종료한다.

 

최종적인 가중치 기록 배열을 보자

1 2 3 4 5 6
0 7 9 20 20 11

 

1번 노드 -> 1번 노드 : 0

1번 노드 -> 2번 노드 : 7

1번 노드 -> 3번 노드 : 9

1번 노드 -> 4번 노드 : 20

1번 노드 -> 5번 노드 : 20

1번 노드 -> 6번 노드 : 11

 

[시간복잡도]

기본적인 다익스트라의 시간복잡도는 

V : 노드의 숫자, E : 간선의 숫자 라고 볼때 O(V^2) 이다.

하지만 우선순위 큐등을 사용해 이를 좀더 개선하면 O((V + E)logV) 가 나온다.

 

이 인접행렬방식을 자바 코드로 구현하자면 다음과 같다.

 

[코드]

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

public class Main {

    static int INF = Integer.MAX_VALUE;
    static int N, E; // 노드의 개수, 간선의 개수
    static int[][] Graph; // 노드들간의 가중치를 저장할 변수
    static int[] Dist; // 최단 거리를 저장할 변수
    static boolean[] Check; // 해당 노드를 방문했는지 체크할 변수
    public static void main(String[] args) throws Exception {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        N = Integer.parseInt(st.nextToken()); // 노드의 개수
        E = Integer.parseInt(st.nextToken()); // 간선의 개수
        Graph = new int[N][N];
        Dist = new int[N];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                // 인접행렬 모든 값 INF 로 초기화
                Graph[i][j] = INF;
            }
        }

        for (int i = 0; i < E; i++) {
            st = new StringTokenizer(br.readLine(), " ");
            int u = Integer.parseInt(st.nextToken());
            int v = Integer.parseInt(st.nextToken());
            int cost = Integer.parseInt(st.nextToken());
            Graph[u][v] = Graph[v][u] = cost; // 양방향 간선
        }

        dijkstra(0);

        bw.flush();
        bw.close();
        br.close();
    }

    public static void dijkstra(int v) {
        Dist = new int[N];
        Check = new boolean[N];

        // Dist 값 초기화
        for (int i = 0; i < N; i++) {
            Dist[i] = INF;
        }

        // 시작노드값 초기화
        Dist[v] = 0;
        Check[v] = true;

        // 결과값 출력
        for (int i = 0; i < N; i++) {
            if(Dist[i] == INF) System.out.print("INF" + "\t");
            else System.out.print(Dist[i]+"\t");
        }
        System.out.println("");

        // 인접한 노드 Dist 갱신
        for (int i = 0; i < N; i++) {
            if (!Check[i] && Graph[v][i] != INF) { // 방문하지 않은 노드 && INF가 아닌경우
                Dist[i] = Graph[v][i]; //값 갱신
            }
        }

        // 결과값 출력
        for (int i = 0; i < N; i++) {
            if(Dist[i] == INF) System.out.print("INF" + "\t");
            else System.out.print(Dist[i]+"\t");
        }
        System.out.println("");

        for (int i = 0; i < N - 1; i++) {
            // 모든 노드가 true가 될때까지 해야하지만
            // 노드가 N개라면 다익스트라를 위해 반복수는 N-1 번만 하면 됨.
            int min = Integer.MAX_VALUE;
            int min_index = -1;

            // 노드 최소값 찾기
            for (int j = 0; j < N; j++) {
                if (!Check[j]) { //방문하지 않은 노드
                    if (Dist[j] < min) { // 방문하지 않은 노드중 가중치가 가장 작은 노드를 찾는다.
                        min = Dist[j];
                        min_index = j; // 가중치가 가장 작은 노드의 번호를 저장
                    }
                }
            }

            // 다른 노드를 거쳐 가는 것이 더 비용이 적은지 확인
            Check[min_index] = true; // 방문한 노드 true
            for (int j = 0; j < N; j++) {
                if (!Check[j] && Graph[min_index][j] != INF) {
                    if (Dist[min_index] + Graph[min_index][j] < Dist[j]) {
                        Dist[j] = Dist[min_index] + Graph[min_index][j];
                    }
                }
            }

            // 결과값 출력
            for (int j = 0; j < N; j++) {
                if(Dist[j] == INF) System.out.print("INF" + "\t");
                else System.out.print(Dist[j]+"\t");
            }
            System.out.println("");
        }
    }
}

[결과]

첫째줄에 노드와 간선의 수를 입력받고 그다음줄 부터 시작노드, 도착노드, 가중치 를 입력해준다.

편리하게 구현하기 위해 문제에서는 가장 작은 노드가 1번노드지만 구현할때는 0번노드로했다.

결과 값을 보면 예시에서 보여준 가중치 배열과 같은 결과값을 보여준다.

 

백준 14888번: 연산자 끼워넣기[JAVA]

https://www.acmicpc.net/problem/14888

 

14888번: 연산자 끼워넣기

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 곱

www.acmicpc.net

 

[정답 코드]

import java.io.*;
import java.math.BigInteger;
import java.util.*;

public class Main {
    static int MAX = Integer.MIN_VALUE;
    static int MIN = Integer.MAX_VALUE;
    static int N; // 숫자 개수
    static int[] arr; // 숫자 배열
    static int[] op = new int[4]; // 연산자 배열
    public static void main(String[] args) throws Exception {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        // 숫자 갯수 입력
        N = Integer.parseInt(st.nextToken());

        // 숫자 배열 입력
        arr = new int[N];
        st = new StringTokenizer(br.readLine(), " ");
        for (int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        // 연산자 갯수 입력
        st = new StringTokenizer(br.readLine(), " ");
        for (int i = 0; i < 4; i++) {
            op[i] = Integer.parseInt(st.nextToken());
        }

        backTracking(arr[0], 1);

        //최대값 최소값 출력
        bw.write(MAX + "\n" + MIN);

        bw.flush();
        bw.close();
        br.close();
    }

    // 로직
    public static void backTracking(int num, int idx) {

        // idx == N 이 되면 종료, arr 끝까지 탐색했다는 의미
        if (idx == N) {
            MAX = Math.max(MAX, num);
            MIN = Math.min(MIN, num);
            return;
        }

        for (int i = 0; i < 4; i++) {
            // 해당 연산자가 존재하면, 해당연산자 사용한 후 개수 하나 제거
            if (op[i] > 0) {
                op[i]--;
                switch (i) {
                    case 0:	backTracking(num + arr[idx], idx + 1);	break;
                    case 1:	backTracking(num - arr[idx], idx + 1);	break;
                    case 2:	backTracking(num * arr[idx], idx + 1);	break;
                    case 3:	backTracking(num / arr[idx], idx + 1);	break;
                }
                // 재귀호출이 종료되면 다시 해당 연산자 개수를 복구한다.
                op[i]++;
            }
        }
    }
}

 

[정리]

 

그림의 예가 약간 잘못된것같고 이상하지만 참고하고 보자. 그림을 예로 들어보자면 입력값이 첫째 줄에 3, 둘째 줄에 2 3 4 셋째 줄에 1 1 1 1을 입력받았을 경우의 대략적인 그림이다. 첫번째 숫자부터시작해 2 + 3 - 4 순서대로 계산을 하고 그 다음은 2 + 3 * 4, 그 다음은 2 + 3 / 4 순서대로 진행하게 된다. 숫자가 더 많거나 연산자가 더 많을 경우에도 그림과 같이 진행된다고 보면 된다.

 

[느낀점]

아직 하노이 알고리즘도 그렇고, dfs, bfs 등 어려운 알고리즘은 혼자만의 생각으로는 아직 풀 수 없는것같다. 다른 글을 참고해가며 정말 어려울 경우에는 클론코딩을 해가며 풀게된다. 중요한 점은 그냥 참고하고 끝나는게 아니라 문제를 풀 때 만큼은 그림으로 그려가며 완벽하게 이해하는게 중요한것 같다.

백준 1914번: 하노이 탑[JAVA]

https://www.acmicpc.net/problem/1914

 

1914번: 하노이 탑

세 개의 장대가 있고 첫 번째 장대에는 반경이 서로 다른 n개의 원판이 쌓여 있다. 각 원판은 반경이 큰 순서대로 쌓여있다. 이제 수도승들이 다음 규칙에 따라 첫 번째 장대에서 세 번째 장대로

www.acmicpc.net

[정답 코드]

import java.io.*;
import java.math.BigInteger;
import java.util.*;

public class Main {
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static int N = 0;

    public static void main(String[] args) throws Exception {

        // 원판의 개수 입력
        N = Integer.parseInt(br.readLine());

        //관계식
        System.out.println(BigInteger.TWO.pow(N).subtract(BigInteger.ONE));

        // N이 20 이하일 경우 과정 출력
        if (N <= 20) {
            hanoi(N, 1, 2, 3);
        }

        bw.flush();
        bw.close();
        br.close();
    }

    // [로직] n 옮길 원판, from 시작 기둥, tmp 거쳐갈 기둥, to 도착 기둥
    public static void hanoi(int n, int from, int tmp, int to) throws IOException{

        // n == 1 일경우 바로 이동
        if (n == 1) {
            bw.write(from + " " + to + "\n"); // 이동
            return;
        }

        hanoi(n - 1, from, to, tmp);
        bw.write(from + " " + to + "\n"); // 이동

        hanoi(n-1, tmp, from, to);
    }
}

 

[시행착오]

 

그림판으로 그림까지 그려가며 하노이 탑을 공부하고 이해하려 노력했다. 코드는 다른글을 참고했지만 이해하기는했다.

처음엔 메모리초과가 발생했다. 알아본 결과 수가 너무 커질 경우 메모리 초과가 발생해 count 를 int 에서 long 으로 변경했다. 그런데 이번엔 시간초과가 발생했다. long 도 문제였다. 한 번 움직일 때마다 count 를 1씩 늘려주는 방식은 N이 매우 큰수가 될때 시간을 많이 잡아먹어 효율이 안좋았다. 애초에 시작할때 움직일만큼의 방정식을 계산해 출력을 해주는것이좋다. 그리고 그때의 타입은 BigInteger가 되어야만 했다. int, long 둘다 N이 100일 경우 메모리를 초과하기 때문이다.

N 이 20이상일 경우에는 hanoi() 자체가 실행되는순간 시간과 메모리를 많이 차지하기때문에 20 이하일 경우에만 hanoi()를 실행하게했다.

 

[정리]

원판의 개수 N을 입력받는다.

그 N을 이용해 관계식을 도출해 원판이 움직이는 횟수를 출력한다.

N이 20 이하일 경우 hanoi()를 실행한다.

 

[하노이 알고리즘]

 

그림과 hanoi() 를 같이 보면 이해하기 쉽다.

 

N개의 원판이 1번기둥에 있다. 제일 아래 큰 원판이 N 번째 원판이다.

hanoi(N, from, tmp, to) 실행

 

1단계 : N번째 원판을 옮기기 위해서는 N번째 원판을 제외한 N-1 번째부터 1번째 원판까지를 2번 기둥으로 옮긴다.

hanoi(N-1, from, to, tmp)

 

2단계 : N번째 원판을 3번 기둥으로 옮긴다.

이동

 

3단계 : N-1 번째부터 1번째 원판을 3번기둥으로 옮긴다.

hanoi(N-1, tmp, from, to)

 

이렇게 간단하게 3단계로 구성되어있다. 직접 알고리즘을 짜려면 어렵지만 막상 알고나면 생각보다 어렵지 않다.

백준 17609번: 회문[JAVA]

https://www.acmicpc.net/problem/17609

 

17609번: 회문

각 문자열이 회문인지, 유사 회문인지, 둘 모두 해당되지 않는지를 판단하여 회문이면 0, 유사 회문이면 1, 둘 모두 아니면 2를 순서대로 한 줄에 하나씩 출력한다.

www.acmicpc.net

 

[정답 코드]

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

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");

        // 문자열의 개수를 나타내는 정수 T
        int T = Integer.parseInt(st.nextToken());
        // 문자열을담는 배열
        String[] arr = new String[T];
        String str = "";

        //문자열 입력받은 후 배열에 저장
        for (int i = 0; i < T; i++) {
            str = br.readLine();
            System.out.println(palindrome(0, str.length()- 1, str, 0));
        }
    }

    // 로직
    private static int palindrome(int start, int end, String s, int check) {
        // 문자 삭제를 2번이상 할 경우 바로 2를 반환
        if (check >= 2) {
            return 2;
        }

        // start 포인터와 end 포인터가 만나거나 지나치기 전까지 반복
        while (start < end) {
						// 같을 경우 포인터 한칸 진행
            if (s.charAt(start) == s.charAt(end)) {
                start++;
                end--;
            } else {
                // ex) abbab 같은 경우 두 경우를 비교해 더 작은 수를 반환
                return Math.min(palindrome(start + 1, end, s, check + 1), palindrome(start, end - 1, s, check + 1));
            }
        }
        return check;
    }
}

 

[정리]

투 포인터 알고리즘과 재귀함수를 활용해 풀었다. 처음엔 재귀함수 없이 while 문과 for 문으로 해결하려고 했지만 반례를 발견할 때 마다 코드가 한줄 한줄 추가되더니 스파게티 코드가 되었다. 몇시간 동안 수정 후 반례 abbab 를 만났을 때 왼쪽 문자를 제거했을 경우와 오른쪽 문자를 제거했을 경우 두 가지 모두를 생각해야 한다는 걸 깨닫고 수정하려 했지만 이미 코드는 난장판이 되어있었다. 그래서 싹 다 지우고 혼자 해결하는것을 포기하고 구글링을하며 반정도 클론코딩을 하며 해결했다. 4시간 정도 걸려서 풀었지만 막상 풀고나니 처음 시작을 제대로 했더라면 금방 풀수있는 문제였다.

+ Recent posts