www.acmicpc.net/problem/10250

 

10250번: ACM 호텔

프로그램은 표준 입력에서 입력 데이터를 받는다. 프로그램의 입력은 T 개의 테스트 데이터로 이루어져 있는데 T 는 입력의 맨 첫 줄에 주어진다. 각 테스트 데이터는 한 행으로서 H, W, N, 세 정수

www.acmicpc.net

심심한 김에, 포스팅 하나 더 올린다.

설명만 길 뿐, 아주 단순한 문제다.

 

101,201,301, ... 102,202,302, ... 순서대로 방에 들어가는 아주 단순한 규칙만 찾으면 된다.

 

H=6일 때 10번째 손님은 402호에 배정된다.

402호라는 숫자를 어떻게 얻어낼 수 있을지 생각해보자.

 

101~601

102~402

 

1번째 손님 = 101호

6번째 손님 = 601호

 

boundary의 규칙만 잘 파악하면 전체 규칙을 파악할 수 있다. 이 때의 boundary는 601호 -> 102호로 흘러가는 타이밍이다.

7번째 손님 = 102호

 

YY=01

XX=02

 

자세한 설명은 다소 생략하고,

N번째 손님에 대해

(XX-1) * H + YY = N라는 규칙이 있음을 알 수 있다

 

검증)

102호 : (2-1)*6+1 = 7번째 손님

402호 : (2-1)*6+4 = 10번째 손님

 

이걸 코드로 표현하면 끝인 것이다.

 

아, 위에서는 X,H,Y를 가지고 N을 구하는 식을 작성했다.

우리가 해야할 일은 N을 가지고 X,Y를 구하는 것이다.

 

삽질..

더보기

N=10,H=6으로 다시 생각해보면

N/H=1과 나머지 4

배정 호 수는 402호이므로

YY=(나머지)

XX=(N/H)+1

임을 알 수 있다

 

검증)

N=3,H=6

배정 호 수 = 301

YY=3

XX=(3/6)+1=1

YYXX=301

 

간단한 문제라고 급하게 풀다가 실수만 하지말자.

 

testcase = int(input())

for i in range(testcase):
    s = input().split()
    h,w,n = map(int, s)
    
    yy = n%h
    xx = n//h+1
    if xx < 10:
        yyxx = str(yy)+'0'+str(xx)
    else:
        yyxx = str(yy)+str(xx)
    print(yyxx)

 

loop 방식이 비효율적이고 생각해서 n/h 방식으로 접근했다가, n/h == 0인 edge case가 문제가 되었다.

그래서 loop 방식으로 다시 접근해보았다.

 

testcase = int(input())

for i in range(testcase):
    s = input().split()
    h,w,n = map(int, s)
    
    temp_n = n
    xx = 1
    while temp_n > h:
        xx += 1
        temp_n -= h
    yy = temp_n
    
    if xx < 10:
        yyxx = str(yy)+'0'+str(xx)
    else:
        yyxx = str(yy)+str(xx)
    print(yyxx)

티스토리.. 코드블럭 기능이 추가되었다 했더니 코드블럭 밑에서 한글이 안 나오는 버그가 있나보다. (바로 입력하면)

아무튼, 다소 효율적인 코드는 아니지만 문제를 해결할 수 있었다

www.acmicpc.net/source/26406155

 

로그인

 

www.acmicpc.net

다음 포스팅은 tistory를 대신할 블로그 플랫폼을 찾는 글이 될 것 같다..

 

 


WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

www.acmicpc.net/problem/14681

 

14681번: 사분면 고르기

점 (x, y)의 사분면 번호(1, 2, 3, 4 중 하나)를 출력한다.

www.acmicpc.net

 

몸풀이 if문 문제다

설명이 필요없다

 

소스는 여기에

www.acmicpc.net/source/26402990

 

---

 

마지막 포스팅 날짜가 2019.06월로, 1년 반만에 포스팅이다.

 

블로그 포스팅을 다시 시작해보려 한다. 단, 본격적인 Tech Blog는 medium에 올리려 한다 (UI/UX 및 검색 노출?)

medium.com/jongho-developer

 

jongho-developer – Medium

Jongho’s Tech Blog.

medium.com

여기는 좀더 일상적이고, 간단한, 가벼운, 한국 사람들에게 노출&소통하고 싶은(관심있어할 만한) 내용을 포스팅해보려 한다.

하다 못해 이런 알고리즘 문제풀이 포스팅이라도 남기려 한다.

 

새삼스럽지만 티스토리 포스팅 UI 참... 떠나고 싶게 만든다.. ㅋㅋㅋㅋ

카테고리 설정 어디에 있는거야..


WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <stdio.h>
#include <stdlib.h>
 
typedef int Element;
 
class Node
{
public:
    int id;
    Element elem;
    Node* left;
    Node* right;
 
    Node() : Node(00, nullptr, nullptr) {};
    Node(int id, Element elem) : Node(id, elem, nullptr, nullptr) {};
    Node(int id, Element elem, Node* left, Node* right) : id(id), elem(elem), left(left), right(right) {}
    virtual ~Node() 
    {
        printf("Node %d Deleted \n", id);
        delete left;
        delete right;
    }
    void preorder()
    {
        printf(" %d", elem);
        if (left) left->preorder();
        if (right) right->preorder();
    }
    void inorder()
    {
        if (left) left->preorder();
        printf(" %d", elem);
        if (right) right->preorder();
    }
    void postorder()
    {
        if (left) left->preorder();
        if (right) right->preorder();
        printf(" %d", elem);
    }
    Node* search_child(int id)
    {
        if (this->id == id)
            return this;
 
        Node* node = nullptr;
        if (left != nullptr)
            node = left->search_child(id);
        if (node == nullptr && right != nullptr)
            node = right->search_child(id);
 
        return node;
    }
};
 
Node* mirror_tree(Node* node)
{
    Node* left = nullptr;
    if (node->right)
        left = mirror_tree(node->right);
 
    Node* r = nullptr;
    if (node->left)
        r = mirror_tree(node->left);
 
    return new Node(node->id, node->elem, left, r);
}
 
int main()
{
    // init tree (bottom-to-top)
    const int NODE_LEN = 6;
    Node* node[NODE_LEN];
    for (int i = 0; i < NODE_LEN; i++)
        node[i] = nullptr;
 
    node[4= new Node(44);
    node[5= new Node(55);
    node[2= new Node(22);
    node[3= new Node(33, node[4], node[5]);
    node[1= new Node(11, node[2], node[3]);
    Node* root = node[1];
 
    // test preorder()
    printf("preorder : ");
    root->preorder();
    printf("\n");
 
    // test preorder() of mirror_tree
    Node* mirror_root = mirror_tree(root);
    printf("preorder : ");
    mirror_root->preorder();
    printf("\n");
 
    // test search_child()
    Node* child = nullptr;
    child = root->search_child(2);
    if (child != nullptr)
        printf("id : %d \n", child->id);
    else
        printf("id : null \n");
 
    child = root->search_child(5);
    if (child != nullptr)
        printf("id : %d \n", child->id);
    else
        printf("id : null \n");
    
    child = root->search_child(6);
    if (child != nullptr)
        printf("id : %d \n", child->id);
    else
        printf("id : null \n");
 
    printf("Delete original tree \n");
    delete root;
    printf("Delete mirror_tree \n");
    delete mirror_root;
    return 0;
}
/*
original tree : 
  1
2   3
   4 5
preorder : 12345

mirror_tree : 
   1
 3   2
5 4
preorder : 13542
*/
 
cs




WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

Intro

알고리즘 문제 해결 전략 책을 읽다가, 흥미로운 문제가 있어서 보충설명을 추가하여 정리하였다.
문제는 아래 링크에서 발췌하였다. (정오표의 틀린 내용도 보완하여 발췌하였다)
http://book.algospot.com/candy.html

사탕 나눠주기 문제

예제 문제를 하나 풀면서 점진적인 개선을 어떻게 적용할 수 있나 살펴봅시다. N(N≤30)개의 사탕을 세 명의 어린이에게 가능한 공평하게 나눠 주려고 합니다. 공평함의 기준은 받는 사탕의 총 무게가 가장 무거운 어린이와 가장 가벼운 어린이 간의 차이로 합시다. 사탕의 무게는 모두 20 이하의 정수입니다. 가능한 최소 차이는 얼마일까요?

이 문제를 푸는 가장 단순한 방법은 사탕을 나눠 주는 모든 방법을 하나씩 만들어 보는 것입니다. 각 사탕마다 셋 중 어느 어린이에게 줄 지를 결정한다고 하면 3^N, 즉 최대 205조 개의 방법을 만들어 보게 됩니다. 이는 죽기 전에 다 세어볼 수 있을까 엄두가 나지 않는 큰 수이지만, 사실 이 때 우리는 엄청나게 많은 수를 중복으로 세고 있습니다. 실제로 받은 사탕들이 서로 다르더라도 그 무게가 같다면 이 문제의 답은 같다는 점을 무시했기 때문입니다.

이 아이디어를 이용해 모든 방법을 만들어 보는 완전 탐색(6장) 대신 각 어린이가 가진 사탕의 총량을 상태 공간으로 하는 너비 우선 탐색(29장)으로 문제를 풀 수 있습니다. 이 때 각 상태는 세 어린이가 가진 사탕의 양을 표현하는 세 개의 정수입니다. 우리는 어떤 어린이에게도 사탕이 없음을 나타내는 (0, 0, 0)에서 시작해, 사탕을 하나 줄 때마다 그 어린이가 가진 사탕의 양을 늘립니다. 이렇게 하면 실제로 가진 사탕들이 다르더라도 그 무게가 같은 경우 하나의 상태로 취급해, 중복을 제거할 수 있습니다. 이 상태 공간의 크기는 얼마일까요? 사탕의 최대 무게는 20이므로, 각 어린이가 가진 사탕의 양은 0에서 20×N 사이입니다. 따라서 각 상태에 대해 방문 여부를 저장하려면 크기가 (20×N+1)3≤601^3(대략 2억)인 배열을 잡아야 합니다.

2억은 대개 시간 제한 안에 탐색할 수 있는 크기입니다만, 이 문제의 답이 최대 얼마인지 생각해 보면 이 방법을 더 최적화할 수 있습니다. 사탕을 가장 많이 받은 어린이와 가장 적게 받은 어린이의 차이가 20 이상이었다고 가정해 봅시다. 사탕의 최대 무게는 20이므로, 이때 사탕을 가장 많이 받은 어린이가 가장 적게 받은 어린이에게 사탕을 하나 준다면 이 둘이 가진 사탕의 양의 차이는 항상 감소합니다. 이렇게 하면 항상 원래보다 더 공평한 답을 얻을 수 있기 때문에, 차이가 20 이상인 경우는 절대로 최적의 답이 될 수 없다는 것을 알 수 있지요. 따라서 넉넉잡아도 사탕을 가장 많이 받은 어린이가 (20×N)/3+20 넘게 사탕을 받는 경우는 무시해도 됩니다. 그러면 실제로 할당해야 하는 배열의 크기는 (20×N^3+20)^3≤220^3(대략 1000만)으로 줄어듭니다.

이 정도면 충분히 문제를 풀 수 있을 것 같지만, 문제를 푸는 더 빠른 방법이 있습니다. 세 어린이 중 누가 가장 사탕을 적게 받고, 누가 가장 많이 받는지는 중요하지 않기 때문입니다. 세 어린이의 사탕의 총량이 (180, 190, 200)이건 (200, 190, 180)이건 우리의 답은 변하지 않지요. 따라서 세 어린이의 사탕의 총량이 항상 오름차순으로 정렬되어 있는 경우만을 탐색하도록 합시다. 서로 다른 세 수를 여섯 가지의 방법으로 나열할 수 있으므로, 이렇게 하면 경우의 수는 다시 대략 1/6으로 줄어듭니다. 결과적으로 대략 200만 개 이하의 경우의 수만을 탐색해 문제를 해결할 수 있지요. 이 과정에서 천재만이 떠올릴 수 있는 번뜩이는 영감은 필요없었지만, 처음에 생각했던 방법에 비해 검사해야 할 상태의 수가 1억분의 1로 줄어들었음을 알 수 있습니다.

보충설명

1

예제 문제를 하나 풀면서 점진적인 개선을 어떻게 적용할 수 있나 살펴봅시다. N(N≤30)개의 사탕을 세 명의 어린이에게 가능한 공평하게 나눠 주려고 합니다. 공평함의 기준은 받는 사탕의 총 무게가 가장 무거운 어린이와 가장 가벼운 어린이 간의 차이로 합시다. 사탕의 무게는 모두 20 이하의 정수입니다. 가능한 최소 차이는 얼마일까요?

문제는 다음과 같다.
최대 30개의 사탕을 3명의 어린이에게 나누어주려고 한다.
사탕의 무게는 1~20의 정수이다.
받은 사탕 무게의 합이 가장 큰 어린이와, 가장 가벼운 어린이의 무게 차는 최소 얼마일까?

2

이 문제를 푸는 가장 단순한 방법은 사탕을 나눠 주는 모든 방법을 하나씩 만들어 보는 것입니다. 각 사탕마다 셋 중 어느 어린이에게 줄 지를 결정한다고 하면 3^N, 즉 최대 205조 개의 방법을 만들어 보게 됩니다. 이는 죽기 전에 다 세어볼 수 있을까 엄두가 나지 않는 큰 수이지만, 사실 이 때 우리는 엄청나게 많은 수를 중복으로 세고 있습니다. 실제로 받은 사탕들이 서로 다르더라도 그 무게가 같다면 이 문제의 답은 같다는 점을 무시했기 때문입니다.

N개의 사탕을 3명의 어린이에게 나누어주는 경우의 수는 3^N개이다 (중복순열)
N=30일 경우 경우의 수는 약 205조 개가 된다
여기서 간과한 점은 각 어린이가 어떤 사탕들을 받았느냐가 아닌, 각 어린이가 받은 사탕 무게의 합이 중요하다는 점이다
받은 사탕의 개수나 각 사탕의 무게는 별도로 카운팅할 필요 없다

3

이 아이디어를 이용해 모든 방법을 만들어 보는 완전 탐색(6장) 대신 각 어린이가 가진 사탕의 총량을 상태 공간으로 하는 너비 우선 탐색(29장)으로 문제를 풀 수 있습니다. 이 때 각 상태는 세 어린이가 가진 사탕의 양을 표현하는 세 개의 정수입니다. 우리는 어떤 어린이에게도 사탕이 없음을 나타내는 (0, 0, 0)에서 시작해, 사탕을 하나 줄 때마다 그 어린이가 가진 사탕의 양을 늘립니다. 이렇게 하면 실제로 가진 사탕들이 다르더라도 그 무게가 같은 경우 하나의 상태로 취급해, 중복을 제거할 수 있습니다. 이 상태 공간의 크기는 얼마일까요? 사탕의 최대 무게는 20이므로, 각 어린이가 가진 사탕의 양은 0에서 20×N 사이입니다. 따라서 각 상태에 대해 방문 여부를 저장하려면 크기가 (20×N+1)3≤601^3(대략 2억)인 배열을 잡아야 합니다.

30개의 사탕을 3명의 어린이에게 나눠주는 방법이 아닌, 3명의 어린이가 받은 사탕 무게의 합의 경우의 수를 카운팅할 것이다
각 어린이가 가진 사탕 무게의 합이 될 수 있는 범위는, 0 ~ (20xN)이다 (20xN인 경우는 무게가 20인 모든 사탕을 다 받았을 경우)
즉, 20xN+1개의 경우의 수이다
어린이가 3명이므로, (20xN+1)^3개의 경우의 수가 존재하고, 최대 601^3 (약 2억)개가 될 수 있다
205조 개에서 2억 개로 경우의 수를 줄인 것은 굉장히 큰 차이라고 할 수 있겠다.
연산시간 관점에서나, 메모리 사용 관점에서나 모두.

4

2억은 대개 시간 제한 안에 탐색할 수 있는 크기입니다만, 이 문제의 답이 최대 얼마인지 생각해 보면 이 방법을 더 최적화할 수 있습니다. 사탕을 가장 많이 받은 어린이와 가장 적게 받은 어린이의 차이가 20 이상이었다고 가정해 봅시다. 사탕의 최대 무게는 20이므로, 이때 사탕을 가장 많이 받은 어린이가 가장 적게 받은 어린이에게 사탕을 하나 준다면 이 둘이 가진 사탕의 양의 차이는 항상 감소합니다. 이렇게 하면 항상 원래보다 더 공평한 답을 얻을 수 있기 때문에, 차이가 20 이상인 경우는 절대로 최적의 답이 될 수 없다는 것을 알 수 있지요. 따라서 넉넉잡아도 사탕을 가장 많이 받은 어린이가 (20×N)/3+20 넘게 사탕을 받는 경우는 무시해도 됩니다. 그러면 실제로 할당해야 하는 배열의 크기는 (20×N^3+20)^3≤220^3(대략 1000만)으로 줄어듭니다.

이 부분이 바로, 이 글을 포스팅하게 만든 부분이다.
2억 가지의 경우의 수 중, 무게 차가 20 이상인 경우에 대해서 생각해보자.
사탕의 총 무게가 가장 무거운 어린이를 A, 가장 가벼운 어린이를 C라고 하자.
사탕의 최대 무게는 20이므로, A가 C에게 사탕을 하나 주면 무게 차는 항상 감소한다고 한다. (사실은 동등 또는 감소)
예시를 통해 검증해보자.

무게 차가 20 이상인 경우는 카운팅 할 필요 없다는 사실을 확인하기 위한 과정을 묘사하는 중이다.
A의 무게가 100, C의 무게가 80이라고 가정하자. 이 때 무게 차는 20이다.
A가 C에게 무게 20의 사탕을 1개 줬다면, A의 무게는 80, C의 무게는 100이 된다.
이 때도 무게 차는 20으로 변동없다. (무게 크기 관계는 역전됐지만)

만약 A가 C에게 무게 1의 사탕을 1개 줬다면, A의 무게는 99, C의 무게는 81이며 무게 차는 18로 감소한다.
사탕의 무게가 1~20이므로, 무게 차가 20인 경우에는 A가 C에게 사탕을 1개 주더라도 무게 차가 동등 또는 감소한다는 사실을 확인했다.

무게가 20 초과인 경우는 직관적으로, A가 C에게 사탕을 1개 주면 무게 차가 항상 감소한다는 사실을 알 수 있을 것이다.

문제에서 원하는 답은, 무게 차의 최소값이다.
하지만 무게 차가 20 이상인 경우는 최소 무게 차가 될 수 없다.
무게 차가 더 작은 경우가 항상 존재하기 때문이다.
무게 차가 더 작은 경우란, 무게 차가 20 이상인 경우에서 A가 C에게 사탕을 1개 준 것과 동일한 경우를 의미한다.

그러므로 무게 차가 20 이상인 경우는 카운팅 할 필요 없다.
문제에 나와있듯이 각 어린이의 사탕 총 무게가 (20×N)/3+20 이상인 경우는 카운팅할 필요 없다.
무게 차가 최소값이 될 수 없는 경우니까.
N=30인 경우 이 무게는 최대 220이 된다 (모든 사탕의 무게가 20)

사탕 총 무게가 가장 무거운 어린이가 220일 때, 가장 가벼운 어린이는 최대 180이 될 수 있다 (나머지 한 어린이는 200)
3명의 무게 합은 600이고, 무게 차는 40이 된다

필자의 생각으로는, 220^3가지의 경우의 수는, 무게 차가 40인 경우를 제외한 모든 경우의 수로 보인다
무게 차가 20 이상인 경우를 모두 제외하면 경우의 수를 더 줄일 수 있지 않을까 생각이 든다 (개선의 여지)
정리하여, 또다시 경우의 수를 획기적으로 감소시켰다.
이미 문제를 푸는 데에는 문제가 없는 수준까지 개선시켰다고 할 수 있겠다.

5

이 정도면 충분히 문제를 풀 수 있을 것 같지만, 문제를 푸는 더 빠른 방법이 있습니다. 세 어린이 중 누가 가장 사탕을 적게 받고, 누가 가장 많이 받는지는 중요하지 않기 때문입니다. 세 어린이의 사탕의 총량이 (180, 190, 200)이건 (200, 190, 180)이건 우리의 답은 변하지 않지요. 따라서 세 어린이의 사탕의 총량이 항상 오름차순으로 정렬되어 있는 경우만을 탐색하도록 합시다. 서로 다른 세 수를 여섯 가지의 방법으로 나열할 수 있으므로, 이렇게 하면 경우의 수는 다시 대략 1/6으로 줄어듭니다. 결과적으로 대략 200만 개 이하의 경우의 수만을 탐색해 문제를 해결할 수 있지요. 이 과정에서 천재만이 떠올릴 수 있는 번뜩이는 영감은 필요없었지만, 처음에 생각했던 방법에 비해 검사해야 할 상태의 수가 1억분의 1로 줄어들었음을 알 수 있습니다.

3명 중 누구의 사탕 총 무게가 가장 무거운지는 중요하지 않다.
그러므로 3명을 무게 순으로 오름차순 배치하여 경우의 줄일 수 있다.
3명의 어린이를 배치하는 방법은 순열 P(3,3) = 3x2x1 = 6가지 이고
이 중 오름차순 배치만 카운팅 하면 되므로, 경우의 수는 1/6배가 된다.
최종 경우의 수는 220^3 / 6가지가 되었다.

구현 코드

코드 하이라이팅 출처 : https://colorscripter.com/

3^N 카운팅 코드

 


WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 1, 2, 3 더하기
 
class Counter(object):
    save = None
 
    def init_save(self, N):
        if N < 3:
            N = 3
 
        self.save = [None] * (N+1)
        self.save[1= 1
        self.save[2= 2
        self.save[3= 4
 
    def count_of_combination(self, N):
        if N < 1:
            return 0
        elif self.save[N] != None:
            return self.save[N]
        else:
            result = (self.count_of_combination(N-1)
                      + self.count_of_combination(N-2)
                      + self.count_of_combination(N-3))
            self.save[N] = result
            # print('save[{}]={}'.format(N, result))
            return result
 
def main():
    T = int(input())
 
    counter = Counter()
 
    for i in range(T):
        N = int(input())
        counter.init_save(N)
        cnt = counter.count_of_combination(N)
        print(cnt)
 
if __name__ == '__main__':
    main()
 
cs





WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

1. 함수의 기능을 이해한다 (함수의 이름을 보고)

2. 함수의 파라미터들의 역할을 이해한다 (함수기능 이해 후에)

3. 블록 단위로 기능을 이해한다



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

<알고리즘 구현 접근법>


필자는 알고리즘을, 모든 케이스에 대하여 적절한 결과를 반환하는 것이라고 정의한다


그리고 알고리즘을 구현하는 과정은 아래와 같다

1. 알고리즘을 머릿속에 정리한다

2. 그것을 코드로 구현한다


어떻게 하면 정리를 할수있는걸까?

필자의 경우 아래와 같이 생각하였다

 - 간단한 케이스만 대응가능한 알고리즘부터 작성 후 발전시켜 나가는 것

 - 결과적으로, 모든 케이스들의 규칙을 찾는 것

 - 코드를 간단하게 만드는 것 (ex: 함수로 모듈화하는 것, ex: swap())


<재귀 알고리즘>

자신의 책임(역할)에만 집중한다

탈출조건은 마지막에 생각해도 된다


--------------------------------------


<hannoi top과 approach>


"일반/추상적인 알고리즘"을 바로 디자인하는 게 어렵다면, "구체적인 입/출력"을 통해서 접근해보자.


이 글은 알고리즘을 구현할 때의 접근방법을 설명하고 있다. 그 예로 하노이 탑을 들고 있다.

글로된 설명이라 이해하기 어려울 수 있으나, 이것 하나만 기억하자

"처음부터 일반적인 알고리즘을 작성하는 것은 어렵지만, 가장 간단한 케이스의 알고리즘부터 작성하고 그것을 발전시켜나가는 것은 비교적 쉽다"


N=3일 때의 기대출력결과이다

A B, 

A C, 

B A, 

C B, 

A B,

A C, 

B A, 

B C, 

A C


N=2일 때의 기대출력결과이다

A B

A C

B C


하노이 탑은 재귀호출로 해결할수 있는 알고리즘이다

하지만 두 입출력 결과를 비교해보아도 포함관계를 파악하기 어렵다

어떻게 접근하는게 좋을까?


N=3과 N=2 출력의 공통규칙을 찾는 것이 중요하다

가장 간단한 N=2에서 규칙을 찾고 그것을 좀더 복잡한 N=3에서 찾는 방식으로 접근하자


N=2일 때, 알고리즘을 생각하기 전에 우리의 목표가 무엇인지부터 확인해보자. 두께에 따라 1,2의 원반이라고 하겠다


2개의 원반을 두께 순서대로 3번째에 놓는 것이므로 계획은 아래와 같다

1. 두께 2의 원반을 3번째에 놓는다

2. 두께 1의 원반을 3번째에 놓는다


목표는 이러한데, 1을 바로 실행할 수가 없다

1의 목표를 달성하기 위한 좀더 구체적인 알고리즘을 생각해보자


1-1. 두께 1의 원반을 2번째에 놓는다 (A B)

1-2. 두께 2의 원반을 3번째에 놓는다 (A C)


하노이 탑 알고리즘의 핵심은 목표지점(3번째)에 원반을 놓기 위해, 나머지 한 곳(2번째)을 buffer로서 활용한다는 점이다

이런 알고리즘의 아이디어를 생각하는 것이 가장 어려운 부분이다.

하지만 원반갯수 N에 대하여 알고리즘을 생각하는 것보다는, 구체적인 입/출력을 놓고 그 케이스에 대한 알고리즘을 추상화시키는 것이 더 쉬운 접근방법일 것이다.


목표 2를 달성하는 데에는 문제가 없지만, 처음 계획과 달라진 점이 하나 있다

두께 1의 원반이 1번째가 아닌 2번째로 이동되었다는 점이다

그러므로 B C가 출력될 것이다


N=2의 분석이 끝났으므로, N=3의 케이스도 위와 같이 분석해보는 것이 다음 단계이다

이때 N=2와 공통된 부분을 찾는 것이 중요하다


N=3 케이스를 분석해보면 나머지 한 곳을 buffer로 활용하여 원반을 옮기는 과정이 반복된다는 것을 확인할 수 있다

글이 너무 길어지니 하노이 탑 예시는 여기서 줄이도록 하겠다 (쓰기 힘들다)


위에서 말한 문장만 기억하도록 하자

조금더 복잡한 알고리즘을 구현할수 있게 해주는 접근방법이 될것이다



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

일반적인 알고리즘 입문 커리큘럼에서는 Sorting을 가장 먼저 배운다

Sort 중에서도 Bubble Sort, Priority Queue(PQ) Sort부터 시작하는데, 아래와 같다

Sort

 - Bubble Sort - Ω(n^2)

 - PQ Sort

    - List - O(n^2)

    - Binary Heap - O(nlogn)

 - Merge Sort - O(nlogn)

 - Quick Sort

    -> expected(average) execute time: O(nlogn)

    -> worst case: O(n^2)

    -> 일반적으로 실제 수행속도는 가장 빠름


PQ sort 이후 Merge sort와 Quick sort를 배우게 된다

PQ, Merge, Quick sort의 상관관계가 헷갈려서 정리해보았다



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

Priority Queue의 구현은 크게 두가지로 나뉜다

 - List

    - unorderd list (무순리스트)

    - ordered list (순서리스트)

 - Binary Heap


여기서 List의 구현은 또 두가지로 나뉜다

 - Array (배열)

 - Linked List (연결리스트)


그리고 Binary Heap은 Complete Binary Tree를 기반으로 한다

Binary Heap

 - Complete Binary Tree (완전이진트리)


완전이진트리는 당연히 Tree의 한 종류이고, Tree의 구현도 두가지인데 리스트의 구현과 동일하다

(Complete Binary) Tree

 - Array

 - Linked List


구조가 복잡하니 보기쉽게 이미지로 표현하였다



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# CONSTANT
INFINITY = 'INF'
 
#################### high-level module ####################
 
def dijkstra(g, src):
    dist = [None] # distance list
    pq = [] # vertex pq (distance priority)
 
    for vtx in g.vtx_list[1:]:
        dist.append(INFINITY)
        pq.append(vtx)
    dist[src] = 0
 
    ### round pq ###
    while len(pq) != 0:
        vtx1 = remove_min_vtx(pq, dist)
        if dist[vtx1.idx] == INFINITY:
            break
        
        for edge in vtx1.adj_list: # 인접 정점들의 dist update
            vtx2 = edge.nxt
            new_dist = dist[vtx1.idx] + edge.w # src->vtx1 + u->vtx2
            if is_smaller(new_dist, dist[vtx2.idx]):
                dist[vtx2.idx] = new_dist
    
    return dist
 
#################### detail implementation ####################
 
# function of distance PQ
def remove_min_vtx(pq, dist):
    min_vtx = pq[0]
    for vtx in pq:
        if is_smaller(dist[vtx.idx], dist[min_vtx.idx]):
            min_vtx = vtx
    
    pq.remove(min_vtx)
    return min_vtx
 
# dist list function
def is_smaller(a, b):
    if a == INFINITY:
        return False
    elif b == INFINITY:
        return True
    else:
        return a < b
 
class DirGraph: # directed, weighted, adj_list graph
    def __init__(self, vtx_cnt):
        self.vtx_list = [None]
        
        for i in range(vtx_cnt):
            i=i+1
            self.add_vtx(i)
    
    def add_vtx(self, idx):
        self.vtx_list.append(Vertex(idx))
 
    def add_edge(self, u, v, w):
        edge = Edge(self.vtx_list[u], self.vtx_list[v], w)
        self.vtx_list[u].adj_list.append(edge) # adj_list == fwd_list
 
class Vertex:
    def __init__(self, idx):
        self.idx = idx
        self.adj_list = []
        
    def __str__(self):
        result = 'vtx_%d { ' % self.idx
        for edge in self.adj_list:
            result += '%s ' % edge
        return result+'}'
 
class Edge:
    def __init__(self, prev, nxt, weight):
        self.prev = prev
        self.nxt = nxt
        self.w = weight
        
    def __str__(self):
        return '(%d,%d,%d)' % (self.prev.idx, self.nxt.idx, self.w)
 
def main():
    vtx_cnt, edge_cnt = (int(i) for i in input().split())
    src = int(input())
    g = DirGraph(vtx_cnt)
    
    for i in range(edge_cnt):
        u, v, w = (int(i) for i in input().split())
        g.add_edge(u, v, w)
    
    dist = dijkstra(g, src)
    for i in dist:
        print(i)
 
def test_main():
    vtx_cnt, edge_cnt = 5,6
    src = 1
    g = DirGraph(vtx_cnt)
 
    g.add_edge(5,1,1)
    g.add_edge(1,2,2)
    g.add_edge(1,3,3)
    g.add_edge(2,3,4)
    g.add_edge(2,4,5)
    g.add_edge(3,4,6)
 
    print('<vtx_list>')
    for v in g.vtx_list[1:]:
        print(v)
    
    dist = dijkstra(g, src)
    print('\n<result>')
    for i in dist:
        print(i)
 
main()
cs


주석 추가예정



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

<graph의 종류>

graph는 크게 아래 두가지로 나뉜다

 - directed graph (방향 그래프)

 - undirected graph (무방향 그래프)


그 기준은 edge에 따라 갈리는데, edge는 graph의 구성요소 중 하나이다. graph의 구성요소는 아래와 같다

 - vertex(정점)

 - edge(간선)

    - directed or not

    - weighted or not


edge가 directed edge이면 directed graph가 되는것이다.


<graph의 구현>

그리고 그래프의 구현(implementation)은 아래 두가지로 나뉜다

 - adjust matrix (인접행렬)

    - index를 통한 빠른 vertex 접근

    - sparse graph일수록 공간 효율이 떨어짐 (<-> dense graph일수록 높아짐)

    - adjust vertices를 순회할 때 O(V)

 - adjust list (인접리스트)

    - adjMatrix와 반대의 특징을 가짐

    - adjust vertices를 순회할 때 O(E) (모든 edge가 해당 vertex의 adj edge일 때)

 - (incidence matrix/list도 있는듯..?)


위에서 saprse/dense graph가 나왔는데, edge가 적음/많음에 따라 graph를 아래와 같이 나눌수도 있다

 - sparse graph (희귀 그래프)

 - dense graph (밀집 그래프)




WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

BOJ에서는 numpy를 사용할수 없어서, 아래와 같이 2차원 리스트를 생성하였다


http://hashcode.co.kr/questions/693/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%97%90%EC%84%9C-2%EC%B0%A8%EC%9B%90-%EB%B0%B0%EC%97%B4%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%93%B0%EB%82%98%EC%9A%94


아래는 코드.

recursion과 dynamic programming을 사용하였다

recursion depth는 n이 될 것이다

그림으로 표현하면 tree처럼 표현할 수 있다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import numpy as np
#save = np.zeros((30, 30), np.uint32)
 
MAX = 30
save = [[0]*MAX for i in range(MAX)]
 
def bridge_cnt(n, m):
    if save[n][m] != 0:
        return save[n][m]
    
    for i in range(n-1, m): # n-1 ~ m-1
        save[n][m] += bridge_cnt(n-1, i)
    return save[n][m]
 
def main():
    for i in range(130):
        save[1][i] = i
 
    T = int(input())
    for i in range(T):
        N, M = tuple(int(i) for i in input().split())
        print(bridge_cnt(N, M))
 
main()
cs


언어는 python3가 아닌 pypy3를 선택했다



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

다익스트라 알고리즘(Dijkstra algorithm)은 그래프에서 노드 사이의 최단 경로를 찾는 알고리즘이다

출처: 나무위키


기회가 되면 코드 첨부예정


---


<다익스트라 알고리즘의 동작>

0. parameter

 - graph

 - src (출발 vertex)

1. init

 - 모든 vertex의 distance와 prev_vtx 초기화 (distance=INFINITE, prev_vtx=None)

 - src vertex의 distance=0

 - PQ <- 모든 vertex (priority=distance)

2. PQ 순회

 - src vertex부터 순회

 - 순회순서: 가까운 vertex 순서대로


---


<구현과정>

1. 가장 간단한 test-case


v1 -> v2 -> v3

   (3)   (5)


2. 다익스트라 함수 구현

 - high-level module부터 구현한다


def dijkstra(g, src):

...


3. 그래프 구현

 - detail implementation은 가장 마지막에 구현한다


class Graph:

...


---


<code example>

아래는 dist가 dict type인 version의 코드이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# CONSTANT
INFINITY = 'INF'
dist = None
 
#################### high-level module ####################
 
def dijkstra(g, src):
    global dist
    
    ### init ###
    dist = {} # distance dict
    #prev = {} # prev_vtx dict
    pq = [] # distance priority queue
 
    for vtx in g.vtx_list:
        dist[vtx] = INFINITY
        #prev[vtx] = None
        pq.append(vtx)
    dist[g.vtx_list[src-1]] = 0
 
    ### round pq ###
    while len(pq) != 0:
        vtx1 = remove_min_vtx(pq)
        if dist[vtx1] == INFINITY:
            continue
        
        for edge in vtx1.adj_list: # 인접 정점들의 dist update
            vtx2 = edge.nxt
            new_dist = dist[vtx1] + edge.w # src->vtx1 + u->vtx2
            if is_smaller(new_dist, dist[vtx2]):
                dist[vtx2] = new_dist
                #prev[vtx2] = vtx1
    
    return dist
 
#################### detail implementation ####################
 
def remove_min_vtx(pq):
    min_vtx = pq[0]
    for vtx in pq:
        if is_smaller(dist[vtx], dist[min_vtx]):
            min_vtx = vtx
    
    pq.remove(min_vtx)
    return min_vtx
 
def is_smaller(a, b):
    if a == INFINITY:
        return False
    elif b == INFINITY:
        return True
    else:
        return a < b
 
class DirGraph: # directed, weighted, adj_list graph
    def __init__(self, vtx_cnt):
        self.vtx_list = []
        
        for i in range(vtx_cnt):
            i=i+1
            #print('add_vtx(%d)' % i)
            self.add_vtx(i)
    
    def add_vtx(self, idx):
        self.vtx_list.append(Vertex(idx))
 
    def add_edge(self, u, v, w):
        edge = Edge(self.vtx_list[u-1], self.vtx_list[v-1], w)
        self.vtx_list[u-1].adj_list.append(edge) # adj_list == fwd_list
 
class Vertex:
    def __init__(self, idx):
        self.idx = idx
        self.adj_list = []
        
    def __str__(self):
        result = 'vtx_%d { ' % self.idx
        for edge in self.adj_list:
            result += '%s ' % edge
        return result+'}'
 
class Edge:
    def __init__(self, prev, nxt, weight):
        self.prev = prev
        self.nxt = nxt
        self.w = weight
        
    def __str__(self):
        return '(%d,%d,%d)' % (self.prev.idx, self.nxt.idx, self.w)
 
def main():
    vtx_cnt, edge_cnt = (int(i) for i in input('vtx_cnt, edge_cnt: ').split())
    src = int(input('src: '))
    g = DirGraph(vtx_cnt)
    
    for i in range(edge_cnt):
        u, v, w = (int(i) for i in input('u,v,w: ').split())
        g.add_edge(u, v, w)
        
    for v in g.vtx_list:
        print(v)
    
    dist = dijkstra(g, src)
    for key in dist:
        print(key, dist[key])        
 
def test_main():
    vtx_cnt, edge_cnt = 5,6
    src = 1
    g = DirGraph(vtx_cnt)
 
    g.add_edge(5,1,1)
    g.add_edge(1,2,2)
    g.add_edge(1,3,3)
    g.add_edge(2,3,4)
    g.add_edge(2,4,5)
    g.add_edge(3,4,6)
 
    for v in g.vtx_list:
        print(v)
    
    dist = dijkstra(g, src)
    for key in dist:
        print(key, dist[key])
 
#main()
test_main()
cs


아래는 dist가 list type인 version의 코드이다

또한 백준 알고리즘 (ACM-ICPC) 1753번 문제의 정답코드이다

주석(코드설명)은 아래 포스팅에서

http://ssaemo.tistory.com/109


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# CONSTANT
INFINITY = 'INF'
 
#################### high-level module ####################
 
def dijkstra(g, src):
    dist = [None] # distance list
    pq = [] # vertex pq (distance priority)
 
    for vtx in g.vtx_list[1:]:
        dist.append(INFINITY)
        pq.append(vtx)
    dist[src] = 0
 
    ### round pq ###
    while len(pq) != 0:
        vtx1 = remove_min_vtx(pq, dist)
        if dist[vtx1.idx] == INFINITY:
            break
        
        for edge in vtx1.adj_list: # 인접 정점들의 dist update
            vtx2 = edge.nxt
            new_dist = dist[vtx1.idx] + edge.w # src->vtx1 + u->vtx2
            if is_smaller(new_dist, dist[vtx2.idx]):
                dist[vtx2.idx] = new_dist
    
    return dist
 
#################### detail implementation ####################
 
# function of distance PQ
def remove_min_vtx(pq, dist):
    min_vtx = pq[0]
    for vtx in pq:
        if is_smaller(dist[vtx.idx], dist[min_vtx.idx]):
            min_vtx = vtx
    
    pq.remove(min_vtx)
    return min_vtx
 
# dist list function
def is_smaller(a, b):
    if a == INFINITY:
        return False
    elif b == INFINITY:
        return True
    else:
        return a < b
 
class DirGraph: # directed, weighted, adj_list graph
    def __init__(self, vtx_cnt):
        self.vtx_list = [None]
        
        for i in range(vtx_cnt):
            i=i+1
            self.add_vtx(i)
    
    def add_vtx(self, idx):
        self.vtx_list.append(Vertex(idx))
 
    def add_edge(self, u, v, w):
        edge = Edge(self.vtx_list[u], self.vtx_list[v], w)
        self.vtx_list[u].adj_list.append(edge) # adj_list == fwd_list
 
class Vertex:
    def __init__(self, idx):
        self.idx = idx
        self.adj_list = []
        
    def __str__(self):
        result = 'vtx_%d { ' % self.idx
        for edge in self.adj_list:
            result += '%s ' % edge
        return result+'}'
 
class Edge:
    def __init__(self, prev, nxt, weight):
        self.prev = prev
        self.nxt = nxt
        self.w = weight
        
    def __str__(self):
        return '(%d,%d,%d)' % (self.prev.idx, self.nxt.idx, self.w)
 
def main():
    vtx_cnt, edge_cnt = (int(i) for i in input().split())
    src = int(input())
    g = DirGraph(vtx_cnt)
    
    for i in range(edge_cnt):
        u, v, w = (int(i) for i in input().split())
        g.add_edge(u, v, w)
    
    dist = dijkstra(g, src)
    for i in dist:
        print(i)
 
def test_main():
    vtx_cnt, edge_cnt = 5,6
    src = 1
    g = DirGraph(vtx_cnt)
 
    g.add_edge(5,1,1)
    g.add_edge(1,2,2)
    g.add_edge(1,3,3)
    g.add_edge(2,3,4)
    g.add_edge(2,4,5)
    g.add_edge(3,4,6)
 
    print('<vtx_list>')
    for v in g.vtx_list[1:]:
        print(v)
    
    dist = dijkstra(g, src)
    print('\n<result>')
    for i in dist:
        print(i)
 
main()
cs



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from random import random

def qsort(arr, n):
    _qsort(arr,0,n-1)

def _qsort(arr,first,last):
    if not first<last:
        return

    kl, kr = partition_first(arr,first,last)
    _qsort(arr,first,kl)
    _qsort(arr,kr,last)

def partition_first(arr,first,last):
    pivot = arr[first]
    i = first+1
    j = last

    while True:
        while not (arr[i] > pivot or i==j):
            i += 1
        while not (arr[j] < pivot or j<i):
            j -= 1

        if i>=j:
            break

        arr[i], arr[j] = arr[j], arr[i]
        i += 1
        j -= 1

    arr[first], arr[j] = arr[j], arr[first]
    return j-1, j+1

def partition_last(arr,first,last):
    pivot = arr[last]
    i = first
    j = last-1

    while True:
        while not (arr[i] > pivot or i>j):
            i += 1
        while not (arr[j] < pivot or j==i):
            j -= 1

        if i>=j:
            break

        arr[i], arr[j] = arr[j], arr[i]
        i += 1
        j -= 1

    arr[i], arr[last] = arr[last], arr[i]
    return i-1, i+1

if __name__ == '__main__':
    for i in range(10000):
        arr1 = [int(random()*10for i in range(100)]
        arr2 = list(arr1)

        qsort(arr1, len(arr1))
        arr2.sort()

        assert arr1 == arr2
    print('done')
cs


quick sort 함수 코드와, 중복이 있는 리스트로 함수를 검증하는 코드이다
quick sort는 크게 2번의 스텝으로 나뉘어진다

1. partition
2. recursion

partition 스텝에서는 리스트를 pivot 기준으로 less group / equal group / more group으로 정렬한다
recursion 스텝에서는 recursion을 통해 less group과 more group을 정렬한다

partition의 구현은 pivot이 first / center / last인 경우 3가지가 있다
위 코드에는 first인 경우, last인 경우 두 가지의 코드가 있다
pivot이 center인 경우, 3-way partition을 하려면 코드가 괜히 길어져서 제외했다

---

<구현과정>
구현을 할때, [5, 4, 3, 2, 1]과 [1, 2, 3, 4, 5] 2개의 테스트 케이스를 기준으로 하면 쉽다
수행과정을 생각해보면 loop의 조건이 명확해지기 때문이다
(작은 의미의 TDD:Test-Driven-Development?)



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

Graph algorithm에서 나오는 용어들이다


MST란 Minimum Spanning Tree(최소 신장 트리)인데, 모든 Vertex가 연결되어있고 Edge의 갯수가 Minimum인 Tree이다


Prim's 알고리즘이란 Graph에서 MST를 찾기 위한 알고리즘 중 하나이다



Time complexity는 Graph의 implementation에 따라 바뀌는데, prim's algorithm wikipedia를 인용하면

AdjMatrix일 경우 V^2

BinHeap + AdjList일 경우 O(ElogV) (= O((V+E)logV) )

등이 된다


시간 계산법은 기회가 되면 나중에 추가하도록 하겠다


https://en.wikipedia.org/wiki/Prim%27s_algorithm



WRITTEN BY
hojongs
블로그 옮겼습니다 https://hojongs.github.io/

사용중인 블로그 스킨이 방명록을 지원하지 않습니다.

초대장을 받고싶으신 분들은 댓글 또는 블로그 설명의 메일로.