728x90
반응형

문제 해설

이 문제는 a는 S에서 출발하여 A까지 가야하고, b는 S에서 출발하여 B까지 도착해야 한다. 그리고 만약 같이 합승한다면 돈은 한번만 내도 된다.
처음에는 진짜 너무 복잡하게 생각했었는데, S->A로 가는 모든 경로를 구하고 S->B로 가는 모든 경로를 구한다음.. 서로 조합을 해서 같은 경로를 구하고.. 같으면 비용 안내도 되니까 한번만 계산해서 그 중 최솟값을 구할려고 했다...

이걸 굳이 각각 나누어서 경로를 구할 필요가 없었다. 결국에는 어떤 노드까지는 a와 b가 같이 가고, 그 다음 그 노드에서 a는 A까지, b는 B까지 가면 된다. 만약 x->y로 가는 최소비용을 알고 있으면 쉬워진다. 그럼 1부터 n까지의 노드들을 다 탐색하여 같이 가는 노드를 구한 다음, a,b 따로 가는 비용을 구해주면 된다.

여기서 x->y로 가는 최소비용을 구하는 방법은 2가지이다. 다익스트라를 이용해서 구할 수 있고, 모든 비용을 구해야하기 때문에 플로이드 워셜을 사용해도 된다.

다익스트라를 이용한 방법

플로이드 워셜보다 다익스트라를 먼저 생각한 것은 시간 효율성 때문이다. 다익스트라는 시간복잡도가 O(ElogV)이기 때문에 여기서 n은 200이하의 자연수이기 때문에 O(E)가 나온다. 즉, 간선의 개수와 시간복잡도가 같다. (O(Elog200) -> O(2E) -> O(E))

파이썬

import heapq

def solution(n, s, a, b, fares):
    INF = int(1e9)
    answer = INF

    def dijkstra(start):
        dist = [INF for _ in range(n + 1)]
        dist[start] = 0
        q = []
        heapq.heappush(q, [dist[start], start])
        while q:
            cur_dist, node = heapq.heappop(q)
            for tmp_node, tmp_dist in cost[node]:
                if dist[tmp_node] > cur_dist + tmp_dist:
                    dist[tmp_node] = cur_dist + tmp_dist
                    heapq.heappush(q, [dist[tmp_node], tmp_node])
        return dist

    cost = [[] for _ in range(n + 1)]
    for start, end, val in fares:
        cost[start].append((end, val))
        cost[end].append((start, val))

    dist = []
    for i in range(n + 1):
        dist.append(dijkstra(i))
    # print(dist)
    for i in range(1, n+1):
        answer = min(dist[s][i] + dist[i][a] + dist[i][b], answer)
    return answer
import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;

class Solution {
    class Node implements Comparable<Node> {
        int node;
        int distance;

        public Node(int node, int distance) {
            this.node = node;
            this.distance = distance;
        }

        @Override
        public int compareTo(Node o) {
            return Integer.compare(this.distance, o.distance);
        }
    }

    public int solution(int n, int s, int a, int b, int[][] fares) {
        int answer = Integer.MAX_VALUE;

        ArrayList<Node>[] cost = new ArrayList[n + 1];
        for (int i = 1; i <= n; i++) {
            cost[i] = new ArrayList<>();
        }

        for (int[] fare : fares) {
            int start = fare[0];
            int end = fare[1];
            int val = fare[2];
            cost[start].add(new Node(end, val));
            cost[end].add(new Node(start, val));
        }

        int[][] dist = new int[n + 1][n + 1];
        for (int i = 1; i <= n; i++) {
            Arrays.fill(dist[i], Integer.MAX_VALUE);
        }

        for (int i = 1; i <= n; i++) {
            dijkstra(i, n, cost, dist[i]);
        }

        for (int i = 1; i <= n; i++) {
            answer = Math.min(answer, dist[s][i] + dist[i][a] + dist[i][b]);
        }

        return answer;
    }

    public void dijkstra(int start, int n, ArrayList<Node>[] cost, int[] dist) {
        PriorityQueue<Node> pq = new PriorityQueue<>();
        pq.add(new Node(start, 0));
        dist[start] = 0;

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

            for (Node next : cost[cur.node]) {
                int newDist = dist[cur.node] + next.distance;
                if (newDist < dist[next.node]) {
                    dist[next.node] = newDist;
                    pq.add(new Node(next.node, newDist));
                }
            }
        }
    }
}

플로이드-워셜을 이용한 방법

플로이드 워셜은 노드들을 거치는 최단거리를 모두 다 구하게 된다. 그래서 노드들을 3번 탐색하게 되어 시간복잡도가 O(N^3)이 나오게 된다.
이 방법은 시간초과를 항상 유념해야 하는데, 문제조건에서 N은 200이하이기 때문에 O(10^6)정도가 나오게 되어 가능하다.

파이썬

def solution(n, s, a, b, fares):
    INF = int(1e9)
    cost = [[INF for _ in range(n + 1)] for _ in range(n + 1)]
    for i in range(1, n + 1):
        cost[i][i] = 0
    for fare in fares:
        start = fare[0]
        end = fare[1]
        val = fare[2]
        cost[start][end] = val
        cost[end][start] = val
    for k in range(1, n + 1):
        for i in range(1, n + 1):
            for j in range(1, n + 1):
                cost[i][j] = min(cost[i][j], cost[i][k] + cost[k][j])
    answer = INF
    for i in range(1, n + 1):
        answer = min(answer, cost[s][i] + cost[i][a] + cost[i][b])
    return answer

자바

import java.util.Arrays;
class Solution {
    static int[][] cost;
    public int solution(int n, int s, int a, int b, int[][] fares) {
        cost = new int[n + 1][n + 1];

        for (int i = 1; i <= n; i++) {
            Arrays.fill(cost[i], 1000000);
        }

        for (int[] fare : fares) {
            int start = fare[0];
            int end = fare[1];
            int val = fare[2];
            cost[start][end] = val;
            cost[end][start] = val;
        }

        for (int k = 1; k <= n; k++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    if (i != j) {
                        cost[i][j] = Math.min(cost[i][j], cost[i][k] + cost[k][j]);
                    }
                }
            }
        }
        for (int i = 1; i <= n; i++) {
            cost[i][i] = 0;
        }

        int answer = Integer.MAX_VALUE;
        for (int i = 1; i <= n; i++) {
            answer = Math.min(answer, cost[s][i] + cost[i][a] + cost[i][b]);
        }

        return answer;
    }
}

