티스토리 뷰
배열을 배우면서 느꼈지만, 문제들이 노가다와 창의력을 요하는 문제들이 나오곤 한다. 일일이 다 확인해서 코드를 짤 수도 있고, 신박한 방법으로 짧게 코드를 구현할 수도 있을 것이다. 아직 직감이 와서 문제를 푸는 단계는 아니기 때문에 최대한 정석으로 풀곤하는데, 정석으로 풀면서도 시뮬레이션하며 경우의 수를 따질 때가 많았다. 그러면 어떻게 문제에 접근하는지 알아보자.
잠깐. 아래 내용을 설명하기 위해 예시에 대한 문제를 먼저 설명하고 가는 것이 좋을 것 같다.
Baby-gin Game
원래는 일반 트럼프 카드로 게임을 진행하는데, 쉬운 알고리즘 문제로 바꾸자면 중복이 가능한 0부터 9까지의 숫자 카드 중 임의의 6장의 패를 가지고 있을 때, 숫자가 연속된 3개(run)거나, 같은 숫자 3개(triple)로만 구성 되었을 때 Baby-gin 이라고 할 수 있다. 마치 루미큐브의 첫 등록을 보는 듯한 느낌, 예를들어 임의의 6개를 뽑았을 때,
1, 1, 1, 2, 3, 4 = triple + run = Baby-gin
1, 4, 2, 2, 3, 2 = only triple = not Baby-gin
이다.
그렇다면 위의 방식으로 baby-gin 여부를 판단하는 알고리즘을 어떻게 만들면 될까?
완전 탐색(Exausive Search)
: 문제의 답으로 생각 되는 모든 경우의 수를 확인하는 기법이다. 즉, 모든 경우의 수를 테스트 한 다음 최종 해법을 도출하는 방식이다.
- Brute-force 혹은 generate-and-test 기법이라고도 불린다.
- 일반적으로 경우의 수가 적을 때 사용한다.
- 모든 경우를 테스트 하기 때문에 수행 시간은 길지만, 틀릴 확률/답을 찾지 못할 확률은 낮은 편이다.
- 문제를 풀 때, 완전 탐색을 통해 해답을 도출한 뒤에, 성능 개선을 하는 것이 바람직하다.
Baby-gin 을 통한 예시
[1] 고려할 수 있는 모든 경우의 수 생성
임의의 숫자 6개를 받았을 경우 모든 숫자를 나열한다. (이때 중복도 포함)
예를들어 [1, 3, 5, 9, 9, 9]를 받았다면 모든 경우를 나열해본다. (순열)
*순열 (Permutation)
- 서로 다른 것들 중 뽑아서 한 줄로 나열하는 것
- 서로 다른 n개 중 r개를 선택하는 순열은 nPr로 표현할 수 있다. (중등 수학할 때 경우의 수 파트에서 지겹게 본거같다)
- nPr = n * (n - 1) * (n - 2) * ... * (n - r + 1)
- n과 r이 같다면[모든 수가 다를 경우] nPn = n! (Factorial, 팩토리얼)로 표현하기도 한다.
[2] 모든 해답 테스트 하기
앞서 설명한 것처럼 baby-gin 은 연속된 숫자 run 혹은 같은 숫자 세개 triple인지 확인하고 이 중 두개가 해당한다면 baby-gin임을 확인 할 수 있다.
예시를 들었던 [1, 3, 5, 9, 9, 9] 에서는 triple은 존재하나 이외 run 혹은 triple은 존재하지 않기 때문에 baby-gin은 아님을 알 수 있다. 결과적으로 나올 수 있는 경우의 수를 모두 살펴보았으나, baby-gin은 되는 것이 없다는 결론에 도달 할 수 있다.
Greedy(탐욕) 알고리즘
: 최적해를 구하는데 지금 당장 좋아보이는 방법을 선택하는 탐욕적인 방법론이다. 그럼 좋은 것이 아닐까? 아쉽게도 아닐 경우가 많다. 알고리즘을 풀다보면 직감적으로 이러면 되지 않을까? 라며 접근을 한다면 그것이 바로 탐욕 알고리즘. 하지만 이후 주어진 문제를 단기적으로 쳐낸다고 생각하다가 전체 코드를 실행 시켰을 때 오류가 날 확률이 높다. baby-gin을 가장 먼저 접했을 때, 생각이 든 방법은 6개를 sort한 다음, 앞3개 뒤 3개를 나누어 판단하는 알고리즘을 만들었다. 이는 run과 triple이 섞일 수 있다는 것을 간과했다. 반례는 [1, 2, 2, 2, 2, 3] 가 있다. 이 경우 앞을 자르면 안된다.
Baby-gin 을 통한 예시
[1] 해 선택
현재 상태에서의 문제에 대한 최적 해를 구한뒤 이를 해 집합(Solution Set)에 추가한다.
6개의 데이터를 counts 배열로 만들어 먼저 run을 만족하는지 조사 후에 triple 혹은 run을 한번 더 검사한다
[2] 실행 가능성 검사
새로운 부분해 집합이 실행 가능한지 확인하며 문제의 제약 조건을 위반하지 않는지 검사한다.
다른 경우에서 앞선 문제 없이 진행가능한지 확인한다.
[3] 해 검사
새로운 부분해 집합이 문제의 해가 되는지 확인한다. 만약 전체의 해가 완성되지 않았다면(예외가 있다면) [1]으로 돌아가 다시 해 선택부터 다시 시작한다.
이제 코드로 작성해보자
정확한 설명을 보여주기 위해서 효율적이진 않지만 모든 반복을 하도록 만들었다. 주의해야할 점은 cnt_lst를 10개가 아닌 12개를 만들어주어야 한다는점, 물론 무조건 10개가 아니어도 되지만 범위지정 제대로 하지 않는다면 out of range를 보고있는 나를 마주할 수 있을 것이다.
이렇게 완전 탐색과 그리디 알고리즘에 대해 알아보았는데, 나는 그리디 탐색...형인 것 같다. 문제를 쭉 읽고 이렇게 하면 되지 않을까? 라며 자신있게 시작하지만 항상 되돌아 온다.. 다만 쉬운 문제를 오래 걸린 적도 있고 어려운 문제를 쉽게 푼적도 있긴하다.
내 목표는 내가 원하는 형태의 알고리즘을 구현하는 것이다. 답을 틀리더라도 이후 내가 원하는 방향으로 잘 짜여진다면, 적어도 방향만 잡으면 도달할 수 있다는게 아닐까?
오늘도 화이팅하자.
'일상코딩 > 노트' 카테고리의 다른 글
Python : 재귀호출, Memoization, Dynamic Programming (0) | 2024.02.11 |
---|---|
Python : Stack (1) (2) | 2024.02.07 |
Python : 1차원 배열 (정렬[Bubble Sort, Counting Sort]) (0) | 2024.01.30 |
Python Functions(2) [재귀함수, 유용한 내장함수] (1) | 2024.01.27 |
Python Method (딕셔너리 Method) (0) | 2024.01.26 |
- Total
- Today
- Yesterday
- dfs
- 카운팅정렬
- Database
- Sequence types
- SQLite
- ChatGPT
- 순열
- Django
- 연산자
- Method
- CodeTree
- vue3
- SQL
- Python
- baby-gin
- vue
- 재귀
- honeymoney
- app
- JavaScript
- 삼성청년SW아카데미
- views.py
- refactoring
- Component
- 백준
- 함수
- Authentication System
- HTML
- basic syntax
- ssafy
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |