선택 심화: Shell 정렬과 간격
떨어진 위치들의 삽입 정렬을 반복하고 간격 선택의 영향을 이해합니다.
이 장은 핵심 과정 뒤의 선택 심화입니다. Shell 정렬의 목표는 멀리 있는 값을 먼저 이동시켜 마지막 삽입 정렬의 부담을 줄이는 것입니다. 삽입 정렬을 이해한 뒤 읽으세요.
개념과 약속
간격 gap을 정하면 인덱스 나머지가 같은 위치들이 하나의 부분 수열입니다. gap=2에서는 0,2,4,…와 1,3,5,…를 각각 삽입 정렬합니다. 간격을 줄이다 마지막 gap=1에서 전체를 정렬합니다. 아래는 gap을 n//2부터 절반씩 줄이는 단순한 수열입니다.
간격 수열마다 성능 분석이 다릅니다. Shell 정렬이라는 이름만으로 한 시간 복잡도를 붙이지 않습니다. 먼 위치 이동으로 동률 항목의 순서를 바꿀 수 있어 일반적으로 안정적이지 않습니다. 부분 수열을 실제 새 목록으로 만들지 않고 같은 작업 목록의 떨어진 칸들을 이동합니다.
손으로 상태 추적하기
입력 [8,5,3,1,6,2]의 gap=3입니다.
| 위치 묶음 | 처음 | 간격 삽입 뒤 |
|---|---|---|
| 0,3 | 8,1 | 1,8 |
| 1,4 | 5,6 | 5,6 |
| 2,5 | 3,2 | 2,3 |
전체는 [1,5,2,8,6,3]입니다. gap을 1로 줄이면 전체 삽입 정렬로 끝냅니다.
직접 실행하기
아래 코드는 이 본문만으로 실행할 수 있습니다. lesson.py로 저장하고 첫 장에서 확인한 Python 명령으로 실행하세요.
def shell_sort(values):
result = list(values)
gap = len(result) // 2
while gap:
for i in range(gap, len(result)):
current, j = result[i], i
while j >= gap and result[j - gap] > current:
result[j] = result[j - gap]
j -= gap
result[j] = current
gap //= 2
return result
print(shell_sort([8, 5, 3, 1, 6, 2]))
print(shell_sort([2, 2, 1]))
print(shell_sort([]))실행 결과:
[1, 2, 3, 5, 6, 8]
[1, 2, 2]
[]왜 맞고 언제 끝나는가
각 간격의 부분 수열은 삽입 정렬의 불변조건으로 정렬됩니다. 마지막 간격 1은 전체 원소가 하나의 부분 수열이므로 전체 정렬이 보장됩니다. gap은 양의 정수에서 절반씩 줄어 0이 되고, 각 삽입 위치도 gap씩 감소해 종료합니다.
비용과 적용 범위
이 절반 간격 수열의 최악은 O(n²)로 설명합니다. 다른 간격 수열에 대한 더 나은 상한을 이 코드의 보장으로 쓰지 않습니다. 작업 목록 내부 보조 공간 O(1), 원본 보존 복사 O(n)입니다.
스스로 풀어 보기
- 길이 8에서 이 코드의 gap 순서를 쓰세요.
- 마지막 gap=1 단계를 생략하면 gap=2 정렬만으로 전체 순서가 보장되나요?
힌트
- 정수 나눗셈으로 절반씩 줄입니다.
- 짝수 위치와 홀수 위치의 순서는 서로 비교하지 않았습니다.
해설
- 4,2,1입니다.
- 보장되지 않습니다.
[1,4,2,5,3,6]은 두 부분 수열이 정렬되어도 전체가 정렬된 것은 아닙니다.
흔한 오해
간격이 작은 순서로만 진행하는 것이 이 코드의 전략이 아닙니다. 처음 먼 위치를 다룬 뒤 전체 정렬을 마무리합니다.
복습과 다음 연결
기수 정렬은 값의 자릿수라는 제약을 이용해 비교와 다른 방식으로 정렬합니다.