접근

푸는데 정말 오래걸린 문제였다..ㅠㅠ 다른 블로그의 솔루션들과 아무리 비교해봐도 똑같은데 자꾸 메모리 초과가 나는 것이다. 그래서 혹시나하고 솔루션 코드를 돌려봤는데.. 똑같이 메모리 초과가 났다. 알고보니 불과 3일전(!)에 데이터가 추가되면서, 4142개 중 1475개의 기존 정답 코드가 메모리 초과가 되었더라😂

그래서 이 블로그의 솔루션 코드를 참고하여 통과할 수 있었다. 기존에는 2차원 배열에 해당 위치에 올 수 있는 거울의 최소 개수를 저장했었다. 그런데 통과한 코드에는 3차원 배열을 선언하여 방향까지 함께 고려해줘야 했다. 아마 추가된 데이터에서 같은 칸, 방향에 여러번 방문하는 경우가 있지 않을까 짐작해본다. 그런데 이상하게 내 코드에서는 현재 방향과 정반대의 경우 넘어가는 조건 Math.abs(now.dir - i) == 2 을 추가하면 틀리게 나온다.. 이유는 아직 모르겠지만 알게된다면 포스팅을 수정하러 오겠다. 

 

반례 모음은 이쪽

 

코드

메모리 초과 코드

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

public class BJ6087 {
    static class Pos implements Comparable<Pos> {
        int x, y, mirrorCnt, dir;

        public Pos(int x, int y, int mirrorCnt, int dir) {
            this.x = x;
            this.y = y;
            this.mirrorCnt = mirrorCnt; // [x][y]에 도달하기 위해 필요한 거울의 개수
            this.dir = dir;
        }

        @Override
        public int compareTo(Pos o) {
            return this.mirrorCnt - o.mirrorCnt;
        }
    }

    static int[] dx = {-1, 0, 1, 0};
    static int[] dy = {0, 1, 0, -1};
    static int INF = 100*100+1;
    static int w, h;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        // 입력값 저장
        w = Integer.parseInt(st.nextToken());
        h = Integer.parseInt(st.nextToken());

        char[][] map = new char[h][w];
        int[][] minCnt = new int[h][w];
        Pos from = null, to = null;

        for (int i = 0; i < h; i++){
            map[i] = br.readLine().toCharArray();

            for (int j = 0; j < w; j++) {
                if(map[i][j] == 'C'){ // 시작 및 도착 위치
                    if(from == null) from = new Pos(i, j, -1, -1);
                    else to = new Pos(i, j, -1, -1);
                }
            }
        }

        // 결과 출력
        System.out.println(bfs(from, to, map, minCnt));
    }

    private static int bfs(Pos from, Pos to, char[][] map, int[][] minCnt) {
        PriorityQueue<Pos> pq = new PriorityQueue<>();

        for (int i = 0; i < map.length; i++)
            Arrays.fill(minCnt[i], INF);

        // 시작지점 처리
        pq.add(from);
        minCnt[from.x][from.y] = 0;

        // bfs
        while(!pq.isEmpty()){
            Pos cur = pq.poll();

            // 도착지점 도달
            if(cur.x == to.x && cur.y == to.y)
                return cur.mirrorCnt;

            // 거울 개수가 갱신되지 않는 경우 넘어감
            if(minCnt[cur.x][cur.y] < cur.mirrorCnt) continue;

            for (int d = 0; d < 4; d++) {
                int nx = cur.x + dx[d];
                int ny = cur.y + dy[d];

                // 범위를 벗어나거나 벽이면 넘어감
                if(!inBound(nx, ny, w, h) || map[nx][ny] == '*') continue;

                Pos next = new Pos(nx, ny, cur.mirrorCnt, d);
                if(d != cur.dir) next.mirrorCnt++;

                // 거울 개수가 갱신되지 않는 경우 넘어감
                if(minCnt[nx][ny] < next.mirrorCnt) continue;

                minCnt[nx][ny] = next.mirrorCnt;
                pq.add(next);
            }
        }

        return -1;
    }

    private static boolean inBound(int x, int y, int w, int h) {
        return !(x < 0 || y < 0 || x >= h || y >= w);
    }
}

 

정답 코드

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

public class BJ6087 {
    static class Pos implements Comparable<Pos> {
        int x, y, mirrorCnt, dir;

        public Pos(int x, int y, int mirrorCnt, int dir) {
            this.x = x;
            this.y = y;
            this.mirrorCnt = mirrorCnt; // [x][y]에 도달하기 위해 필요한 거울의 개수
            this.dir = dir;
        }