정답 및 후기

python은 두 가지 방법 모두 통과하였지만, java의 경우 다익스트라에서 효율성에서 4개를 틀렸었다.
원인은 시간복잡도 때문인데, 시간초과가 나는 이유는 다익스트라에서 간선이 많은 경우에 문제가 생기는 것 같다.
간선이 많으면 위의 문제에서 시간복잡도가 초과될 수 있다.

항상 음수간선이 없으면 플로이드-워셜보다 다익스트라를 먼저 생각했는데(일반적으로 간선보다 노드 개수에 제한이 많은 경우가 많아서..)
이 경우는 노드가 200이하라 플로이드-워셜이 더 효과적인 문제였다.

728x90
반응형

'알고리즘 > 알고리즘 문제' 카테고리의 다른 글

[알고리즘] 2048 (Easy)  (0) 2023.10.08
[알고리즘] 친구비  (2) 2023.06.03
[백준] 타임머신 11657  (0) 2023.03.22
[백준 2636] 치즈  (0) 2023.03.11
[백준 - 1062] [파이썬] 가르침  (0) 2023.01.08
728x90
반응형

📌 신뢰적 데이터 전송의 원리

슬라이딩 윈도우

고정 사이즈의 윈도우가 이동하면서 윈도우 내에 있는 데이터를 이용해 문제를 풀이하는 알고리즘을 말합니다. 

배열이나 리스트의 요소의 일정 범위의 값을 비교할때 사용하면 매우 유용합니다. 

원래 네트워크에서 사용되던 알고리즘을 문제풀이에 응용한 경우라고 할 수 있습니다.

Go Back N

GBN에서 송신자는 송신한 패킷에 대한 확인 응답 없이, 최대 N개의 패킷을 전송할 수 있습니다.

이를 크기가 N인 윈도우로 표현합니다. (N은 흐름제어와 혼잡제어에 의해 조정된다.)

송신한 패킷이 올바르게 수신측에 도착하여 확인 응답을 받으면, 윈도우는 앞으로 이동하고 다음 패킷을 전송합니다.

이를 슬라이딩 윈도우 프로토콜(sliding-window protocol)이라고 부릅니다.

Selective Repeat

Selective Repeat 프로토콜은 손실 되거나 손상된 패킷에 대해서만 재전송 합니다.

GBN처럼 윈도우 크기만큼 패킷들을 전송하지 않는다. 즉, 불필요한 재전송을 하지 않습니다.

GBN 수신자는 누적확인 응답을 하지 않는다. 순서번호가 앞서는 패킷이 도착하면 그대로 수신합니다.

📌 TCP

TCP

TCP (전송 제어 프로토콜)는 두 개의 호스트를 연결하고 데이터 스트림을 교환하게 해주는 네트워크 프로토콜입니다.

3 way handshake

3-way-handshake는 신뢰적인 데이터 전송을 보장하기 위해 3개의 패킷을 주고 받으며 사전 연결 설정을 수립하는 과정입니다.

4 way handshake

4개의 특별한 패킷을 주고 받으며 TCP 연결을 해제하는 방법입니다.

TCP 빠른 재전송

타임아웃에 의한 재전송의 문제점은 타임아웃의 주기가 때때로 길다는 점입니다.

다행히도 송신자는 중복 ACK 수신을 통해 타임아웃이 일어나기 전에 송신된 패킷이 손실되었음을 인지할 수 있습니다.

수신자는 순서가 올바르지 않은 세그먼트를 수신하면, 마지막으로 올바르게 수신된 세그먼트에 대한 ACK를 송신측에 전송합니다.

송신자가 만약 같은 세그먼트에 대해 3개의 중복된 ACK를 수신하게 되면

해당 ACK에 해당하는 세그먼트가 손실되었음을 인지하게 재전송을 하게됩니다.

이는 타임아웃에 상관없이 이루어지므로 빠른 재전송이라고 할 수 있습니다.

Congestion control

네트워크 혼잡을 줄이기 위해 패킷 송신 속도 즉, 송신측의 윈도우 크기를 조절하는 것을 말합니다.

네트워크가 혼잡해지면 지연이 커지고 패킷 손실이 발생합니다.

패킷이 지연되거나 손실되면 재전송이 이루어지는데, 이렇게 되면 네트워크는 더욱 혼잡해집니다.

Flow control

송신자가 패킷을 보내는 속도가 수신자가 수신버퍼에서 패킷을 읽어드리는 속도보다 빠른경우
수신 버퍼에 오버플로우가 일어나 패킷을 수신할 수 없는 상황이 발생합니다.

이를 방지하기 위해 송신자가 패킷 송신 속도를 조절하는 것. 즉 송신측의 윈도우 크기를 조절하는 것을 flow control이라고 합니다.
728x90
반응형

'CS' 카테고리의 다른 글

[네트워크] UDP  (0) 2023.11.21
[네트워크] DNS  (0) 2023.11.21
[데이터베이스] 데이터베이스 트랜잭션, 회복  (0) 2023.10.12
Token과 Session, Cookie와 Local Storage  (0) 2023.02.13
728x90
반응형

UDP

TCP와 달리 UDP는 연결 지향형이 아니고, 신뢰적인 데이터 전송을 보장하지 않습니다.

단지 체크섬을 통해 수신된 패킷의 오류 여부 정도만을 알 수 있습니다.

하지만 UDP는 TCP에 비해 기능이 별로 없기 때문에 적은 오버헤드로 빠른 전송이 가능 합니다.

따라서 일정 전송 요구량이 있고, 조금의 데이터 손실을 허용하는 스트링 애플리케이션에 어울립니다.

DNS 서버도 UDP를 사용합니다.

UDP의 장단점

UDP의 장점은 비연결형 서비스이므로 TCP에 비해 속도가 빠르며 네트워크 부하가 적습니다.
또한, 1:1, 1:N, N:N 통신이 가능합니다.

단점으로는 데이터의 신뢰성이 없습니다.

UDP 체크섬

