알고리즘

Insertion vs Merge vs Heap vs Quick

상명대학교 컴퓨터과학과 동아리 파느쎄 스터디할때 찍어놨던 유물

상명대학교 컴퓨터과학과 동아리 파느쎄 스터디할때 찍어놨던 유물

DFS (깊이우선탐색)

모든 노드를 방문할때 이방법을 선택

  • 최대한 깊이 내려간 뒤 더이상 깊이 갈 곳이 없을 경우 옆으로 이동
  • 다른 노드로 넘어가기 전에 해당 깊이를 완벽하게 탐색
  • 경로의 특징을 저장해야 하는 문제에서 유리
  • 스택 혹은 재귀함수 이용
DFS
1
2
3
4
5
6
7
8
9
# DFS 메서드 정의
def dfs(graph, v, visited):
    # 현재 노드를 방문 처리
    visited[v] = True
    print(v, end = '')
    # 현재 노드와 연결된 다른 노드를 재귀적으로 방문
    for i in graph[v]:
    	if not visited[i]:
        	dfs(graph, i, visited)

BFS (너비우선탐색)

두 노드 사이의 최단 거리를 찾을 때 사용

  • 최대한 넓게 이동한 다음, 더이상 넓게 갈 수 없을때 아래로 이동
  • 미로찾기처럼 최단거리를 구해야하는 경우 유리
  • 아래 예시는 큐를 사용
BFS
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from collections import deque
# BFS 메서드 정의
def bfs(graph, start, visited):
	queue = deque([start])
    # 현재 노드를 방문 처리
    visited[start] = True
    # 큐가 빌 때까지 반복
    while queue:
    	# 큐에서 하나의 원소를 뽑아 출력
        v = deque.popleft()
        print(v, end = ' ')
        # 해당 원소와 연결된, 아직 방문하지 않은 원소들을 큐에 삽입
        for i in graph[v]:
        	if not visited[i]:
            	queue.append(i)
                visited[i] = True

GREEDY (탐욕법)

최적의 해를 찾을 수 있을 때 사용

  • 시간복잡도에서 우위를 차지할 수 있음
  • 가장 큰 순서대로, 가장 작은 순서대로, 평균값부터 접근한다고 문제에서 주어졌을 때
  • 최적의 해 (아이디어)가 생각나지 않으면 DP나 Graph로 풀어야한다

BINARY SEARCH (이분탐색)

리스트를 절반씩 나누어가며 탐색하는 알고리즘, 낮은 시간복잡도

  • 순차 탐색으로는 O(N)의 시간 복잡도라면, 이분 탐색은 O(log N)이다.
  • 리스트의 중간값 mid 의 값이 key보다 작으면 좌측은 탐색할 필요가 없으므로 left = mid + 1이된다
  • left > right 라면 리스트에 원하는 데이터가 없다
  • mid의 값이 key와 같으면 key값을 찾았기 때문에 answer = mid 탐색을 종료한다.
이분탐색
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def binary_search(target, data):
    left, right = 0, len(data) - 1
    while left <= right:
        mid = (left + right) // 2
        if data[mid] == target:
            return mid
        elif data[mid] < target:
            left = mid + 1
        else:
            right = mid -1
    return None

이분탐색 재귀
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def binary_search_recursion(target, start, end, data):
    if left > right:
        return None
    mid = (left + right) // 2
    if data[mid] == target:
        return mid
    elif data[mid] > target:
        right = mid - 1
    else:
        left = mid + 1
    return binary_search_recursion(target, left, right, data)

DIJSTRA (다익스트라)

그래프에서 노드에서 다른 노드까지 최단 경로를 구하는 알고리즘

  1. 출발 노드도착 노드를 설정한다.
  2. 최단 거리 테이블을 초기화한다.
  3. 현재 노드의 인접 노드방문하지 않은 노드를 구별하고, 방문하지 않은 노드 중 거리가 가장 짧은 노드를 택하여 방문 처리한다.
  4. 해당 노드를 거쳐 다른 노드로 넘어가는 간선 비용(가중치)을 계산해 최단 거리 테이블을 업데이트한다.
  5. 3 ~ 4 의 과정을 반복한다.
  • 최단 거리 테이블은 1차원 배열로, N개 노드까지 오는 데 필요한 최단 거리를 기록. N개(1부터 시작하는 노드 번호와 일치시키려면 N + 1개) 크기의 배열을 선언하고 큰 값을 넣어 초기화시킨다.
  • 노드 방문 여부 체크 배열은 방문한 노드인지 아닌지 기록하기 위한 배열로, 크기는 최단 거리 테이블과 같다. 기본적으로는 False로 초기화하여 방문하지 않았음을 명시한다.
  • 간선의 개수E, 노드의 개수V 일때 시간 복잡도는 O(ElogV)