        @Override
        public int compareTo(Pos o) {
            return this.mirrorCnt - o.mirrorCnt;
        }
    }

    static int[] dx = {-1, 0, 1, 0};
    static int[] dy = {0, 1, 0, -1};
    static int INF = 100*100+1;
    static int w, h;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        // 입력값 저장
        w = Integer.parseInt(st.nextToken());
        h = Integer.parseInt(st.nextToken());

        char[][] map = new char[h][w];
        int[][][] visited = new int[4][h][w];
        Pos from = null, to = null;

        for (int i = 0; i < h; i++){
            map[i] = br.readLine().toCharArray();

            for (int j = 0; j < w; j++) {
                if(map[i][j] == 'C'){ // 시작 및 도착 위치
                    if(from == null) from = new Pos(i, j, -1, -1);
                    else to = new Pos(i, j, -1, -1);
                }
            }
        }

        // 결과 출력
        System.out.println(bfs(from, to, map, visited));
    }

    private static int bfs(Pos from, Pos to, char[][] map, int[][][] visited) {
        PriorityQueue<Pos> pq = new PriorityQueue<>();

        for (int d = 0; d < 4; d++) {
            for (int i = 0; i < h; i++)
                Arrays.fill(visited[d][i], INF);
        }

        // 시작지점 처리
        pq.add(from);

        // bfs
        while(!pq.isEmpty()){
            Pos cur = pq.poll();

            // 도착지점 도달
            if(cur.x == to.x && cur.y == to.y)
                return cur.mirrorCnt;

            for (int d = 0; d < 4; d++) {
                int nx = cur.x + dx[d];
                int ny = cur.y + dy[d];

                // 범위를 벗어나거나 벽이면 넘어감
                if(!inBound(nx, ny, w, h) || map[nx][ny] == '*') continue;

                Pos next = new Pos(nx, ny, cur.mirrorCnt, d);
                if(d != cur.dir) next.mirrorCnt++;

                if(visited[d][nx][ny] > next.mirrorCnt) {
                    visited[d][nx][ny] = next.mirrorCnt;
                    pq.add(next);
                }
            }
        }

        return -1;
    }

    private static boolean inBound(int x, int y, int w, int h) {
        return !(x < 0 || y < 0 || x >= h || y >= w);
    }
}
<관련 포스팅>
[네트워크 IP] IP 주소 체계(클래스 기반 할당 방식, 서브넷 마스크
[네트워크 IP] DHCP, NAT

ARP

ARP(Address Resolution Protocol)와 RARP(Reverse ARP)

  • 컴퓨터와 컴퓨터 간의 통신은 정확히 얘기하면 IP 주소 기반이 아니라 MAC 주소 기반
  • ARP란 IP와 MAC 주소의 다리 역할을 하는 프로토콜로, 가상 주소인 IP 주소를 실제 주소인 MAC주소로 변환함
  • 이와 반대로 RARP를 통해 실제 주소인 MAC 주소를 가상 주소인 IP 주소로 변환하기도 함

 

ARP의 주소를 찾는 과정

  1. ARP Request 브로드캐스트를 보내 구하고자 하는 IP 주소를 모든 호스트에게 전송
  2. 그에 해당하는 장치가 ARP Replay 유니캐스트를 통해 자신의 MAC주소를 반환

 


홉바이홉과 라우팅

홉바이홉 통신이란

  • IP 주소를 통해 통신하는 과정을 홉바이홉 통신이라고 함
  • 홉(hop)이란 영어 뜻 자체로는 건너뛰는 모습을 의미
  • 통신망에서 각 패킷이 여러 개의 라우터를 건너가는 모습을 비유적으로 표현한 것

 

라우팅 테이블

  • 라우팅 테이블은 송신지에서 수신지까지 도달하기 위해 사용됨
  • 목적지에 도달하기 위해 거쳐야할 다음 라우터의 정보를 리스트로 저장
    • Network Address(Destination)
    • Interface
    • NHA(Next Hop Address)
  • netstat -r 명령어를 통해 라우팅 테이블을 확인할 수 있음

 


게이트웨이

게이트웨이란

  • 사용자가 다른 네트워크와 통신하기 위해 반드시 거쳐야 하는 거점을 의미
  • 사전적으로는 ‘관문’이나 ‘출입구’라는 의미로, 고속도로의 톨게이트에 비유할 수 있음
  • 서로 다른 네트워크 상의 통신 프로토콜을 변환해주는 역할도 함
  • 라우터와 동일한 개념으로 이해할 수 있지만, 게이트웨이가 더 포괄적임
  • 컴퓨터가 통신을 위해 고유한 IP 주소를 할당하듯이, 게이트웨이에도 고유한 IP 주소가 필요

 

일상 속의 게이트웨이

  • 인터넷 유무선 공유기가 우리가 만나게되는 첫번째 게이트웨이
  • 사용자가 속해 있는 로컬 네트워크의 통신 프로토콜과 인터넷의 통신 프로토콜이 다르기 때문
  • 참고로 공유기는 게이트웨이, 라우터, 방화벽 등의 역할을 동시에 제공함

 

홉 카운트(hop count)

  • 컴퓨터에서 목적지 네트워크까지 도달하기까지 여러 개의 게이트웨이를 거침
  • 톨게이트의 요금 부과와 같이, 게이트웨이를 거칠 때마다 네트워크 부하도 증가하여 전송 속도가 느려질 수 있음
  • 이때 거치는 게이트웨이의 수를 홉 카운트라고 함

 

기본 게이트웨이(default gateway)

  • 게이트웨이는 필요에 따라 여러 개를 설정, 사용할 수 있음
  • 여러 게이트웨이 중에 기본으로 통과하는 것을 의미
  • 기업에서 보안 상의 이유로 인터넷 망과 사내 망을 분리했다면 게이트웨이가 각각 존재함
    (이런 경우 기본 게이트웨이를 인터넷 망용 게이트웨이로 설정하는 것이 일반적)
  • 사내 네트워크에 접근하고자 하는 경우, 사용자 컴퓨터에 저장된 라우팅 테이블을 참고하여 사내 네트워크용 게이트웨이를 거쳐 접근하게 됨
  • 그 외에의 경우, 모두 기본 게이트웨이인 인터넷 망용 게이트웨이를 타고 나감

 


프록시 서버

프록시란

  • 클라이언트와 서버 사이에 위치하여 그들의 HTTP 메세지를 정리하는 중개인의 역할
  • 클라이언트에게는 프록시가 서버고, 서버에게는 프록시가 클라이언트임
  • 즉 프록시는 양쪽 규칙을 잘 따라야 함

 

게이트웨이와의 차이점

  • 프록시는 HTTP 메세지만을, 게이트웨이는 HTTP나 POP등 서로 다른 프로토콜을 중계함
  • 프록시는 허용된 네트워크만 통과할 수 있지만 게이트웨이는 이러한 필터링 기능이 없음
  • 시용 목적
    • 프록시: 네트워크 캐싱, 특정 웹 사이트 액세스 방지, 보안 방화벽, 웹 캐시 문서 접근
    • 게이트웨이: 서로 다른 프로토콜과 애플리케이션 간의 통신을 통해 여러 종류의 리소스를 받아오기 위해 사용

 


References

접근

이 문제는 다익스트라로 풀 수도, 플로이드 워샬로 풀 수도 있는 문제다. 나는 플로이드로 풀었다. 플로이드 알고리즘의 경우 O(N^3)로 상당히 비효율적인 시간복잡도를 가지긴 하지만, 여기서는 N의 최댓값이 100이기 때문에 제한 시간 안에 가능하다. 또한, 문제를 읽어보면 결국 모든 노드 -> 모든 노드로 가는 최단거리를 구해야하는 문제이기 때문에 다익스트라를 선택해도 결국 N번만큼 함수를 호출해야 한다. 

 

코드

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

public class BJ14938 {
    static int INF = 1501;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;

        // 입력값 저장
        st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        int r = Integer.parseInt(st.nextToken());

        int[][] graph = new int[n+1][n+1];
        int[] items = new int[n+1];

        st = new StringTokenizer(br.readLine());
        for (int i = 1; i <= n; i++) {
            items[i] = Integer.parseInt(st.nextToken());

            Arrays.fill(graph[i], INF);
            graph[i][i] = 0;
        }

        for (int i = 0; i < r; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            int l = Integer.parseInt(st.nextToken());

            graph[a][b] = graph[b][a] = l;
        }

        // 플로이드 워샬
        int[][] dist = floyd(graph, n);

        // 최대 아이템 개수 계산
        int answer = 0;
        for (int i = 1; i <= n; i++) {
            int sum = 0;

            for (int j = 1; j <= n; j++) {
                if(dist[i][j] <= m)
                    sum += items[j];
            }

            answer = Math.max(answer, sum);
        }

        System.out.println(answer);
    }

    private static int[][] floyd(int[][] graph, int n) {
        int[][] dist = graph.clone();

        for (int k = 1; k <= n; k++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    if(i == k || k == j || i == j || dist[i][k] == INF || graph[k][j] == INF)
                        continue;

                    dist[i][j] = Math.min(dist[i][j], dist[i][k] + graph[k][j]);
                }
            }
        }

        return dist;
    }
}