UDP 체크섬은 UDP 세그먼트의 오류 검출을 위해 사용되는 것 입니다.

체크섬은 송신할 세그먼트를 16비트 단위로 나누고, 모두 더한 다음 1의 보수를 취해서 만들어 집니다.

이 체크섬을 세그먼트와 같이 전송 합니다.

수신자는 수신된 세그먼트에 대해 동일한 방식으로 체크섬을 만들고 
헤더의 체크섬과 일치 하는지 비교함으로써
수신된 세그먼트의 오류를 검출할 수 있습니다.

전송 후 대기 프로토콜

전송후 대기 프로토콜은 패킷을 전송하고 그 패킷에 대한 수신 확인 응답을 받고나서, 다음 패킷을 전송하는 방식 입니다. 

이러한 방식은 네트워크 링크 이용률이 낮아 속도가 느리다는 단점이 있습니다.

파이프라인 프로토콜

파이프라이닝 프로토콜은 전송한 패킷에 대한 수신 확인 응답을 받지 않고도, 

여러 개의 패킷을 연속으로 전송하여 링크 이용률과 전송 속도를 높이는 프로토콜 입니다. 
728x90
반응형

'CS' 카테고리의 다른 글

[네트워크] TCP  (3) 2023.11.22
[네트워크] DNS  (0) 2023.11.21
[데이터베이스] 데이터베이스 트랜잭션, 회복  (0) 2023.10.12
Token과 Session, Cookie와 Local Storage  (0) 2023.02.13
728x90
반응형

📌 DNS

DNS

DNS는 도메인 네임을 IP 주소로 변환해주는 프로토콜이자 계층형 구조로 구현된 분산 데이터베이스 입니다.

호스트가 도메인 네임에 대한 IP 주소를 요청하면 DNS는 계층 질의를 통해 IP 주소를 얻어다가 줍니다. 

만약 로컬 DNS에 해당 IP 주소가 캐싱되어 있다면 바로 받습니다.

빠른 응답을 제공하기 위해 DNS는 UDP 기반으로 동작하고 DNS 서버들은 요청 정보를 캐싱해둡니다.

DNS 작동 방식

사용자가 웹 브라우저에서 도메인 이름을 입력합니다.

브라우저는 해당 도메인을 DNS 서버에 보내서 IP 주소를 요청합니다.

DNS는 IP주소를 찾아서 브라우저에 보내고, 브라우저는 IP주소를 통해 서버에 요청을 보냅니다.

DNS 질의 종류

재귀적 질의는 도메인 네임에 해당하는 IP주소를 통해 DNS가 다른 DNS에게 재귀적으로 IP 주소를 물어보는 것을 뜻합니다.

반복적 질의는 IP 주소를 찾기 위해 반복적으로 질의하는 것입니다.

로컬 DNS가 루트 DNS에게 IP주소를 물어봤는데 없으면 TLD DNS에게 물어보고 또 없으면 
authoriative DNS에 반복적으로 물어보는 것을 예시로 들 수 있습니다.

DNS 서버에게 IP 주소를 요청할 때, UDP를 사용하는 이유

DNS는 신뢰성보다 속도가 더 중요한 서비스이기 때문에 연결 설정에 드는 비용이 없는 UDP를 사용합니다.

DNS는 연결 상태를 유지할 필요가 없고, TCP보다 많은 클라이언트를 수용할 수 있는 UDP를 사용합니다.

DNS 레코드

DNS 서버는 데이터베이스 서버의 한 유형이며, 클라이언트로부터 질의를 받았을 때 그에 맞는 데이터를 응답해야 합니다. 

데이터베이스의 한 항목(row)을 DNS 서버에서는 리소스 레코드라고 부릅니다.

- A
    - IP 주소와 도메인 주소를 매핑할 때 사용하는 레코드입니다.
- CNAME
    - 기존 도메인에 별명을 붙인 레코드입니다.
- TXT
    - 텍스트를 입력할 수 있는 레코드입니다.
    - 개인 프로젝트에서 무료 SSL 인증서를 등록하는 과정에서 사용하였습니다.
728x90
반응형

'CS' 카테고리의 다른 글

[네트워크] TCP  (3) 2023.11.22
[네트워크] UDP  (0) 2023.11.21
[데이터베이스] 데이터베이스 트랜잭션, 회복  (0) 2023.10.12
Token과 Session, Cookie와 Local Storage  (0) 2023.02.13
728x90
반응형

DB 세션

  • 데이터베이스 세션이란 데이터베이스 접속을 시작으로 여러 작업을 수행한 후 접속 종료까지의 전체 기간을 의미합니다.
  • 세션 안에는 여러 개의 트랜잭션이 존재할 수 있고, 여러 곳에서 데이터베이스를 접속할 경우, 많은 세션이 동시에 연결될 수 있습니다.

트랜잭션

  • 트랜잭션이란 데이터베이스 내에서 하나의 그룹으로 처리되어야 하는 명령문들을 모아 놓은 작업 단위입니다.

Commit

  • Commit이란 Transaction의 처리 과정을 데이터베이스에 정상적으로 처리하겠다고 확정하는 명령어입니다.
  • Commit을 수행하면 하나의 트랜잭션 과정을 종료하게 됩니다.

Rollback

  • Rollback이란 작업 중 문제가 발생했을 때, Transaction의 처리 과정에서 발생한 변경 사항을 취소하고, Transaction 과정을 종료시켜 Transaction으로 처리가 시작되기 이전의 상태로 되돌리는 명령어를 의미합니다.
  • 이전 COMMIT한 곳까지만 복구합니다.

Auto Commit 설정

  • DDL문에는 CREATE, ALTER, DROP과 같은 명령어들이 있는데 이들 모두는 자동으로 COMMIT을 실행합니다.

