Python 코딩테스트 21~30
포스트
취소

Python 코딩테스트 21~30

파이썬 코딩테스트 준비를 하며 공부한 내용을 기록하고 복습합니다.

코딩테스트 참고자료

파이썬 코딩테스트

문제21

USACO 2018 January Contest, Bronze Problem 2. Lifeguards

인명구조원을 하나씩 제거한 모든 시간표를 구해 커버 되는 최대 시간을 찾는다.

  • 커버되는 시간대의 개수를 세면 된다
    • 중간에 시간이 끊겨도 해당 시간대는 포함됨
  • 모든 시간표의 조합을 생성해 내는 것이 핵심 (이후 최대값 출력)
    • 완전(전체) 탐색 알고리즘

내가 제출한 코드

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
lines = [
    '3','5 9','1 4','3 7'
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

def read_data():
    n = int(input())
    data = []
    for i in range(n):
        data.append( list(map(int, input().split())) )
    return data

data = read_data()
# print(sorted(data, key=lambda v: (v[0], v[1])))

def calc_covered_time(data):
    times = set()
    for i,j in sorted(data, key=lambda v: (v[0], v[1])):
        times.update(range(i,j))
    return len(times)

def max_covered_time(data):
    max_time = 0
    for i in range(len(data)):
        sliced = data[:i] + data[i+1:]
        covered_time = calc_covered_time(sliced)
        print(sliced, '==>', covered_time)
        if covered_time > max_time:
            max_time = covered_time
    return max_time

print( max_covered_time(data) )
## 결과
# [[1, 4], [3, 7]] ==> 6
# [[5, 9], [3, 7]] ==> 6
# [[5, 9], [1, 4]] ==> 7
# 7

문제22

USACO 2014 January Contest, Bronze Problem 1. Ski Course Design

비용함수를 구현하고 탐색범위를 제한하는 것이 핵심

내가 제출한 코드

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
lines = [
    '5',
    '20','4','1','24','21'
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

def read_data():
    n = int(input())
    data = []
    for i in range(n):
        data.append( int(input()) )
    return data

heights = read_data()
min_h, max_h = min(heights), max(heights)
ALLOWED_HEIGHT = 17
print(f'heights={heights}')

def calc_cost(heights, min_h, max_h):
    """
    주의!
    언덕의 높이가 min_h, max_h 사이에 들어가는 경우는 비용을 계산하지 않는다
    """
    cost = 0
    targets = [ x for x in heights if x < min_h or x > max_h ]
    for h in targets:
        diff = min( abs(h-min_h), abs(max_h-h) )
        cost += diff ** 2
        # print(h, diff, cost)
    return cost

def find_min_cost(heights):
    min_h, max_h = min(heights), max(heights)
    if max_h - min_h <= ALLOWED_HEIGHT:
        return 0

    # 허용치 17을 만족하기 위한 height 차이 (탐색범위)
    diff = (max_h - min_h) - ALLOWED_HEIGHT
    # 가장 많이 깍게 되는 경우를 최대치로 설정하고
    min_pair = min_h+diff, max_h-diff
    min_cost = calc_cost(heights, *min_pair)
    # min_h 를 시작으로 diff 차이만큼 구간을 옮기며 cost 계산
    for h in range(min_h, min_h+diff):
        cost = calc_cost(heights, h, h+ALLOWED_HEIGHT)
        # print(h, h+ALLOWED_HEIGHT, '==>', cost)
        if cost < min_cost:
            min_cost = cost
            min_pair = h, h+ALLOWED_HEIGHT
    return min_cost, min_pair

print( find_min_cost(heights) )
## 결과
# [20, 4, 1, 24, 21]
# (18, (4, 21))

문제23

USACO 2013 December Contest, Bronze Problem 2. Cow Baseball

나는 combinations 를 사용했는데, 책에서는 bisect 로 탐색을 수행했다

  • from itertools import combinations
    • 조합을 구한 후 정렬하고, 조합의 거리값으로 조건을 비교한다
  • from bisect import bisect_left, bisect_right
    • 배열이 정렬되어 있어야 하고, 찾는 값이 배열 내에 존재해야 한다

내가 제출한 코드

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
lines = [
    # '5','3','1','10','7','4'
    '7','16','14','23','18','1','6','11'
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

def read_data():
    n = int(input())
    data = []
    for i in range(n):
        data.append( int(input()) )
    return data

positions = read_data()
positions.sort()
print(f'positions={positions}')

from itertools import combinations
def find_xyz(positions):
    results = []
    for tp in combinations(positions,3):
        xyz = sorted(tp)
        dist_xy = xyz[1]-xyz[0]
        dist_yz = xyz[2]-xyz[1]
        if dist_xy <= dist_yz <= dist_xy*2:
            results.append(xyz)
    return results

tuples = find_xyz(positions)
print( '==>', tuples )
print( len(tuples) )

## 결과
# positions=[1, 3, 4, 7, 10]
# [[1, 3, 7], [1, 4, 7], [1, 4, 10], [4, 7, 10]]
# 4
# positions=[1, 6, 11, 14, 16, 18, 23]
# ==> [[1, 6, 11], [1, 6, 14], [1, 6, 16], [1, 11, 23], [6, 11, 16], [6, 11, 18], [6, 14, 23], [11, 14, 18], [11, 16, 23], [14, 16, 18], [14, 18, 23]]
# 11

다른 제출자 코드

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
from bisect import bisect_left, bisect_right
lines = [
    # '5','3','1','10','7','4'
    '7','16','14','23','18','1','6','11'
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

def read_data():
    n = int(input())
    data = []
    for i in range(n):
        data.append( int(input()) )
    return data

positions = read_data()
positions.sort()
print(f'positions={positions}')

n = len(positions)
total = 0
for i in range(n):
    for j in range(i+1, n):
        xy_diff = positions[j] - positions[i]
        low_pos = positions[j] + xy_diff
        high_pos = positions[j] + xy_diff*2
        left = bisect_left(positions, low_pos)
        right = bisect_right(positions, high_pos)
        if right-left > 0:
            print(f'x({positions[i]}), y({positions[j]})에 대해 거리값 {low_pos}~{high_pos} 범위에 해당하는 아이템 개수 ==> {right-left} (인덱스 {left}:{right})')
        total += right - left

print(total)

## 결과
# positions=[1, 6, 11, 14, 16, 18, 23]
# x(1), y(6)에 대해 거리값 11~16 범위에 해당하는 아이템 개수 ==> 3 (인덱스 2:5)
# x(1), y(11)에 대해 거리값 21~31 범위에 해당하는 아이템 개수 ==> 1 (인덱스 6:7)
# x(6), y(11)에 대해 거리값 16~21 범위에 해당하는 아이템 개수 ==> 2 (인덱스 4:6)
# x(6), y(14)에 대해 거리값 22~30 범위에 해당하는 아이템 개수 ==> 1 (인덱스 6:7)
# x(11), y(14)에 대해 거리값 17~20 범위에 해당하는 아이템 개수 ==> 1 (인덱스 5:6)
# x(11), y(16)에 대해 거리값 21~26 범위에 해당하는 아이템 개수 ==> 1 (인덱스 6:7)
# x(14), y(16)에 대해 거리값 18~20 범위에 해당하는 아이템 개수 ==> 1 (인덱스 5:6)
# x(14), y(18)에 대해 거리값 22~26 범위에 해당하는 아이템 개수 ==> 1 (인덱스 6:7)
# 11

# [
#   [1, 6, 11], [1, 6, 14], [1, 6, 16],
#   [1, 11, 23],
#   [6, 11, 16], [6, 11, 18],
#   [6, 14, 23],
#   [11, 14, 18],
#   [11, 16, 23],
#   [14, 16, 18],
#   [14, 18, 23]
# ]

문제24

DMOPC ‘20 Contest 2 P2 - Lousy Christmas Presents

내가 제출한 코드

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
lines = [
    '4 2','1 2 3 4','1 3','2 3'  # --> 3
    # '3 2','1 2 3','2 5','1 1'  # --> 1
    # '3 1','1 2 3','3 2'  # --> 0
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

def read_data():
    # n 스카프 길이, m 친척 숫자
    n, m = list(map(int, input().split()))
    # 스카프 색상
    scarf = list(map(int, input().split()))
    # 친척이 원하는 색상 (처음,끝)
    data = []
    for i in range(m):
        data.append( list(map(int, input().split())) )
    return n, m, scarf, data

n, m, scarf, wants = read_data(lines)
print(f'scarf_size={n}')
print(f'n_wants={m}')
print(f'scarf={scarf}')
print(f'wants={wants}')

def find_index(arr, value):
    for i, v in enumerate(arr):
        if v == value:
            yield i

def find_part(scarf, head, tail):
    longest_size = 0
    heads = [ i for i in find_index(scarf, head) ]
    for i in heads:
        tails = [ j for j in find_index(scarf, tail) if j >= i ]
        # [1:1] 도 한칸이기 때문에 +1을 해야 한다
        if tails:
            size = max(tails)-i+1
            if size > longest_size:
                print(scarf[i], scarf[max(tails)], '==>', size)
                longest_size = size
    return longest_size

longest_size = 0
for w in wants:
    size = find_part(scarf, w[0], w[1])
    longest_size = size if size > longest_size else longest_size

print(longest_size)

## 결과
# scarf_size=4
# n_wants=2
# scarf=[1, 2, 3, 4]
# wants=[[1, 3], [2, 3]]
# 1 3 ==> 3
# 2 3 ==> 2
# 3

다른 제출자 코드

1

문제25

DMOPC ‘17 Contest 4 P1 - Ribbon Colouring Fun

문제에 Subtask #N 이라는 항목으로 테스트 배점이 표시되어 있다.
(Constraints) N,Q <= 1000 가 40점, N,Q <= 1000000 가 60점

  • 내가 제출한 코드는 TLE [>2.000s, 34.38 MB] 판정받아 40점 밖에 못받음
    • 내 코드는 set 을 이용해 겹쳐지는 부분을 제거했고
  • 반면에 책 정답은 AC [0.170s, 21.05 MB] 로 100점을 받음
    • 책 코드는 rightmost_position 으로 색칠된 영역을 유지하는 방식으로 했다

내가 제출한 코드

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
lines = [
    '20 4','18 19','4 16','4 14','5 12'
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

def read_data():
    n, q = list(map(int, input().split()))
    data = []
    for i in range(q):
        data.append( list(map(int, input().split())) )
    return n, data

n, strokes = read_data()
strokes.sort()  # key=lambda x: (x[0], x[1]))
print(f'N={n}, strokes={strokes}')

painted = set()
left, right = n, 0
for s in strokes:
    painted.update(range(s[0],s[1]))

print(painted)
print(n-len(painted), len(painted))

## 결과
# N=20, strokes=[[4, 14], [4, 16], [5, 12], [18, 19]]
# {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18}
# 7 13

다른 제출자 코드

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
lines = [
    '20 4','18 19','4 16','4 14','5 12'
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

lst = input().split()
n, q = int(lst[0]), int(lst[1])

strokes = []
for i in range(q):
    stroke = input().split()
    strokes.append( [int(stroke[0]), int(stroke[1])])
strokes.sort()

rightmost_position = 0
blue = 0

for stroke in strokes:
    stroke_start = stroke[0]
    stroke_end = stroke[1]
    if stroke_start <= rightmost_position:
        if stroke_end > rightmost_position:
            blue += stroke_end - rightmost_position
            rightmost_position = stroke_end
    else:
        blue += stroke_end - stroke_start
        rightmost_position = stroke_end

print(n - blue, blue)

문제 26

(백준) 토마토가 모두 익을 때까지의 최소 날짜 구하기

익은 토마토를 중심으로 상하좌우의 인접 토마토가 익는데 1일이 소요된다.
모든 토마토가 익을 때까지 며칠이 소요되는지 구하라.
(단, 비어있는 셀은 -1로 표시되고 모두 익을 수 없는 경우에는 -1을 출력)

  • 시간 복잡도가 있는 문제라서, 결과가 맞아도 점수를 받지 못함
  • 탐색의 문제라 deque 와 bfs 탐색전략을 사용해야 올바르다

내가 제출한 코드

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
m, n = 6, 4
# days = 8
box = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
# days = -1 (토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다)
#box = [0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
# days = 6
#box = [1, -1, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, 1]


def get_adjacent(m, n, idx) -> list:
    indices = [
        None if idx < m else idx - m,       # up
        None if idx > m*(n-1) else idx + m, # down
        None if idx%m == 0 else idx - 1,    # left
        None if idx%m == m-1 else idx + 1,  # right
    ]
    # return list(filter(None, indices))
    # box 총 크기를 벗어나는 숫자(m*n)가 발생하지만 필터링으로 제외
    return list(filter(lambda x: x != None and 0 <= x < m*n, indices))


# 익지 않은 토마토 주변에 인접한 공간이 없는 상태가 발견되면
# 모두 익을 수 없는 상태로 판단하고 중단시키기
def exists_impossible(box, m, n) -> bool:
    unripen_indices = [ i for i, x in enumerate(box) if x == 0 ]
    for i in unripen_indices:
        adjacent_indices = [ x for x in get_adjacent(m, n, i) if box[x] >= 0 ]
        # print(i, adjacent_indices)
        if not adjacent_indices:
            return True
    return False


def become_ripe(box, m, n):

    def find_unripen(ripen_indices) -> list:
        unripen_indices = set()
        for i in ripen_indices:
            indices = [ x for x in get_adjacent(m, n, i) if box[x] == 0 ]
            unripen_indices.update(indices)
        return sorted(unripen_indices)

    ripen_indices = [ i for i, x in enumerate(box) if x > 0 ]
    # print('- ripen_indices:', ripen_indices)
    # 안익은 토마토 위치 찾아내기
    unripen_indices = find_unripen(ripen_indices)
    # print('- unripen_indices:', unripen_indices)
    # 익은 토마토로 상태 변경
    for i in unripen_indices:
        box[i] = 1
    return len(unripen_indices)


def main(box, m, n):
    # 초기 상태
    print(box)

    # 익을 수 없는 상태가 존재하는지 체크 (한번만 실행)
    if exists_impossible(box, m, n):
        print('-1')
        return

    days = 0
    while box.count(0) > 0:
        days += 1
        # print('unripen remains:', box.count(0))
        ripen_cnt = become_ripe(box, m, n)
        print(f'day {days}: {ripen_cnt} items became ripe')
        print(box)

    print(days)


main(box, m, n)

출력결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[1, -1, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, -1, 1]
day 1: 2 items became ripe
[1, -1, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, -1, 1, 0, 0, 0, 0, -1, 1]
day 2: 2 items became ripe
[1, -1, 0, 0, 0, 0, 1, -1, 0, 0, 0, 1, 1, 0, 0, 0, -1, 1, 0, 0, 0, 0, -1, 1]
day 3: 4 items became ripe
[1, -1, 0, 0, 0, 1, 1, -1, 0, 0, 1, 1, 1, 1, 0, 0, -1, 1, 1, 0, 0, 0, -1, 1]
day 4: 4 items became ripe
[1, -1, 0, 0, 1, 1, 1, -1, 0, 1, 1, 1, 1, 1, 1, 0, -1, 1, 1, 1, 0, 0, -1, 1]
day 5: 4 items became ripe
[1, -1, 0, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 0, -1, 1]
day 6: 2 items became ripe
[1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1]
6

다른 제출자 코드

출처: [백준] 7576: 토마토 (파이썬 / 해설포함)

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
# bfs 특 queue 사용하기
# deque 모듈 안쓰면 시간복잡도 박살남(pop(0)이 시간복잡도가 O(n)이고 popleft()가 O(1)이라고 함)
from collections import deque

m, n = map(int, input().split())
# 토마토 받아서 넣기. 이차원 리스트로 만들어질거.
matrix = [list(map(int, input().split())) for _ in range(n)]
# 좌표를 넣을거니까 []를 넣자.
queue = deque([])
# 방향 리스트. [dx[0], dy[0]]은 곧 [-1, 0]이고 이는 왼쪽으로 이동하는 위치이다.
# 그려보면 이해하기 편함
dx, dy = [-1, 1, 0, 0], [0, 0, -1, 1]
# 정답이 담길 변수
res = 0

# queue에 처음에 받은 토마토의 위치 좌표를 append 시킨다
# n과 m을 사용하는걸 헷갈리지 말아야 함!
for i in range(n):
    for j in range(m):
        if matrix[i][j] == 1:
            queue.append([i, j])

# bfs 함수. 한번 들어가면 다 돌고 나오니까 재귀 할 필요 없음
def bfs():
    while queue:
        # 처음 토마토 좌표 x, y에 꺼내고
        x, y = queue.popleft()
        # 처음 토마토 사분면의 익힐 토마토들을 찾아본다.
        for i in range(4):
            nx, ny = dx[i] + x, dy[i] + y
            # 해당 좌표가 좌표 크기를 넘어가면 안되고, 그 좌표에 토마토가 익지 않은채로 있어야 함(0).
            if 0 <= nx < n and 0 <= ny < m and matrix[nx][ny] == 0:
                # 익히고 1을 더해주면서 횟수를 세어주기
                # 여기서 나온 제일 큰 값이 정답이 될 것임
                matrix[nx][ny] = matrix[x][y] + 1
                queue.append([nx, ny])

bfs()
for i in matrix:
    for j in i:
        # 다 찾아봤는데 토마토를 익히지 못했다면 -1 출력
        if j == 0:
            print(-1)
            exit(0)
    # 다 익혔다면 최댓값이 정답
    res = max(res, max(i))

# 처음 시작을 1로 표현했으니 1을 빼준다.
print(res - 1)

문제27

(백준) 1로 만들기

정수 X에 사용할 수 있는 연산은 다음과 같이 세 가지 이다. (동적계획법)

  • X가 3으로 나누어 떨어지면, 3으로 나눈다.
  • X가 2로 나누어 떨어지면, 2로 나눈다.
  • 1을 뺀다.

내가 제출한 코드

1

다른 제출자 코드 (상향식)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
lines = [
    '10'  # --> 3
    #'2'  # --> 1
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

n = int(input())

# dp 는 연산 횟수를 카운팅
dp = [0] * (n+1)
# i 는 연산 대상인 값 (아래 +1 들은 연산 회수 증가를 뜻함)
for i in range(2, n+1):
    dp[i] = dp[i-1] + 1    # -1 연산
    if i % 2 == 0:
        dp[i] = min(dp[i], dp[i//2] + 1)    # //2 연산
    if i % 3 == 0:
        dp[i] = min(dp[i], dp[i//3] + 1)    # //3 연산

print(dp[n])

문제28

코딩테스트 깊이우선탐색(DFS)

DFS의 핵심은 방문하자마자 visited를 체크하는 것이다. 인접행렬로 푼다.

  • 정점의 인덱스가 1~V 까지라서 V+1 을 사용한다 (0은 안씀)
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
lines = [
    # 정점(V)과 간선수(E), 정점간 연결(v-w)
    '7 8', '1 2 1 3 2 4 2 5 4 6 5 6 6 7 3 7'
]
from functools import partial
input = partial(lambda x: next(x), iter(lines))

V, E = list(map(int, input().split()))
temp = list(map(int, input().split()))

# 재귀호출로 찾을 때까지 탐색
def dfs(v):
    visited[v] = 1
    print(v, end=" ")

    for w in range(V+1):
        # 간선으로 연결되어 있고, 방문을 안했다면 dfs!
        if G[v][w] == 1 and visited[w] == 0:
            dfs(w)

#간선을 넣는 곳이다(2차원으로 바꿔주자.)
G = [ [0 for _ in range(V+1)] for _ in range(V+1) ]

# 방문을 체크해 주는 곳이다.
visited = [ 0 for _ in range(V+1) ]

# 1 2 가 들어오면 1, 2와 2, 1을 둘다 체크한다
for i in range(0, len(temp)-1):
    G[ temp[i] ][ temp[i+1] ] = 1
    G[ temp[i+1] ][ temp[i] ] = 1

# 1에서 시작하기 때문에 1을 대입하자
dfs(1)

## 결과
# 1 2 3 7 6 4 5

문제 29

이진탐색 트리 - BFS

BFS 클래스에 DFS 함수를 재귀호출, 반복호출 방식으로 구현해보았다.

  • DFS 루프 방식을 구현할 때는 스택을 사용해야 한다.
  • 초보자들이 실수하는 가장 큰것이 visited 배열을 안만드는 것이다. 반드시 만들어 주자.
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
# 노드 생성
class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

# 노드 삽입
class BST:
    def __init__(self, root):
        self.root = root

    def insert(self, value):
        # 현재 위치를 root로 설정
        self.current_node = self.root
        while True:
            if value < self.current_node.value:
                # 왼쪽 노드 처리 : 없으면 생성, 있으면 이동 후 반복
                if self.current_node.left != None:
                    self.current_node = self.current_node.left
                else:
                    self.current_node.left = Node(value)
                    break
            else:
                # 오른쪽 노드 처리 : 없으면 생성, 있으면 이동 후 반복
                if self.current_node.right != None:
                    self.current_node = self.current_node.right
                else:
                    self.current_node.right = Node(value)
                    break

    # 노드 탐색
    def search(self, value):
        # 현재 위치를 root로 설정
        self.current_node = self.root
        # 현재 위치를 계속 확인
        while self.current_node:
            # 현재 위치에서 찾으면 반환
            if self.current_node.value == value:
                return True
            # 현재 노드값이 더 크면 왼쪽으로 이동
            elif self.current_node.value > value:
                self.current_node = self.current_node.left
            # 현재 노드값이 더 작으면 오른쪽으로 이동
            else:
                self.current_node = self.current_node.right
        return False

    # 왼쪽 호출, 없으면 오른쪽 호출
    def dfs_recursive(self, value):
        def _dfs(curr):
            print(curr.value)
            if curr.value == value:
                return True
            if curr.left != None:
                return _dfs(curr.left)
            if curr.right != None:
                return _dfs(curr.right)
            return False
        # root로부터 재귀호출 탐색
        return _dfs(self.root)

    # stack 을 사용한다
    def dfs_iteration(self, value):
        # visited = 방문한 꼭지점들을 기록한 리스트
        visited = []
        # dfs는 stack, bfs는 queue개념을 이용한다.
        stack = [ self.root ]
        while stack:
            curr = stack.pop()
            if curr == None: continue
            print(curr.value)
            if curr.value == value:
                return True
            #현재 node가 방문한 적 없다 -> visited에 추가한다.
            #그리고 해당 node의 자식 node들을 stack에 추가한다.
            if curr not in visited:
                visited.append(curr)
                stack.extend([curr.right, curr.left])
        return False

root = Node(5)
bst = BST(root)
bst.insert(1)
bst.insert(2)
bst.insert(7)
bst.insert(4)
bst.insert(8)
bst.insert(10)

print( bst.dfs_iteration(4))

# print(bst.search(2))   # True
# print(bst.search(5))   # False
# print(bst.search(7))   # True
# print(bst.search(8))   # True
# print(bst.search(15))  # False

반복문 형태의 DFS 탐색

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
graph = {'A':['B','C'],
         'B':['A','D','E'],
         'C':['A','G','H'],
         'D':['B'],
         'E':['B','F'],
         'F':['E'],
         'G':['C'],
         'H':['C']}

def dfs_iteration(graph, start):
    # 다음 이동(파생) 위치를 유지하는 stack 과
    stack = [start]
    # 이미 지나온 경로를 유지하는 visited 가 있어야 함
    visited = []
    # 최초 위치로부터 더 이상 이동할 곳이 없을 때까지
    while stack:
        # 현재 위치를 꺼내고 (선입후출)
        # 만약 queue 를 사용하면 너비우선(BFS) 탐색이 됨
        curr = stack.pop()
        # 아직 탐색되지 않은 곳이면
        if curr not in visited:
            # 탐색 기록을 하고 (추가적으로 할 작업 있으면 여기서)
            visited.append(curr)
            # 다음 이동 경로를 추가
            stack.extend(graph[curr])
    return visited

dfs_iteration(graph,'A')
# ==> ['A', 'C', 'H', 'G', 'B', 'E', 'F', 'D']

반복문 형태의 너비 우선 탐색(BFS)

dequelist 처럼 queue[ idx ] 로 원소를 조회할 수 있음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from collections import deque

def bfs_iteration(graph, start):
    # 초기값 (생성자 이용)
    queue = deque( [start] )
    visited = []
    while queue:
        curr = queue.popleft()
        if curr not in visited:
            visited.append(curr)
            queue.extend(graph[curr])
    return visited

print( bfs_iteration(graph, 'A') )
# ==> ['A', 'B', 'C', 'D', 'E', 'G', 'H', 'F']

반복문 형태의 binary search

1
2
3
4
5
6
7
8
9
10
11
12
def binary_search(array, target, start, end):
    while start <= end:
        mid = (start + end) // 2
        if array[mid] == target:
            return mid
        elif array[mid] > target:
            end = mid -1
        else:
            start = mid +1
    return None

result = binary_search(array, target, 0, n - 1)

문제 30

코딩테스트 - 정렬 알고리즘

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
array = [2, 3, 1, 4]

# 가장 작은 값과 SWAP (이중루프)
def selection_sort(array, reverse=False):
    for i in range(len(array)):
        min_index = i
        for j in range(i+1, len(array)):
            if array[j] < array[min_index]:
                min_index = j
                array[i], array[min_index] = array[min_index], array[i]
    return array

# pivot 값을 중심으로 나머지(tail) 값들을 작은값, 큰값으로 나누어 재귀 처리
def quick_sort(array):
    if len(array) <= 1:
        return array

    pivot = array[0] # first element as pivot
    tail = array[1:] # list accept pivot
    left_side = [x for x in tail if x <= pivot] # left side
    right_side = [x for x in tail if x > pivot] # right side
    return quick_sort(left_side) + [pivot] + quick_sort(right_side)

print( selection_sort(array[:]) )
print( quick_sort(array[:]) )

 
 

끝!   읽어주셔서 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.