접근

옛날에 C++로 풀었던 코드가 있어서 함께 첨부했다. 이번에 자바로 푼 방법이랑은 조금 달라서, 분명히 과거의 내가 작성한 코드일텐데 접근 방식이 다른게 새삼 신기했다. 자바 코드의 경우 입력으로 들어오는 Edge 데이터를 저장하기 위해 이너 클래스를 만들고, 2차원 ArrayList에 저장했다. 그리고 거리가 갱신된 경우 큐에 해당 간선을 넣는 방식으로 풀었다. 그런데 다익스트라 함수에서, 큐는 순서대로 넣고 빼다 보니 앞서 이미 갱신된 데이터들에 대해서 처리가 필요했다. 주석의 이미 업데이트된 경우 부분을 넣지 않으니 시간 초과가 발생했었다. c++의 경우에는 NxN 배열에 간선의 정보를 저장한 후 우선순위 큐를 사용했다. 

 

코드

JAVA 코드

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

public class BJ1916 {
    static class Edge{
        int from, to, cost;

        public Edge(int from, int to, int cost) {
            this.from = from;
            this.to = to;
            this.cost = cost;
        }
    }

    static ArrayList<ArrayList<Edge>> graph;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;

        // 입력값 저장
        int n = Integer.parseInt(br.readLine());
        int m = Integer.parseInt(br.readLine());