트랜잭션의 성질 ACID

  • ACID는 하나의 transaction의 안전성을 보장하기 위해 필요한 성질입니다. 각각은 Atomicity, Consistency, Isolation, Durability를 의미합니다.
  • Atomicity는 원자성으로 한 Transaction의 연산들이 모두 성공하거나, 반대로 전부 실패되는 성질을 말합니다.
    • 하나의 거래에서 계좌 출금과 입금은 모두 성공하거나 전부 실패해야 합니다.
  • Consistency는 일관성으로 Transaction의 이전과 이후, 데이터베이스 상태는 이전과 같이 유효해야 한다는 성질입니다.
    • 제약 조건 중 모든 고객은 반드시 이름이 있어야 한다는 제약이 있다면 이름 없는 새로운 고객 추가 쿼리 같은 것은 일관성이 유지되지 않는 쿼리입니다.
  • Isolation은 격리성으로 모든 Transaction은 다른 Transaction으로부터 독립되어야 한다는 뜻입니다.
  • Durability은 지속성으로 하나의 Transaction이 성공적으로 수행되었다면, 해당 Transaction에 대한 로그가 남아야하는 성질을 말합니다.
  • 만약 런타임 오류나 시스템 오류가 발생하더라도, 해당 기록은 영구적이어야 합니다.

트랜잭션 격리 수준

  • Transaction의 격리 수준(Isolation Level)이란 여러 Transaction이 동시에 처리될 때, 특정 Transaction이 다른 Transaction에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 여부를 결정하는 것입니다.
  • 트랜잭션의 격리 수준은 수준이 높은 순서대로 SERIALIZABLE, REPEATABLE READ, READ COMMITTED, READ UNCOMMITED가 존재합니다.

트랜잭션 격리 수준 READ UNCOMMITTIED

  • READ UNCOMMITTED는 커밋하지 않은 데이터 조차도 접근할 수 있는 격리 수준입니다.
  • 다른 트랜잭션의 작업이 커밋 또는 롤백되지 않아도 즉시 보이게 됩니다.
  • 어떤 트랜잭션의 작업이 완료되지 않았는데도, 다른 트랜잭션에서 볼 수 있는 부정합 문제를 Dirty Read라고 합니다.

Dirty Read

  • 어떤 트랜잭션의 작업이 완료되지 않았는데도, 다른 트랜잭션에서 볼 수 있는 부정합 문제를 Dirty Read라고 합니다.
  • Dirty Read 상황은 시스템에 상당한 버그를 초래할 수 있기 때문에 MySQL을 사용한다면 최소한 READ COMMITTED이상의 격리 수준을 사용해야 합니다.

트랜잭션 격리 수준 READ COMMITTED

  • READ COMMITTED는 커밋된 데이터만 조회할 수 있습니다.
  • REPEATABLE READ에서 발생하는 Phantom Read에 더해 Non-Repeatable(반복 읽기 불가능) 문제까지 발생합니다.

READ COMMITTED에서 발생할 수 있는 Non-Repeatable Read(반복 읽기 불가능)

  • READ COMMITTED에서 반복 읽기를 수행하면 다른 트랜잭션의 커밋 여부에 따라 조회 결과가 달라질 수 있는 것을 반복 읽기 불가능이라고 합니다.
  • 하나의 트랜잭션에서 동일한 데이터를 여러 번 읽고 변경하는 작업이 금전적인 처리와 연결되면 문제가 생길 수 있습니다.

트랜잭션 격리 수준 REPEATABLE READ

  • RDBMS는 변경 전의 레코드를 백업해두어서 변경 전과 변경 후의 데이터가 모두 존재합니다. 이걸 MVCC(Multi-Version Concurrency Control, 다중 버전 동시성 제어)라고 부릅니다.
  • 이를 통해 서로 다른 트랜잭션 간에 접근할 수 있도록 고유한 트랜잭션 번호가 존재하는데, 백업 레코드에서는 어느 트랜잭션에 의해 백업되었는지 트랜잭션 번호를 함께 저장합니다.
  • REPEATABLE READ는 MVCC를 이용해 한 트랜잭션 내에서 동일한 결과를 보장하지만, 새로운 레코드가 추가되는 경우에 부정합이 생길 수 있습니다.
  • REPEATABLE READ는 어떤 트랜잭션이 읽은 데이터를 다른 트랜잭션이 수정하더라도 동일한 결과를 반환할 것을 보장합니다.

유령 읽기

  • 유령 읽기란 다른 트랜잭션에서 수행한 작업에 의해 레코드가 안보였다 보였다 하는 현상을 의미합니다.
  • 트랜잭션이 끝나기 전에 다른 트랜잭션에 의해 추가된 레코드가 발견될 수 있는 현상을 의미합니다.
  • 유령 읽기는 잠금이 사용되는 경우에 발생할 수 있습니다.
  • 일반적으로 MySQL의 REPEATABLE READ에서는 Phantom Read가 발생하지 않습니다.

쓰기 잠금과 읽기 잠금

  • 쓰기 잠금은 SELECT ... FOR UPDATE 구문으로 사용합니다.
  • 읽기 잠금은 SELECT FOR SHARE 구문을 사용해야 합니다.
  • 락은 트랜잭션이 커밋 또는 롤백될 때 해제됩니다.

트랜잭션 격리 수준 SERIALIZABLE

  • 가장 엄격한 격리 수준으로, 이름 그대로 트랜잭션을 순차적으로 진행시킵니다.
  • 여러 트랜잭션이 동일한 레코드에 동시 접속할 수 없으므로 어떠한 데이터 부정합 문제도 발생하지 않습니다.
  • 하지만, 트랜잭션이 순차적으로 처리되어야 하므로 동시 처리 성능이 떨어집니다.

오라클에서는 READ COMMITTED를 기본으로 사용하고 MYSQL에서는 REPEATABLE READ를 기본으로 사용합니다

DB 동시성 제어(Concurrency Control)

  • 동시성 제어란 동시에 실행되는 여러 개의 트랜잭션이 작업을 성공적으로 마칠 수 있도록 트랜잭션의 실행 순서를 제어하는 기법입니다.
  • 트랜잭션의 직렬성을 보장하고, 데이터의 무결성 및 일관성을 보장하여 줍니다.

동시성 제어를 하지 않은 경우 발생하는 문제점

갱신 손실 문제

  • 하나의 트랜잭션이 갱신한 내용을 다른 트랜잭션이 덮어씀으로써 갱신이 무효화가 되는 것을 의미합니다.
  • 두 개의 트랜잭션이 한 개의 데이터를 동시에 갱신할 때 발생합니다.

