반응형
순열과 중복순열 알고리즘을 파이썬으로 구현하기
순열이란?
- 원소들을 순서에 따라 나열하는 것
- n개의 원소에서 r개를 나열한다 하면 그 가짓수는 n!/(n-r)!
- 중복해서 원소를 뽑으면 n^r
- 전체 순열 가짓수의 합은 조금 복잡하다. (링크)
여러가지 순열 알고리즘 구현해보기
기본 순열
n개의 원소를 가진 배열 arr에서 r개를 뽑아 나열한다.
swap을 이용한 방식
def permutations (arr, r):
n = len(arr)
results = []
def _perm_swap (arr, depth):
if (depth == r):
results.append(arr[:r])
return
for i in range(depth, n):
# 위치를 바꿈
arr[i], arr[depth] = arr[depth], arr[i]
_perm_swap(arr, depth+1)
# 다시 되돌림
arr[i], arr[depth] = arr[depth], arr[i]
_perm_swap (arr, 0)
return results
print(permutations([1,2,3], 2))
# [[1, 2], [1, 3], [2, 1], [2, 3], [3, 2], [3, 1]]
- 과정 : 0번째 인덱스 원소를 n-1번째까지 바꿔 고정한다. 그렇게 해서 나온 결과들을 다시 1번째, 2번째 인덱스에서도 하여 r번째 인덱스까지 반복한다. 인덱스가 r번째가 되면 앞의 r개만큼의 원소를 빼낸다.
- 특징 : 수열의 순서가 보장되지 않는다. 원래 배열을 건드림.
visited 배열을 이용한 방식
def permutations(arr, r):
n = len(arr)
visited = [False] * n
output = []
results = []
def _perm_visited(arr, visited, output, depth):
if (depth == r):
results.append(output[:])
return
for i in range(n):
if (visited[i]): #사용된 원소일경우 넘어감
continue
visited[i] = True
new_output = output + [arr[i]]
_perm_visited(arr, visited, new_output, depth + 1)
visited[i] = False
_perm_visited(arr, visited, output, 0)
return results
print(permutations([1, 2, 3], 2))
- 과정 : DFS를 돌며 모든 인덱스에 방문해 output에 넣음. 한번 넣은 원소의 인덱스는 visited 배열에 True로 표시해 중복을 막는다. 이 과정을 depth가 r이 될 때까지 반복한다.
- 특징 : 수열의 순서가 보장됨. 원 배열도 그대로 보장되지만 저장공간을 더 많이 사용하는 듯하다.
길이를 하나씩 늘려 재귀하는 방식
def permutation(arr, r):
result = []
if r == 0:
return [[]]
for i in range(len(arr)):
item = arr[i]
for rest in permutation(arr[:i] + arr[i+1:], r - 1):
result.append([item] + rest)
return result
print(permutation([1,2,3], 2))
Heap's algorithm을 이용한 순열
# 재귀 이용
def permutation(arr):
size = len(arr)
result = []
def _perm_heap(arr, size):
if size == 1:
result.append(arr[:])
return
for i in range(size):
_perm_heap(arr, size - 1)
if size & 1: # 홀수일 때
arr[0], arr[size - 1] = arr[size - 1], arr[0] #0번째를 맨 끝과 교체
else: # 짝수일 때
arr[i], arr[size - 1] = arr[size - 1], arr[i] #i번째를 맨 끝과 교체
_perm_heap(arr, size)
return result
a = [1, 2, 3]
print(permutation(a))
# 재귀 없는 버전
def permutation(arr):
size = len(arr)
result = []
result.append(arr[:])
count = [0] * size
i = 1
while i <= size - 1:
if count[i] < i:
if i % 2 == 0: k = 0
else: k = count[i]
arr[i], arr[k] = arr[k], arr[i]
count[i] += 1
i = 1
result.append(arr[:])
else:
count[i] = 0
i += 1
return result
a = [1, 2, 3, 4]
print(permutation(a))
- 과정 : 홀짝에 따라 같은 교체를 반복하면 모든 순열 배열을 표시한다는 원리. 나중에 자세히 공부하자
- 특징 : 원소를 모두 배열하는 경우에만 사용 가능, 최소로 swap을 함
기타 알아볼 것
- Steinhaus–Johnson–Trotter algorithm
완전탐색 순열
순서있이 나열할 수 있는 모든 가짓수를 출력하기.
위의 순열을 모두 합치기
def permutation_all(arr):
n = len(arr)
result = []
for i in range(1, n + 1):
result += permutations(arr, i)
return result
print(permutation_all([1, 2, 3))
# [[1], [2], [3],
# [1, 2], [1, 3], [2, 1], [2, 3], [3, 2], [3, 1],
# [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 2, 1], [3, 1, 2]]
위에서 r개의 원소를 뽑아 순열을 구할 때 썼던 함수를 재이용해서 반복문을 돌려준다.
고정값을 하나씩 추가하는 방식
def permutation_all(arr):
result = []
def _perm_add(arr, fixed):
if len(arr) == 0:
return
for i in range(len(arr)):
new_fixed = fixed + [arr[i]]
result.append(new_fixed[:])
new_arr = arr[:i] + arr[i+1:]
_perm_add(new_arr, new_fixed)
_perm_add(arr, [])
return result
print(permutation_all([1, 2, 3]))
# [[1], [1, 2], [1, 2, 3], [1, 3], [1, 3, 2],
# [2], [2, 1], [2, 1, 3], [2, 3], [2, 3, 1],
# [3], [3, 1], [3, 1, 2], [3, 2], [3, 2, 1]]
고정값을 하나 정해두고 거기에 다른 값을 덧붙이는 방식.
원소를 하나씩 추가하는 방식
def permutation_all(arr):
if len(arr) == 0:
return [[]]
new_item = arr.pop()
prev_perm = permutation_all(arr)
new_perm = []
for perm_item in prev_perm:
for idx in range(len(perm_item)+1):
new_perm.append(perm_item[:idx] + [new_item] + perm_item[idx:])
return prev_perm + new_perm
print(permutation_all([1, 2, 3]))
# [[],
# [1],
# [2], [2, 1], [1, 2],
# [3], [3, 1], [1, 3], [3, 2], [2, 3], [3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]]
원소를 하나씩 추가해가며 순열을 구하는 방식. 재귀를 이용하지 않을 수도 있었지만 그냥 재귀를 이용하였다.
중복 순열
def permutations(arr, r):
n = len(arr)
output = []
results = []
def _perm(arr, output, depth, n, r):
if (depth == r):
results.append(output[:])
return
for i in range(n):
new_output = output + [arr[i]]
_perm(arr, new_output, depth + 1, n, r)
_perm(arr, output, 0, n, r)
return results
print(permutations([1, 2, 3], 2))
순열 계산시에 썼던 코드를 재사용하여 만들었다.
반응형
'📝 알고리즘 > 수학' 카테고리의 다른 글
🧮 부분 집합, 멱집합 구하기 알고리즘 직접 구현! (2) | 2023.01.10 |
---|---|
🧮 조합 알고리즘 직접 구현! (2) | 2023.01.10 |
⚡ 고속 거듭제곱 알고리즘 간단설명! (0) | 2022.10.06 |
[알고리즘, 원리] 유클리드 호제법 (0) | 2022.09.10 |
댓글