        graph = new ArrayList<>();
        for (int i = 0; i <= n; i++)
            graph.add(new ArrayList<>());

        for (int i = 0; i < m; i++) {
            st = new StringTokenizer(br.readLine());
            int from = Integer.parseInt(st.nextToken());
            int to = Integer.parseInt(st.nextToken());
            int cost = Integer.parseInt(st.nextToken());

            graph.get(from).add(new Edge(from, to, cost));
        }

        st = new StringTokenizer(br.readLine());
        int cityA = Integer.parseInt(st.nextToken());
        int cityB = Integer.parseInt(st.nextToken());

        int[] dist = dijkstra(cityA, cityB, n);

        System.out.println(dist[cityB]);
    }

    private static int[] dijkstra(int cityA, int cityB, int n) {
        int[] dist = new int[n+1];
        Arrays.fill(dist, -1);

        // 시작지점 처리
        Queue<Edge> q = new LinkedList<>();
        q.add(new Edge(cityA, cityA, 0));
        dist[cityA] = 0;

        while(!q.isEmpty()){
            Edge cur = q.poll();

            // 이미 업데이트된 경우
            if(dist[cur.to] < dist[cur.from] + cur.cost)
                continue;

            for(Edge neighbor : graph.get(cur.to)){
                if(dist[neighbor.to] == -1 || dist[neighbor.to] > dist[neighbor.from] + neighbor.cost) {
                    dist[neighbor.to] = dist[neighbor.from] + neighbor.cost;
                    q.add(neighbor);
                }
            }
        }

        return dist;
    }
}

 

C++ 코드

#include <iostream>
#include <vector>
#include <queue>
#define INF 1000000000
using namespace std;
typedef pair<int, int> pii;

vector<vector<int>> city;
vector<int> dist;
priority_queue<pii> pq; //방문 가능한 <거리, 노드번호>를 저장한다


void pq_push(int idx){
    int s = city.size();
    for(int i = 1;i < s;i++){
        //방문 가능한 노드를 거리와 함께 pq에 삽입
        if(city[idx][i] != INF){
            if(dist[idx]+city[idx][i] < dist[i]){
                pq.push(pii(-city[idx][i], i));
                dist[i] = dist[idx]+city[idx][i];
            }
        }
    }
}

int main(){
    int n, m, a, b, c;
    int start_node, end_node;
    scanf("%d%d", &n, &m);
    city = vector<vector<int>> (n+1, vector<int> (n+1, INF));
    dist = vector<int> (n+1, INF);

    for(int i = 0;i < m;i++){
        scanf("%d%d%d", &a, &b, &c);
        city[a][b] = min(city[a][b], c);
    }

    //시작 위치는 방문 처리
    scanf("%d%d", &start_node, &end_node);
    dist[start_node] = 0;
    pq_push(start_node);

    while(!pq.empty()){
        pii cur = pq.top(); pq.pop();
        pq_push(cur.second);
    }

    printf("%d", dist[end_node]);
}

다양한 인공지능 프로젝트

 