DB 락(Lock)

  • Lock이란 트랜잭션 처리의 순차성을 보장하기 위한 방법으로 데이터의 무결성과 일관성을 지키기 위해 Lock을 사용합니다.

Lock의 종류

  • Shared Lock(공유 락) : 공유락은 데이터를 변경하지 않는 읽기 명령에 대해 주어지는 락입니다.
  • Exclusive Lock(베타 락) : 데이터에 변경을 가하는 명령들에 대해 주어지는 락으로 다른 세션이 해당 자원에 접근하는 것을 막습니다.
  • Update Lock(업데이트 락) : 데이터를 수정하기 위해 베타 락을 걸기전에 데드 락을 방지하기 위해 사용되는 락입니다.
  • Intent Lock(내제 락) : 내제 락은 사용자가 요청한 범위에 대한 락을 걸 수 있는지 여부를 빠르게 파악하기 위해 사용되는 락입니다.

Blocking

  • 블로킹은 Lock간의 경합이 발생해서 특정 트랜잭션이 작업을 진행하지 못하고 멈춰선 상태를 말합니다.
  • 베타-베타, 베타-공유 간에 블로킹을 발생시킬 수 있습니다.

DB 데드락

  • 데드락은 교착상태라고 하며 교착상태는 두 트랜잭션이 각각 Lock을 설정한 다음 서로의 Lock에 접근하여 값을 얻어오려고 할 때 이미 각각의 트랜잭션에 의해 Lock이 설정되어 있기 때문에 양쪽 트랜잭션 모두 영원히 처리가 되지 않게 되는 상태를 말합니다.

데이터베이스 장애의 유형

  • 트랜잭션 장애 : 트랜잭션의 실행 시 논리적인 오류로 발생할 수 있는 에러 상황(잘못된 데이터 입력, 데이터의 부재, 오버플로우, 자원의 한계 초과 요청)
  • 시스템 장애 : H/W 시스템 자체에서 발생할 수 있는 에러 상황
  • 미디어 장애 : 디스크 자체의 손상으로 발생할 수 있는 에러 상황

DB 회복

  • 데이터베이스를 장애가 발생했던 이전의 상태로 복구시켜서 일관된 데이터베이스 상태를 만드는 것을 의미합니다.

REDO, UNDO

  • REDO는 무언가를 다시 하는 것이고, UNDO는 무언가를 되돌리는 것입니다.
  • 둘 다 복구를 하지만, REDO는 복구를 할 때 사용자가 했던 작업을 그대로 하지만, UNDO는 사용자가 했던 작업을 반대로 진행합니다.

체크포인트 회복 기법

  • 회복 기법을 간단하게 하기 위해서 사용하는 방법이 체크포인트 회복 기법입니다.
  • 체크 포인트 이전에 커밋 기록이 있는 경우 아무 작업이 필요 없고,
  • 체크 포인트 이후에 커밋 기록이 있는 경우 REDO를 진행하고
  • 체크 포인트 이후에 기록이 없는 경우 UNDO를 진행합니다.

MySQL InnoDB의 기본 트랜잭션 고립 수준

  • REPEATABLE READ입니다.
728x90
반응형

'CS' 카테고리의 다른 글

[네트워크] TCP  (3) 2023.11.22
[네트워크] UDP  (0) 2023.11.21
[네트워크] DNS  (0) 2023.11.21
Token과 Session, Cookie와 Local Storage  (0) 2023.02.13
728x90
반응형

문제 설명

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

 

12100번: 2048 (Easy)

첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2

www.acmicpc.net

문제 자체는 2048 게임을 해보았다면 알 수 있는 문제이다. 물론 게임과는 달리 블록이 추가되지는 않는다. 

처음에 생각한 방법은 브루트포스로 모든 가지수를 해보는 것이다. 최대 5번 이동한다고 하였으므로 사실 모든 가지수를 구하더라도 

4가지 방향에 5번이면 4*4*4*4*4 = 1024 이므로 10의 세제곱 정도면 나머지 연산에서 10000번의 미만의 연산이면 시간 부족 문제는 없을거라 판단하였다. 

 

물론 왼쪽, 오른쪽, 위, 아래 이동하는 것에서 생각하거나 헷갈려서 오래 걸렸던 것 같다. 그리고 살펴볼 개념이 조금 더 있는 것 같다. 

1. 반복할때 product 사용하는 부분 -> DFS로도 해결 가능
2. left right는 다르다
3. 백트래킹의 개념과 활용법
해를 찾는 도중에 막히면 더 이상 깊이 들어가지 않고, 이전 단계로 되돌아가서 해를 찾아나가는 기법.
4. DeepCopy의 개념과 copy와의 차이점

문제 해설

2번 먼저 생각하면 왼쪽과 오른쪽은 당연히 다른데, 구현하기가 힘들어서 잠깐 생각해보았다.. 

왼쪽 오른쪽과 왼쪽 왼쪽의 결과는 최대값만 구하면 된다고 생각하여 동일한 결과가 나오지 않을까? 라고 생각했는데 

중간에 위로 올라가는 간단한 예시만 들어도 반례를 찾을 수 있다. 

224

022

204

 

1번과 3번은 같은 맥락이다.

나는 product를 사용하여 중복 순열을 통해서 모든 가지수를 구하였지만, 이를 백트래킹과 DFS를 이용하여 연산의 수를 줄일 수 있다.

def dfs(board, cnt):
    global ans
    if cnt == 5:
        for i in range(n):
            for j in range(n):
                ans = max(ans, board[i][j])
        return

    for i in range(4):
        tmp_board = move(deepcopy(board), i)
        dfs(tmp_board, cnt + 1)

위와 같은 코드를 사용하면 백트래킹과 DFS를 둘 다 적용할 수 있는 코드이다. https://hongcoding.tistory.com/126

 

[백준] 12100 2048 (Easy) (Python 파이썬)

https://www.acmicpc.net/problem/12100 12100번: 2048 (Easy) 첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모

hongcoding.tistory.com

사실 4번째 이슈에서 제일 해결못했었는데 python에 대한 문법이였다.

python은 List가 1차원일 경우에는 list.copy()로 복사를 할 수 있지만, 2차원이 넘어갈 경우 오류가 생긴다.

무조건 같은 배열을 깊은 복사하기 위해서는 from copy import deepcopy를 한 뒤 deepcopy를 해서 복사를 하도록 하자. 