DIJSTRA
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import heapq

def dikjstra(start, distance, graph):
    q = []
    # 시작노드 정보 우선순위 큐에 삽입
    heapq.heappush(q, (0, start))
    # 시작노드->시작노드 거리 기록
    distance[start] = 0
    while q:
        dist, now = heapq.heappop(q)
        # 큐에서 뽑아낸 거리가 이미 갱신된 거리보다 클 경우(=방문한 셈) 무시
        if distance[now]<dist:
            continue
        # 큐에서 뽑아낸 노드와 연결된 인접노드들 탐색
        for i in graph[now]:
            # 시작->node거리 + node->node의인접노드 거리
            cost = dist+i[1]
            # cost < 시작->node의인접노드 거리
            if cost < distance[i[0]]:
                distance[i[0]] = cost
                heapq.heappush(q, (cost, i[0]))




Python 함수

itertools

순열, 조합 사용시 사용하는 함수

combinations

  • combinations(iterable, r): iterable에서 원소 갯수가 r개인 튜플 리턴 (중복 제거)
combinations(iterable, r)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# combinations
from itertools import combinations
example = [1, 2, 3]
print(list(combinations(example, 1)))
# [(1,), (2,), (3,)]
print(list(combinations(example, 2)))
# [(1, 2), (1, 3), (2, 3)]
print(list(combinations(example, 3)))
# [(1, 2, 3)]
print(list(combinations(example, 4)))
# []

combinations_with_replacement

  • combinations_with_replacement(iterable, r): iterable에서 원소 갯수가 r개인 중복 포함 튜플 리턴
combinations_with_replacement(iterable, r)
1
2
3
4
5
6
7
# combinations_with_replacement
from itertools import combinations_with_replacement
example = [1, 2]
print(list(combinations_with_replacement(example, 2)))
# [(1, 1), (1, 2), (2, 2)]
print(list(combinations_with_replacement(example, 3)))
# [(1, 1, 1), (1, 1, 2), (1, 2, 2), (2, 2, 2)]

permutations

  • permutations(iterable, r=None) : iterable에서 원소 갯수가 r개인 순열 튜플 리턴
permutations(iterable, r=None)
1
2
3
4
5
6
7
# permutation
from itertools import permutations
example = [1, 2, 3]
print(list(permutations(example)))
# [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
print(list(permutations(example, 2)))
# [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]

product

  • product(*iterables, repeat=1) : 여러 iterable의 데카르트곱 튜플 리턴
product(*iterables, repeat=1)
1
2
3
4
5
6
7
8
9
# product
from itertools import product
number = [1, 2]
alpha = ['a', 'b']
fruit = ['apple', 'banana']
print(list(product(number, alpha)))
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
print(list(product(number, alpha, fruit)))
# [(1, 'a', 'apple'), (1, 'a', 'banana'), (1, 'b', 'apple'), (1, 'b', 'banana'), (2, 'a', 'apple'), (2, 'a', 'banana'), (2, 'b', 'apple'), (2, 'b', 'banana')]

collections

자료형(list, tuple, dict)를 이용할때 확장된 기능을 사용할 수 있는 기본 라이브러리

deque

스택이나 큐를 이용하여 알고리즘 풀때 사용하며 속도가 list 보다 빠르다. 따라서 push, pop을 많이 사용해야하는 문제에서 사용하기 적합하다.

리스트 자료형으로 구현하기 복잡한 것

  • deque.popleft(): 데크의 왼쪽 끝 원소 리턴하면서 데크에서 삭제
  • deque.rotate(num): 데크를 num만큼 이동한다 (양수면 오른쪽, 음수면 왼쪽)

리스트 자료형으로도 구현하기 쉬운 것

  • deque.append(item): item을 데크의 오른쪽 끝에 삽입 (list append)
  • deque.extend(array): 배열을 순회하면서 데크의 오른쪽에 추가 (list extend)
  • deque.extendleft(array): 배열을 순회하면서 데크의 왼쪽에 추가 (list extend)
  • deque.appendleft(item): item을 데크의 왼쪽 끝에 삽입 (list insert)
  • deque.pop(): 데크의 오른쪽 끝 원소 리턴하면서 데크에서 삭제 (list pop)
  • deque.remove(item): item을 데크에서 삭제 (list del)

Counter

  • 리스트가 주어졌을때 중복되지 않는 원소들과 갯수를 딕셔너리 형태로 리턴함
Counter
1
2
3
from collections import Counter
print(Counter(["a", "b", "c", "a", "b"]))
# Counter({'a': 2, 'b': 2, 'c': 1})