애플의 AR 개발 SW

  • 앞으로 출시될 헤드셋 판매 촉진을 위해서, 코딩을 모르는 사람도 AR앱을 개발 가능한 소프트웨어를 준비 중
  • Siri에게 명령해서 앱을 만들고, 앱스토어에 올려서 다운로드 가능하게 될 것
  • 애플은 자체 AR 콘텐츠도 준비중(VR보다는 AR에 더 집중)
  • 애플의 헤드셋은 AR 과 VR을 혼합하여, 사용자가 둘 간에 전환 가능

 

야후(Yahoo)가 검색으로 돌아온다

  • 가까운 시일 내에 컴백을 준비 중. 몇 주간 힌트가 될만한 행동을 했음
  • @YahooSearch 트위터 계정을 재활성화
  • 검색 플랫폼의 수석 PM 채용을 진행중
  • 경영진들이 검색 관련 포스팅을 올림

 

chatGPT는 혁신의 도구가 될 수 있을까?

  • 한계점
    • 기능상의 한계: 공정하고 정확한가?
      (잘못된 정보나 편향된 콘텐츠를 생성할 수 있고, 인간의 결함을 모방할 우려 존재)
    • 서비스상의 한계: 지속가능한 수익을 창출할 수 있는가?
      (단기간 사용자 확보보다는 ‘생성 AI의 시작을 알리는 계기’라는 점이 더 바람직)
  • 시사점
    • 한계 극복을 통해 초거대 AI는 보편화
    • 지식을 얻기 위한 노력이 줄어드는 세상
    • 검색 엔진 시대에서 창의성 엔진 시대로의 전환

 

금융앱의 미래

  • 리서치 기관인 컨슈머인사이트에서 금융앱 이용 만족도를 조사
  • 카카오뱅크와 토스가 1, 2위를 차지
  • 카카오뱅크, 토스, 카카오페이는 2000명 이상의 응답자를 기록한 반면 온라인 결제에서 강력한 성장세를 보이는 네이버페이는 1000명 이하의 응답자를 기록
  • 뱅크샐러드의 응답자수는 24위지만 앱 만족도는 3위 → 고도화된 자산관리 덕분
  • 토스는 압도적인 사용자 수와 만족도를 보이며 여러 세대의 필수 앱으로 등극
  • 10위권 안의 6개가 기존 금융사 앱으로, 만족도가 의외로 높음
  • 또한 파편화되고 있음에도 불구하고 사용자 수가 높음

 

강남권 새벽배송 핵심 거점...컬리, 송파 물류센터 이전

  • 서울 송파 물류센터의 운영을 올해 상반기 중으로 종료
  • 샛별배송 서비스
    • 컬리는 2015년 식품업계 최초문앞까지 신선식품을 배달해주는 서비스를 도입
    • 하지만 콜드체인이 갖춰진 물류센터 특수 포장과 배송, 인건비 등의 대규모 투자가 필요
    • 컬리, 쿠팡, SSG닷컴 모두 이익을 내지 못하다 쿠팡이 8년만에 드디어 흑자 전환
  • 유통/물류업계 반응
    • 재무 상황이 어려워지고 있다는 신호’로 해석
    • 기업공개(IPO)를 목표로 화장품 새벽배송 등 덩치를 키우던 컬리가 상장이 미뤄지면서 비용 절감에 사활을 걸었다는 것
    • 송파 물류센터가 없으면 배송 동선이 두배, 세배는 늘어날 것
  • 컬리 관계자
    • 송파 물류 클러스터는 사업 초기에 문을 연 곳으로, 오랜 기간 운영하다보니 설비나 시설 개선의 필요성이 있음
    • 가장 최근에 연 김포 물류 클러스터로 이전하는 것 뿐, 중단되는 기능은 없음
    • 송파가 단순히 서울만 담당했던 곳은 아님

접근

처음에 보았을 때는 문제가 잘 이해되지 않았는데, 고속도로가 없는 구간은 길이의 차이만큼 운전해서 간다는 사실을 뒤늦게 이해했다. 사실 이번 문제는 많이 헤매서 이게 괜찮은 코드인지는 잘 감이 안온다.. 다익스트라 알고리즘을 풀려고 고른 문제이지만 dp로도 풀 수 있다고 한다. 

 