코드

from itertools import product
from copy import deepcopy

n = int(input())
graph = []
for _ in range(n):
    graph.append(list(map(int, input().split())))


def move_left(graph):
    new_graph = graph.copy()
    for i in range(n):
        start_index = 0
        next_index = start_index + 1
        while next_index < n:
            if new_graph[i][start_index] == new_graph[i][next_index]:
                new_graph[i][start_index] = new_graph[i][start_index] + new_graph[i][next_index]
                new_graph[i][next_index] = 0
                start_index = next_index + 1
                next_index = start_index + 1
                if start_index > n or next_index > n:
                    break
            else:
                if new_graph[i][next_index] == 0:
                    next_index += 1
                    if next_index > n:
                        break
                else:
                    start_index = next_index
                    next_index = start_index + 1
                    if start_index > n or next_index > n:
                        break
        temp = new_graph[i]
        zero = []
        for j in range(n):
            if temp[j] == 0:
                zero.append(j)
            else:
                if zero:
                    new_graph[i][zero.pop(0)] = new_graph[i][j]
                    new_graph[i][j] = 0
                    zero.append(j)
    return new_graph


def move_right(graph):
    new_graph = graph.copy()
    for i in range(n):
        start_index = n - 1
        next_index = start_index - 1
        while next_index >= 0:
            if new_graph[i][start_index] == new_graph[i][next_index]:
                new_graph[i][start_index] = new_graph[i][start_index] + new_graph[i][next_index]
                new_graph[i][next_index] = 0
                start_index = next_index - 1
                next_index = start_index - 1
                if start_index <= 0 or next_index < 0:
                    break
            else:
                if new_graph[i][next_index] == 0:
                    next_index -= 1
                    if next_index < 0:
                        break
                else:
                    start_index = next_index
                    next_index = start_index - 1
                    if start_index <= 0 or next_index < 0:
                        break
        temp = new_graph[i]
        zero = []
        for j in range(n - 1, -1, -1):
            if temp[j] == 0:
                zero.append(j)
            else:
                if zero:
                    new_graph[i][zero.pop(0)] = new_graph[i][j]
                    new_graph[i][j] = 0
                    zero.append(j)
    return new_graph


def move_up(graph):
    new_graph = graph.copy()
    for i in range(n):
        start_index = 0
        next_index = start_index + 1
        while next_index < n:
            if new_graph[start_index][i] == new_graph[next_index][i]:
                new_graph[start_index][i] = new_graph[start_index][i] + new_graph[next_index][i]
                new_graph[next_index][i] = 0
                start_index = next_index + 1
                next_index = start_index + 1
                if start_index > n or next_index > n:
                    break
            else:
                if new_graph[next_index][i] == 0:
                    next_index += 1
                    if next_index > n:
                        break
                else:
                    start_index = next_index
                    next_index = start_index + 1
                    if start_index > n or next_index > n:
                        break
        zero = []
        for j in range(n):
            if new_graph[j][i] == 0:
                zero.append(j)
            else:
                if zero:
                    new_graph[zero.pop(0)][i] = new_graph[j][i]
                    new_graph[j][i] = 0
                    zero.append(j)
    return new_graph


def move_down(graph):
    new_graph = graph.copy()
    for i in range(n):
        start_index = n - 1
        next_index = start_index - 1
        while next_index >= 0:
            if new_graph[start_index][i] == new_graph[next_index][i]:
                new_graph[start_index][i] = new_graph[start_index][i] + new_graph[next_index][i]
                new_graph[next_index][i] = 0
                start_index = next_index - 1
                next_index = start_index - 1
                if start_index <= 0 or next_index < 0:
                    break
            else:
                if new_graph[next_index][i] == 0:
                    next_index -= 1
                    if next_index < 0:
                        break
                else:
                    start_index = next_index
                    next_index = start_index - 1
                    if start_index <= 0 or next_index < 0:
                        break
        zero = []
        for j in range(n - 1, -1, -1):
            if new_graph[j][i] == 0:
                zero.append(j)
            else:
                if zero:
                    new_graph[zero.pop(0)][i] = new_graph[j][i]
                    new_graph[j][i] = 0
                    zero.append(j)
    return new_graph


ways = product([0, 1, 2, 3], repeat=5)
ans = 0
for way in ways:
    temp = deepcopy(graph)
    for i in way:
        if i == 0:
            temp = move_left(temp)
        elif i == 1:
            temp = move_right(temp)
        elif i == 2:
            temp = move_up(temp)
        elif i == 3:
            temp = move_down(temp)
    for i in range(n):
        for j in range(n):
            ans = max(ans, temp[i][j])
print(ans)

 

728x90
반응형

'알고리즘 > 알고리즘 문제' 카테고리의 다른 글

[알고리즘] 합승 택시 요금  (2) 2023.11.27
[알고리즘] 친구비  (2) 2023.06.03
[백준] 타임머신 11657  (0) 2023.03.22
[백준 2636] 치즈  (0) 2023.03.11
[백준 - 1062] [파이썬] 가르침  (0) 2023.01.08
728x90
반응형

JWT(Json Web Token)

JWT란 Json 객체로 안전하게 전송하기 위한 방식이다. 이 정보는 디지털 서명이 되어 있으므로 신뢰할 수 있다.

보통 HMAC 알고리즘이나 RSA 알고리즘을 사용하여 암호화를 한다.

핵심은 서명된 토큰이라는 점이고, 내가 쓴게 맞다! 라는 의미로 사용한다.

서명된 토큰은 그 안에 포함된 클레임(정보)의 무결성을 확인할 수 있게 해준다.

 

JSON은 Base64URL로 인코딩되어 있다. Base64URL은 암호화를 하고, 복호화를 할 수 있게 해주는 방법이다. (해시랑 다른 것이다. 예를 들어 비밀번호를 데이터베이스에 저장할 때는 해시 함수를 이용하여 암호화를 해주기 때문에 암호화된 데이터로는 원래의 비밀번호가 어떤건지 알 수 없다. -> 그래서 비밀번호 찾기에서 비밀번호를 알 수는 없는 것이다. )

