순열(Permutation)은 주어진 원소들의 순서를 고려한 배열이다. 즉, 주어진 원소들의 모든 가능한 순서의 경우를 나열하는 것이 순열 생성이다. 주어진 원소가 n개라면, 순열의 개수는 n! (팩토리얼)개가 된다.
예시:
주어진 원소가 [1, 2, 3]일 때, 가능한 모든 순열은 다음과 같다:
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
순열 생성의 특징
순열은 원소들의 순서를 바꾸어 다양한 조합을 만들기 때문에, 원소들의 순서가 매우 중요하다. 따라서 순열 생성 문제에서는 모든 원소를 한 번씩 사용하여 가능한 모든 경우를 나열하는 방식으로 접근해야 한다.
백트래킹을 이용한 순열 생성
백트래킹을 사용한 순열 생성은 각 단계에서 아직 사용하지 않은 원소를 선택하여 현재 순열에 추가하고, 그 상태에서 다음 원소를 재귀적으로 선택하는 방식으로 진행된다.
백트래킹 알고리즘 개념
- 원소 선택: 현재까지 만들어진 순열에 사용되지 않은 원소를 선택하여 추가한다.
- 재귀 호출: 그 원소를 추가한 후, 나머지 원소들 중에서 다시 순열을 만든다.
- 기저 조건: 현재까지 만든 순열의 길이가 원소의 개수와 같으면, 해당 순열을 결과로 저장한다.
- 백트래킹: 재귀 호출이 완료되면, 마지막으로 추가한 원소를 제거하여 이전 상태로 되돌아가고, 다른 경로를 탐색한다.
백트래킹을 이용한 순열 생성 구현
def generate_permutations(nums):
def backtrack(current_permutation, used):
# 모든 원소를 사용한 경우, 순열을 저장
if len(current_permutation) == len(nums):
result.append(list(current_permutation))
return
# 가능한 모든 원소를 탐색
for i in range(len(nums)):
if used[i]: # 이미 사용한 원소는 패스
continue
# 현재 원소를 순열에 추가
current_permutation.append(nums[i])
used[i] = True # 현재 원소를 사용했음을 표시
# 재귀적으로 나머지 원소들에 대해 순열 생성
backtrack(current_permutation, used)
# 백트래킹: 원래 상태로 되돌리기
current_permutation.pop()
used[i] = False
result = []
used = [False] * len(nums) # 원소 사용 여부를 기록
backtrack([], used) # 빈 순열로 시작
return result
# 테스트
nums = [1, 2, 3]
print(generate_permutations(nums))
# [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
코드 설명
- generate_permutations(nums) 함수:
- 이 함수는 입력 리스트 nums의 모든 순열을 생성한다. 결과를 저장할 리스트 result를 선언하고, 백트래킹 함수 backtrack을 호출하여 순열을 생성한다.
- backtrack(current_permutation, used) 함수:
- current_permutation: 현재까지 생성된 순열을 저장하는 리스트이다.
- used: 각 원소가 현재 순열에서 사용되었는지 여부를 기록하는 리스트이다.
- 기저 조건: 현재까지 만든 순열의 길이가 원소의 개수와 같으면, 즉 모든 원소를 한 번씩 사용한 경우, 해당 순열을 result에 추가한다.
- 재귀적 호출: 사용되지 않은 원소를 찾아 현재 순열에 추가하고, 나머지 원소들에 대해 재귀 호출을 통해 순열을 생성한다.
- 백트래킹: 재귀 호출이 끝난 후에는 마지막으로 추가한 원소를 제거(current_permutation.pop())하고, 그 원소를 다른 경로에서 다시 사용할 수 있도록 used[i] 값을 False로 되돌린다.
- used 리스트:
- used[i]는 원소 nums[i]가 현재 순열에서 사용되었는지 여부를 나타낸다. 이미 사용된 원소는 다시 사용할 수 없으므로 continue를 통해 건너뛴다.
백트래킹 순열 생성의 동작 과정
- 초기 상태: current_permutation = []에서 시작하고, 모든 원소가 아직 사용되지 않았다 (used = [False, False, False]).
- 첫 번째 재귀 호출:
- 첫 번째 원소 1을 선택하여 current_permutation = [1]이 되고, used = [True, False, False]로 갱신된다.
- 재귀 호출을 통해 나머지 원소들 중에서 순열을 생성한다.
- 두 번째 재귀 호출:
- 첫 번째 원소 1을 사용했으므로, 두 번째 원소 2를 선택하여 current_permutation = [1, 2]가 된다. used = [True, True, False]로 갱신된다.
- 마지막 남은 원소 3을 선택하여 current_permutation = [1, 2, 3]이 된다.
- 이 상태에서 모든 원소를 사용했으므로, 순열 [1, 2, 3]을 저장한다.
- 백트래킹:
- 순열 [1, 2, 3]을 저장한 후, 백트래킹을 통해 3을 제거하고 current_permutation = [1, 2]로 돌아간다.
- 그다음에는 다른 경로를 탐색할 수 있도록 used[2]를 False로 갱신한다.
- 다른 경로 탐색:
- 이번에는 원소 3을 먼저 선택하는 경로를 탐색합니다. 이 과정을 반복하여 모든 순열을 탐색한다.
시간 복잡도
순열 생성에서 모든 원소를 사용하여 순서를 고려한 배열을 만들기 때문에, 각 원소는 한 번씩 모든 위치에 올 수 있다. 주어진 집합의 원소 수가 n개라면, 순열의 개수는 n!개이다.
- 시간 복잡도: O(n!)
- 순열의 개수는 n!개이며, 각각의 순열을 생성하는데 n개의 원소를 한 번씩 탐색해야 하므로, 시간 복잡도는 O(n!)이다.
- 공간 복잡도: O(n) (재귀 깊이 및 사용 리스트)
- 재귀 호출의 깊이는 n이므로, 스택 메모리는 O(n)입니다. 또한, used 리스트와 current_permutation 리스트가 각각 O(n)의 공간을 사용한다.
백트래킹 순열 생성의 장점
- 효율적인 탐색: 백트래킹을 통해 모든 가능한 경로를 탐색하며, 중복을 방지하고 필요한 경로만 탐색할 수 있다.
- 간결한 구현: 재귀적 호출과 백트래킹을 통해 문제를 직관적이고 간결하게 해결할 수 있다.
- 확장 가능성: 이 방식은 다양한 문제(순열 생성, 조합, 부분 집합 생성 등)에 응용될 수 있다.
- 순열 생성은 주어진 집합의 원소들을 순서를 고려하여 모든 가능한 배열을 생성하는 문제이다.
- 백트래킹을 사용하여 사용된 원소를 추적하고, 각 원소를 선택할 때마다 재귀적으로 나머지 원소들을 탐색하는 방식으로 순열을 생성한다.
- 시간 복잡도는 O(n!)이며, 백트래킹을 통해 효율적으로 순열을 생성할 수 있다.
모든 코드는 github에 저장되어 있습니다.
https://github.com/SeongUk18/Algorithm/tree/main/Data_structure_algorithms
728x90
'Data structure & Algorithms study' 카테고리의 다른 글
다익스트라 알고리즘 (Dijkstra's Algorithm) (0) | 2024.10.18 |
---|---|
백트래킹_부분 집합 생성 (Subset Generation) (0) | 2024.09.25 |
백트래킹 (Backtracking)_N-Queen (0) | 2024.09.24 |
최대 부분 배열 합 (Maximum Subarray Sum) (0) | 2024.09.22 |
최장 공통 부분 수열 (Longest Common Subsequence, LCS) (0) | 2024.09.19 |