일단 이 문제에서 신경써야할 부분은 "입력으로 들어오는 지름길이 최단거리가 아닐 수도 있다"는 점이다. 그리고 지름길의 도착 지점이 d값을 넘어간다면 결국 쓸모없는 데이터이므로, 입력값을 받을 때 이 두 경우를 고려해서 처리해줬다. 그렇게 값을 저장한 후, 다익스트라 함수에서는 출발 지점을 기준으로 정렬하는 PQ를 사용했다. d의 범위를 벗어나거나 거리가 갱신되지 않는 경우는 넘어가고 나머지는 새로운 값을 넣어준다. 이 때, 지름길이 있어도 +1칸으로 이동하는게 추후에 이득인 경우도 있으므로 지름길의 여부와 상관없이 한 칸 이동한 값을 함께 넣는다. 그리고 마지막에 결과값을 출력할 때, dist 배열을 -1로 초기화했기 때문에 dist[d]에서 1을 더해주도록 했다. 

 

코드

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

public class BJ1446 {
    static class Road implements Comparable<Road> {
        int from, to, cost;

        public Road(int from, int to, int cost) {
            this.from = from;
            this.to = to;
            this.cost = cost;
        }

        @Override
        // 출발 지점이 작은 것부터 정렬
        public int compareTo(Road o) {
            return this.from - o.from;
        }
    }

    static int INF = 10001;
    static ArrayList<ArrayList<Road>> graph;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;

        // 입력값 저장 및 초기값 설정
        st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int d = Integer.parseInt(st.nextToken());

        graph = new ArrayList<>();
        int[] dist = new int[INF];

        for (int i = 0; i < INF; i++)
            graph.add(new ArrayList<>());
        Arrays.fill(dist, -1);

        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            int from = Integer.parseInt(st.nextToken());
            int to = Integer.parseInt(st.nextToken());
            int cost = Integer.parseInt(st.nextToken());

            // 도착 지점을 넘어가는 지름길은 의미 없음 & 지름길이 항상 최단인 것은 아님
            if(to > d || cost >= to - from) continue;
            graph.get(from).add(new Road(from, to, cost));
        }

        dijkstra(dist, d);

        // 결과 출력(dist[0]이 -1이므로 1 증가시켜 출력)
        System.out.println(dist[d] + 1);
    }

    static void dijkstra(int[] dist, int d){
        PriorityQueue<Road> pq = new PriorityQueue<>();
        pq.add(new Road(0, 0, 0));
        dist[0] = -1;

        while(!pq.isEmpty()){
            Road cur = pq.poll();

            // 범위를 벗어남
            if(cur.to > d) continue;
            // 거리가 갱신되지 않음
            if(dist[cur.to] != -1 && dist[cur.to] < dist[cur.from] + cur.cost) continue;

            // 최단 거리 갱신
            dist[cur.to] = dist[cur.from] + cur.cost;

            // 지름길 이용
            for(Road road : graph.get(cur.to))
                pq.add(new Road(road.from, road.to, road.cost));

            // 한 칸 이동
            pq.add(new Road(cur.to, cur.to+1, 1));
        }
    }
}

접근

이러한 최단거리 문제를 풀 때, 현재 그래프의 데이터를 저장하는 데에는 두 가지 방식이 있다. 간단하게는 이차원 배열을 선언해서 연결된 곳에는 그에 해당하는 거리를, 갈 수 없는 노드라면 -1 혹은 INF값을 저장해놓는 방식이다. 다만 이 경우에는 NxN 으로 모든 노드의 연결 여부를 저장하므로, 간선의 개수가 적은 편이라면 메모리를 낭비하게 된다. 즉, N값이 커지면 메모리 초과가 날 수도 있다는 얘기다. 

 

두 번째로는 이차원 ArrayList를 통해 서로 연결된 노드의 정보만을 저장하는 방식이다. 첫 번째 방식보다 코드 구현 측면에서는 더 복잡하지만, 노드 개수에 비해 간선의 개수가 적다면 이 방식이 더 효율적이다. 나는 두번째 방식으로 풀었다.

 

코드

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

public class BJ18352 {
    static class City {
        int num, dist;

        City(int num, int dist){
            this.num = num;
            this.dist = dist;
        }
    }

    static int INF = -1;
    static ArrayList<ArrayList<Integer>> graph;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = null;
        ArrayList<Integer> answer = new ArrayList<>();

        // 입력값 저장
        st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        int k = Integer.parseInt(st.nextToken());
        int x = Integer.parseInt(st.nextToken());

        graph = new ArrayList<>();
        int[] dist = new int[n+1];

        for (int i = 0; i <= n; i++) {
            graph.add(new ArrayList<>());
            dist[i] = INF;
        }

        for (int i = 0; i < m; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            graph.get(a).add(b);
        }

        // 다익스트라
        dijkstra(dist, x);