그러니까 사실 이상하게 적혀있긴 하지만 Header와 payload에 대한 정보는 사실 그냥 디코딩만 하면 알 수 있다. (Base64 URL을 통해 디코딩)

JWT의 핵심은 signature이다. 

 

JWT의 구조는 xxxx.yyyy.zzzz -> 헤더, payload, signature로 구성되어 있다.

signature은 헤더 + payload + 개인키 를 HMAC(HS256)으로 암호화한 것이다. 

 

헤더에 들어가는 정보

  • 사용중인 알고리즘
  • 토큰 유형(JWT)

payload에 들어가는 정보

  • 클래임
    • 등록된 클레임 : 필수 조건은 아니지만 권장되는 미리 정의된 클레임(발행자, 주체, 청중..)
    • 개인 클레임 : 유저아이디 같은 정보를 넣을 수 있다. 유저를 특정할 수 있는 것을 개인 클레임에 보통 넣는다.

secret키는 서버만 알고 있는 키이다. 클라이언트가 username으로 tae77777을 보내고 비밀번호로 1234를 보낸다고 생각해보자. 그리고 secret키가 'secret'이라고 했을 시 아래와 같다. 여기서 signature만 HS256으로 암호화를 한 뒤에 Base64로 인코딩을 하게 되는 것이다. HS256 방식은 HMAC 방식과 SHA256방식을 합해서 말한 것으로 시크릿 키를 포함한 암호화 방식이다. 

중요한 점은 복호화 할 수 없다 는 점이다. 

 

JSON web token을 client는 보통 local Storage에 넣어둔다. 이후에 클라이언트가 서버에 요청할 때 JWT를 실어서 요청하면 서버가 JWT가 신뢰할 수 있는 token인지 확인한다.

그럼 서버에서는 JWT 토큰에 대해서 어떻게 검증할까? -> Signature에 HS256으로 암호화된 정보가 유효한지 알아야 한다.

일단, JWT를 받으면 Header와 Payload를 알 수 있다. 그럼 Base64URL로 인코딩된 header와 Base64URL로 인코딩된 payload와 secret key를 한번에 HS256으로 암호화를 한다. 그럼 클라이언트에게서 받은 JWT의 signature부분을 Base64URL로 복호화해서 같은지 비교해서 인증한다.
-> 인증이 되면 payload에 있는 user 정보를 통해서 DB에서 select해서 돌려주면 된다.

이 방법은 비밀번호를 저장할 때도 같은 방식이다!


세션과 JWT 토큰

Http는 stateless인데 stateful처럼 쓰기 위해서 세션을 만들고 쿠키를 만든다. 쿠키는 동일 도메인에서만 요청이 올 때 발동한다. (CSRF)

 

쿠키와 세션은 http의 장점인 stateless를 벗어나는 stateful하게 되는 것이다. 즉 서버에 상태를 저장한다. 서버가 한 대일때는 세션과 쿠키에 별다른 단점이 없다. 세션의 문제는 서버가 여러 개가 생길 때 문제가 생긴다. 

 

만약 서버를 scale out하여 개수를 늘린다면 서버마다 세션에 대한 정보를 공유하여야 사용자를 확인할 수 있다. 그럼 결국 중앙 세션 저장소를 반드시 만들어야 한다. 

 

Security Config 작성

Security Filter Chain은 다른 Filter보다 무조건 먼저 작동한다. 그리고 Jwtfilter는 Security 동작전에 실행되어야 한다.

 

http Basic 방식 : 매번 요청할 때마다 ID와 Password를 다 알고 요청하게 된다. -> 쿠키, 세션을 만들 필요가 없다.
-> ID, pw가 중간에 노출될 수 있기 때문에 https서버를 사용해야 한다.

 

우리가 쓰려는 방식은 Bearer 방식이다.
: Authorization에 token을 넣는 방식. -> 노출되어도 얘 자체가 ID, PW가 아니기 때문에 위험부담이 적다.
이런 방식이 http Bearer방식이다.

728x90
반응형
728x90
반응형