defaultdict

  • 딕셔너리값의 초기값을 지정할 수 있다. 처음 선언할때 어떤 자료형을 사용할 건지 입력받아야한다.
  • keyError 를 우회할 수 있다.
defaultdict
1
2
3
4
5
6
7
8
from collections import defaultdict
example = defaultdict(list)
print(example['list'])
# []

example = {}
print(example['list'])
# keyError 'list'

sort

sorted() vs sort()

  • sorted(X) " 원본을 변형시키지 않고 정렬된 리스트를 리턴함
  • X.sort() : 원본을 변형시켜줌, 리턴값 없음
  • Params : sortsorted 모두 동일한 파라미터를 갖을 수 있다. 파라미터는 keyreverse가 있으며 lambda와 함께 사용하요 내림차순, 오름차순, 배열의 몇번째 원소 기준으로 정렬할지 정할 수 있다.
  • 문자는 알파벳순(가나다순), 숫자는 오름차순이 기본
sort
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
example = [1, 4, 3, 2]
print(sorted(example), example)
# [1, 2, 3, 4]  [1, 4, 3, 2]
print(example.sort(), example)
# None          [1, 2, 3, 4]

# reverse 를 이용한 내림차순
print(sorted(example, reverse=True)).
# [4, 3, 2, 1]

# key 를 이용한 2차원 배열(튜플) 정렬
example = [[1, 3], [2, 2], [3, 1]]
# 첫번째 배열 원소를 이용해 정렬
print(sorted(example, key= lambda x : x[0]))
# [[1, 3], [2, 2], [3, 1]]
# 두번째 배열 원소를 이용해 정렬
print(sorted(example, key= lambda x : x[1]))
# [[3, 1], [2, 2], [1, 3]]

# key와 reverse를 동시에 이용할 수 도 있다.
print(sorted(example, key= lambda x : x[0], reverse=True))
# [[3, 1], [2, 2], [1, 3]]

zip

iterable한 자료형을 순서대로 묶어서 리턴해준다.

  • 되도록 zip할 자료형의 갯수가 같을때 사용 (같지 않을 경우 최소 길이의 자료형으로 리턴한다.)
  • 자료형은 리스트나 튜플같은 반복 가능한 자료형을 의미한다.
zip
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
print(zip([1, 2, 3], ['a','b','c']))
# <zip object at 0x100a76e48>

print(list(zip([1, 2, 3], ['a','b','c'])))
# [(1, 'a'), (2, 'b'), (3, 'c')]

print(list(zip([1, 2, 3], ['a','b'])))
# [(1, 'a'), (2, 'b')]

# zip을 이용한 for문 예시, List 형태로 변환하지 않아도 됨
example = zip([1, 2, 3], ['a','b','c'])
for number, alpha in example:
    print(number, alpha)
# 1 a
# 2 b
# 3 c

# 딕셔너리 자료형 생성 예시
print({ number:alpha for number, alpha in example })
# {1: 'a', 2: 'b', 3: 'c'}

re

정규식

  • re.sub(pattern, replacement, string) : 정규식 패턴과 일치하는 내용을 replacement로 변경
  • \uAC00-\uD7A30 : 모든 한글 음절(가-힣)
  • a-z : 영어 소문자
  • A-Z : 영어 대문자
  • 0-9 : 숫자
  • \s : 띄어쓰기
re
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import re
example = 'aㄱ1*bㄴ2@'

# 특수문자 제거
print(re.sub(r"[^\uAC00-\uD7A30-9a-zA-Z\s]", "", example))
# a1b2

# 숫자만 가져오기
print(re.sub(r"[^0-9]", "", example))
# 12

# 숫자만 제거
print(re.sub(r"[0-9]", "", example))
# aㄱ*bㄴ@

# 숫자를 `~`로 변경
print(re.sub(r"[0-9]", "~", example))
# aㄱ~*bㄴ~@

최소공배수, 최대공약수

파이썬 math 라이브러리 이용하면 쉽게 구할 수 있다

최소공배수, 최대공약수
1
2
3
4
5
6
7
8
9
# python 3.9 미만
from math import gcd
gcd(a, b)           # 최대공약수
a * b // gcd(a, b)  # 최소공약수

# python 3.9 이상
from math import gcd, lcm
gcd(a, b) # 최대공약수
lcm(a, b) # 최소공약수

진수

내장 함수 bin을 이용하면 쉽게 구할 수 있음.

최소공배수, 최대공약수
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
a = bin(4)
# a = 'ob100'
b = int(a, 2)
# b = 4

# 혹은 직접 구현할 수도 있음
def makeBin(len):
    result = []
    while len != 0:
        if len % 2 == 1:
            result.append("1")
            len = (len - 1) / 2
        else:
            result.append("0")
            len = len / 2
    return result