        // 거리가 같은 도시의 개수 세기
        for (int i = 1; i <= n; i++) {
            if(dist[i] == k)
                answer.add(i);
        }

        if(answer.size() == 0){
            System.out.println(-1);
        }
        else{
            Collections.sort(answer);
            for (int i = 0, s = answer.size(); i < s; i++) {
                System.out.println(answer.get(i));
            }
        }
    }

    static void dijkstra(int[] dist, int x){
        Queue<City> q = new LinkedList<>();
        
        // 시작 지점 처리
        q.add(new City(x, 0));
        dist[x] = 0;

        while(!q.isEmpty()){
            City cur = q.poll();

            for(int node : graph.get(cur.num)){
                if(dist[node] == INF || dist[node] > cur.dist+1) {
                    dist[node] = cur.dist+1;
                    q.add(new City(node, cur.dist+1));
                }
            }
        }
    }
}
<관련 포스팅>
[네트워크 기초] 허브, 스위치, 라우터 차이
[네트워크 계층 모델] TCP/IP 4계층

[네트워크 계층 모델] OSI 7계층
[네트워크] TCP/UDP 차이(3, 4-way handshaking)

네트워크 기기의 처리 범위

  • OSI 7계층별로 처리 범위를 나눌 수 있음
  • 상위 계층을 처리하는 기기는 하위 계층을 처리할 수 있지만 그 반대는 불가능

 

계층별 기기의 종류

  • 물리 계층: 허브, NIC, 리피터, AP
  • 데이터 링크 계층: L2 스위치, 브리지
  • 인터넷 계층: 라우터, L3 스위치
  • 전송 계층: L4 스위치
  • 애플리케이션 계층: L7 스위치

 


1계층(물리 계층) 기기

NIC(Network Interface Card)

  • 컴퓨터와 네트워크 사이의 중개자 역할
  • 이더넷 케이블을 사용하여 네트워크에 연결하기 위한 컴퓨터 확장 카드
  • 이더넷 표준의 인기와 낮은 비용 때문에 대부분의 마더보드에 네트워크 인터페이스가 내장됨
  • 각 카드에는 고유의 식별번호인 MAC 주소가 있음
  • 전송시, CPU가 패킷 생성 → NIC가 전송 시작 → 종료 시, 인터럽트로 CPU에 통보
  • 수신시, CPU 메모리에 버퍼공간 할당 → NIC가 버퍼에 데이터 저장 → 종료 시, 인터럽트로 CPU에 통보

 

리피터

  • 단지 약해진 전기 신호를 증폭하여 전달하는 역할을 수행
  • 현재는 광케이블이 보급됨에 따라 잘 쓰이지 않음

 

더미 허브(L1 스위치)

  • 리피터 역할을 하며, 기존의 리피터와 다르게 여러 장비를 연결할 수 있음
  • CSMA/CD 방식을 적용하고 있기 때문에 여러 장비에서 동시에 데이터를 전송하지 못함
  • 하나의 허브에 연결된 모든 장비는 같은 Collision(충돌) 도메인에 속함

 

AP(Access Point)

  • 패킷을 복사하는 기기
  • AP에 유선 LAN을 연결한 후 다른 장치에서 무선 LAN 기술(와이파이 등)을 사용하여 무선 네트워크 연결을 할 수 있음
  • 공유기도 AP의 한 종류

 


2계층(데이터 링크 계층) 기기

브리지

  • 두 개의 근거리 통신망(LAN)을 상호 접속할 수 있도록 하는 연결 장치 → 통신망 범위를 확장
  • 포트와 포트 사이의 다리 역할을 하며, MAC 주소를 테이블로 관리
  • 단순히 전기적 신호만을 증폭시키는 것이 아니라 Frame의 전송 포트를 결정하여 다시 만들어 전송

 

L2 스위치

  • 가장 기본적인 스위치(계층 지정 없이 부를 경우 이에 해당)
  • 장치들의 MAC 주소를 테이블을 통해 관리
  • 단순히 패킷의 MAC 주소를 읽어 스위칭하는 역할
  • IP주소를 이해하지 못해 IP주소를 기반으로 라우팅은 불가능

 

브리지와 스위치의 차이

  • 스위치는 HW, 브리지는 SW 방식으로 처리함
  • 스위치의 속도가 더 빠르고, 제공되는 포트 수가 많으며 포트 별로 속도를 지정할 수도 있음
  • 스위치가 브리지의 상위 호환 장비로, 브리지는 현재 사용되지 않음

 

수행 기능

  • Learning: 호스트에서 요청이 오면 해당 MAC 주소를 테이블에 저장함
  • Flooding: 목적지 정보가 테이블에 없으면 모든 포트로 데이터를 전송함(Broadcasting)
  • Forwarding: 테이블의 MAC 주소를 참조하여 해당 포트로 데이터를 전송함(Unicasting)
  • Filtering: 출발지와 목적지가 같은 세그먼트에 있는 경우에는 다른 세그먼트로의 통로를 차단
    → 허브와 달리 Collision 도메인을 나눌 수 있음
  • Aging: 특정 시간이 지나면 테이블에서 오래된 MAC 주소를 삭제함

 


3계층(네트워크 계층) 기기

라우터

  • 네트워크간 데이터 전송을 위해 패킷 소모를 최소화하고 경로를 최적화하여 패킷을 포워딩
  • L3 스위치에 비해 가격이 비싸고 초기 설정이 복잡하다는 단점이 있음

 

L3 스위치

  • L2 스위치에 라우팅 기능이 추가된 장비로, IP 정보를 보고 스위칭
  • 기능적으로는 라우터와 차이가 거의 없으며 L3 스위치를 라우터라 해도 무방함
  • 라우터는 SW 방식이고 L3 스위치는 HW 방식이기 때문에 스위치가 훨씬 빠르고 저렴
  • 제품에 따라 지원하는 기능이 다르기 때문에 라우터와 스위치는 용도에 맞게 사용하면 됨
구분 L2 스위치 L3 스위치
참조 테이블 MAC 주소 테이블 라우팅 테이블
참조 PDU 이더넷 프레임 IP 패킷
참조 주소 MAC 주소 IP 주소

 


4계층(전송 계층) 기기

L4 스위치

  • TCP/UDP 등의 헤더를 보고 FTP, HTTP 등 프로토콜을 확인하여 스위칭의 우선 순위를 부여
  • IP주소와 PORT 기반으로 스위칭
  • 로드 밸런싱(많은 양의 트래픽을 여러 서버로 분산) 기능을 제공

 

로드밸런서를 이용한 서버 이중화

 

  • 여러 대의 서버를 마치 하나의 서버처럼 동작시키는 것
  • 로드밸런서의 대표적인 기능으로 서버 이중화를 들 수 있음
  • 성능을 쉽게 확장하고, 장애 발생 시에도 타 서버로 운영이 가능하게 함으로써 신뢰성을 향상

 


7계층(응용 계층) 기기

L7 스위치

  • 로드 밸런서라고도 하며, 시스템이 처리할 수 있는 트래픽 증가를 목표로 함
  • URL, 서버, 캐시, 쿠키뿐 아니라 IP주소, TCP/UDP 포트 정보의 패킷 내용까지 참조하여 세밀하게 트래픽을 분산함
  • 바이러스, 불필요한 외부 데이터 등을 걸러내는 필터링 기능
  • 응용 프로그램 수준의 트래픽 모니터링도 가능
  • 정기적인 헬스 체크(health check)를 통해 장애가 발생한 서버를 감지하고 분산 대상에서 제외

 

L4 스위치와 L7 스위치의 차이

  • L4 스위치와 기능과 역할은 동일하나, 추가적으로 페이로드를 분석하여 패킷을 처리
  • 페이로드를 분석함으로써 네트워크 공격에 대해 방어가 가능하며, 특정 바이러스 감염 패킷을 필터링할 수도 있음
  • L7 스위치는 네트워크 보안 강화의 장점이 있지만, 가격이 비싸다는 단점
  • L4 스위치는 스트리밍 관련 서비스에는 사용할 수 없으며 메시지를 기반으로 인식하지 못하고 IP와 포트를 기반으로(특히 포트) 트래픽을 분산함
  • 반면 L7 로드밸런서는 IP, 포트 이외에도 URL, HTTP 헤더, 쿠키 등을 기반으로 트래픽을 분산함

 

헬스 체크(health check)

  • 전송 주기와 재전송 횟수 등을 설정한 이후 반복적으로 서버에 요청을 보내는 것
  • L4, L7 스위치 모두 헬스 체크를 통해 정상적인/비정상적인 서버를 판별
  • 서버에 부하가 되지 않을 만큼 요청 횟수가 적절해야 함
  • TCP, HTTP 등 다양한 방법으로 요청을 보내며 이 요청이 정상적으로 이루어졌다면 정상적인 서버로 판별함
  • 예를 들어 TCP 요청을 보냈는데 3-way handshake가 정상적으로 일어나지 않았다면 정상이 아닌 것

 


References

+ Recent posts