(https://school.programmers.co.kr/learn/courses/30/lessons/214289)

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

[문제 정보]

먼저 온도가 여러 개 나오는데 실내온도, 실외온도, 희망온도가 주어지고, 또 승객들 등등 여러 가지 정보가 한번에 나온다. 한번에 읽으면 정리가 안되기 때문에 어떤 걸 해결해야하는지 고민해봐야 한다. 

 

먼저 온도가 변하는 것을 생각하지 말고 온도가 어떤 범위 내에 있어야 하는지를 생각해보자. 승객이 있을 때는 온도는 t1~t2에 있어야 한다. 그리고 승객이 없을 때는 온도가 어디든지 사실 상관이 없다. 그러나 문제는 온도는 1도씩밖에 바뀌지 않기 때문에 승객이 있기 1분 전에는 반드시 t1-1에 있거나 t2+1에 있어야 승객이 있을 때 범위 내에 있게 된다. 

 

이번엔 온도가 어떻게 변하는지 살펴보겠다. 일단 에어컨이 꺼져 있을 경우, 온도는 실외온도를 따라간다. 에어컨을 껐는데 실외온도에 차이가 없다면 온도는 그대로 유지가 된다.

에어컨이 켜져 있을 경우엔 2가지 이다. 에어컨을 켜고 온도를 유지하는 경우와 에어컨을 켜서 실내온도와 반대 방향으로 1도 변경하는 경우이다. 

이렇게 단계적으로 생각해보면 희망온도는 사실 의미가 없다. 에어컨을 켜서 온도를 변하게 할 것인지 아니면 유지할 것인지의 문제일 뿐이다.

  • 에어컨 X, 실내온도 방향으로 1도 변경하는 경우(비용 0)
  • 에어컨 X, 온도를 유지하는 경우(비용 0)
  • 에어컨 O, 온도를 유지하는 경우(비용 b)
  • 에어컨 O, 온도를 실내온도와 반대 방향으로 1도 변경하는 경우(비용 a)

여기까지 이해하고 문제를 풀면 된다.

[문제 해설]

먼저 시간의 흐름이 있고, 온도의 변화도 있으므로 dp 2차원을 생각해내야 한다. 시간이 흐름에 따라 온도가 어떻게 변화하는지 확인하여 최적의 비용을 계산할 수 있기 때문이다.

 

DP를 사용하려면 문제가 있다. 지금 온도 범위는 -10도에서 40도까지인데 2차원 배열의 인덱스에는 음수가 들어갈 수 없다는 점이다. 그래서 온도를 임의로 10도씩 올려서 계산한다. 

 

그리고 dp를 사용하여 int(1e9)의 큰 값으로 모든 값들을 초기화 한다.

처음의 온도는 실외온도와 같다고 하였으므로 dp[0][temperature]은 0으로 초기화해준다. (여기가 출발점이다.)

 

그 이후에는 dp를 1부터 주어진 onboard의 길이까지 진행하면 된다. 

 

승객이 있을 경우는 dp 온도를 t1에서 t2만 구하면 된다. 그리고 승객이 없다면 나머지를 굳이 구할 필요없이 min(t1, 실외온도) 에서 max(t2, 실외온도)로 구하면 된다. 실외온도는 t1~t2 바깥 값이므로 이렇게 범위를 구하면 된다. 온도가 계속하여 이 사이의 값에 있어야 효율적임을 알 수 있다. 이유는 예시를 하나 생각해보면 된다. t1이 10도 이고, 실외온도가 8도라고 가정하면 8도 이상이어야 된다. 에어컨을 사용하여 8도 밑으로 내려갈 이유가 없다. (에어컨의 소비전력이 최소화가 되지 않는다)

 

그래서 먼저 범위를 구해주고 범위내의 dp값을 업데이트하는 식을 통해서 진행하면 된다. 

 

이 업데이트 하는 식이 조금 고민해야 한다. 

for j in range(start, end + 1):
    if j - 1 < 0:
        x = int(1e9)
    else:
        x = dp[i - 1][j - 1]
    if j + 1 > 50:
        y = int(1e9)
    else:
        y = dp[i - 1][j + 1]
    if j < temperature:
        dp[i][j] = min(x, y + a, dp[i - 1][j] + b)
    elif j > temperature:
        dp[i][j] = min(x + a, y, dp[i - 1][j] + b)
    elif j == temperature:
        dp[i][j] = min(x, y, dp[i - 1][j])

여기서 먼저 j-1이 0보다 작거나 j+1이 50을 넘어가면 계산이 안되므로 해당 값들을 처리해준다. 

 

먼저 j가 실외온도보다 낮을때 처리하는 경우를 생각해보자. dp[i][j]는 i분에 j온도가 되기 위한 비용이다. 

첫번째 먼저 i-1분에 j-1온도일 때는 실외온도를 따라가므로 에어컨이 없더라도 1도 올라간다. 

두번째 i-1분에 j+1온도일 때는 실외온도를 따라가면 j+2가 되거나 j+1이 유지(실외온도가 j+1이라면)되므로 j가 되기 위해서는 에어컨을 틀어서 온도를 낮춰야 한다. 

세번째 i-1분에 j온도일때는 유지만 하면 된다. 만약 에어컨을 끄면 실외온도를 따라가서 온도가 올라가므로 유지하려면 에어컨을 켜서 유지해야 한다. 에어컨이 유지하는 비용은 b라고 명시해주었다. 따라서 식은 다음과 같다. 여기서 x는 dp[i-1][j-1], y는 dp[i-1][j+1]이라고 생각하면 된다.

if j < temperature:
    dp[i][j] = min(x, y + a, dp[i - 1][j] + b)

 

그 다음에는 j가 실외온도보다 높을때 살펴보겠다. 

먼저 현재온도가 j-1이라면 j가 되려면 에어컨을 켜야한다. 아니면 실외온도를 따라가므로 온도가 떨어지거나 유지되기 때문이다.

두번째 j+1온도라면 실외온도를 그냥 따라가면 내려가므로 에어컨을 꺼도 된다.

세번째 j온도라면 유지를 해야한다. 그럼 그냥 b 비용을 내면된다. 이 세 개중에서 최소가 dp[i][j]가 된다.

elif j > temperature:
    dp[i][j] = min(x + a, y, dp[i - 1][j] + b)

 

마지막으로 j와 실외온도가 같을때 이다.

첫번째 현재온도가 j-1이라면 실외온도를 따라 올라가면 되므로 에어컨이 필요없다.

두번째 현재온도가 j+1이라면 실외온도를 따라 내려가면 되므로 에어컨이 필요없다.

세번쨰 현재온도가 j라면 실외온도 현재온도 목표온도가 다 같으므로 그냥 나두면 된다.

elif j == temperature:
    dp[i][j] = min(x, y, dp[i - 1][j])

 

이렇게 점화식을 계산하고 마지막 dp[i][j]에서 i가 onboard의 마지막일때 가능한 비용들이 나온다. 이 중 최소값이 정답이 되게 된다.

[코드]

def solution(temperature, t1, t2, a, b, onboard):
    temperature = temperature + 10
    t1 = t1 + 10
    t2 = t2 + 10

    dp = [[int(1e9)] * 51 for _ in range(len(onboard))]
    dp[0][temperature] = 0
    for i in range(1, len(onboard)):
        if onboard[i] == 1:
            start = t1
            end = t2
        else:
            start = min(t1, temperature)
            end = max(t2, temperature)
        for j in range(start, end + 1):
            if j - 1 < 0:
                x = int(1e9)
            else:
                x = dp[i - 1][j - 1]
            if j + 1 > 50:
                y = int(1e9)
            else:
                y = dp[i - 1][j + 1]
            if j < temperature:
                dp[i][j] = min(x, y + a, dp[i - 1][j] + b)
            elif j > temperature:
                dp[i][j] = min(x + a, y, dp[i - 1][j] + b)
            elif j == temperature:
                dp[i][j] = min(x, y, dp[i - 1][j])
        if i == len(onboard) - 1:
            return min(dp[len(onboard) - 1])

[후기]

실제 현대모비스 알고리즘 대회 때 머리만 싸매다가 못풀었었는데.. 다시 풀어도 머리가 터질 뻔했었다.
어려운 알고리즘이 있는 것보다 더 어려운 문제가 아닐까..(dp만 알면 풀 수는 있는)
그래도 좋은 문제 같아서 나중에 코딩 테스트 전에 한번 풀어보면 또 좋을것 같다.

728x90
반응형

+ Recent posts