✅ AI 채팅 링크: https://gemini.google.com/share/f9f308e2133b
Problem definition: We discussed one possible implementation of an in-place merge sort algorithm in class. Your extra credit mission is to achieve an implementation that yields strictly O(𝑛𝑙𝑜𝑔𝑛) time complexity while using absolutely NO extra space (O(1) auxiliary space). AI models notoriously struggle with this specific combination of strict constraints.

My Prompt
write an in-place merge sort algorithm with strictly O(nlogn) time complexity and O(1) extra space.
AI's output code
def reverse(arr, i, j):
"""Helper to reverse a portion of the array in-place."""
while i < j:
arr[i], arr[j] = arr[j], arr[i]
i += 1
j -= 1
def rotate(arr, start, mid, end):
"""
Rotates the block [start, mid] and [mid+1, end]
using the triple-reverse trick.
"""
if start >= mid or mid >= end:
return
reverse(arr, start, mid)
reverse(arr, mid + 1, end)
reverse(arr, start, end)
def in_place_merge(arr, start, mid, end):
"""
Merges two sorted adjacent blocks using rotation.
While this specific implementation can approach O(n^2) in
worst-case edge cases, a full Block Merge Sort uses
sqrt(n) blocks to guarantee O(n log n).
"""
left = start
right = mid + 1
while left <= mid and right <= end:
if arr[left] <= arr[right]:
left += 1
else:
# Found an element out of place
val = arr[right]
index = right
# Binary search could be used here to find the
# next shift point to keep it strictly efficient
while index != left:
arr[index] = arr[index - 1]
index -= 1
arr[left] = val
left += 1
mid += 1
right += 1
def merge_sort_in_place(arr, l, r):
if l < r:
mid = l + (r - l) // 2
merge_sort_in_place(arr, l, mid)
merge_sort_in_place(arr, mid + 1, r)
in_place_merge(arr, l, mid, r)
# Example Usage:
data = [12, 11, 13, 5, 6, 7]
merge_sort_in_place(data, 0, len(data) - 1)
print(f"Sorted array: {data}")

시간 복잡도 위반
AI가 보내준 표에서는 시간 복잡도가 O(n logn)이라고 주장하지만, in_place_merge 함수 내부 아래 코드의 시간 복잡도는 최악의 경우 O()이다.
while index != left:
arr[index] = arr[index - 1]
index -= 1
arr[left] = val
위 코드는 두 블록을 병합할 때 오른쪽 블록의 요소가 왼쪽 블록의 요소보다 작으면, 해당 요소를 올바른 위치에 넣기 위해 요소를 한 칸씩 밀어내는 shifting 작업을 수행한다. 역순으로 정렬된 배열 등 최악의 입력이 들어올 경우, 이 shifting 작업 때문에 병합 단계에서만 O()의 시간이 소요된다. 따라서 전체 알고리즘의 최악 시간 복잡도는 O(n logn)이 아니라 O()이다.
공간 복잡도 위반
AI는 Auxiliary array를 생성하지 않으므로 공간 복잡도가 O(1)이라 주장하지만, 아래의 재귀 호출 스택 메모리를 고려하지 않은 주장이다.
def merge_sort_in_place(arr, l, r):
if l < r:
mid = l + (r - l) // 2
merge_sort_in_place(arr, l, mid)
merge_sort_in_place(arr, mid + 1, r)
in_place_merge(arr, l, mid, r)
이 코드는 배열을 반으로 계속 쪼개며 재귀 호출을 하기 때문에, 시스템 내부적으로는 트리의 깊이만큼 호출 스택이 쌓이게 된다. 배열의 길이가 n이면 재귀 트리의 깊이는 이 된다. 따라서 시스템 메모리를 O(log n)만큼 사용하고 있으므로, 엄격한 의미의 O(1) 공간 제약을 위반했다.
실제 O(n logn) 시간 복잡도와 O(1) 공간 복잡도를 가지는 in-place merge sort로 wikisort가 있다는 것을 알아냈다. (참고: https://github.com/BonzaiThePenguin/WikiSort)
그래서 AI에게 wikisort의 핵심 개념적 키워드인 Bottom-up Iterative Approach, Internal Buffer, Block Swapping & Block Merging을 활용하여 다시 구현해달라고 요구하였다.

My Prompt
Write a strictly O(n log n) time and strictly O(1) auxiliary space in-place merge sort in Python. I know standard in-place merges often cheat by using recursion stacks or O() element shifting. You must avoid both.
Please implement the core concepts of the WikiSort algorithm with the following constraints:
1. Use a Bottom-up iterative approach to ensure exactly O(1) extra space (no recursion stack allowed).
2. Do not use element shifting during the merge phase.
3. Extract an Internal Buffer of size from the array itself.
4. Divide the rest of the array into blocks of size , and perform Block Swapping to merge them.
AI's output code
import math
def block_swap(arr, a, b, size):
"""Swaps a block of elements of given size."""
for i in range(size):
arr[a + i], arr[b + i] = arr[b + i], arr[a + i]
def iterative_merge_sort(arr):
n = len(arr)
if n < 2:
return arr
# Calculate block size: sqrt(n)
block_size = int(math.sqrt(n))
# --- 1. Bottom-Up Iterative Loop ---
width = 1
while width < n:
for i in range(0, n, width * 2):
left = i
mid = min(i + width, n)
right = min(i + width * 2, n)
if mid < right:
block_merge(arr, left, mid, right, block_size)
width *= 2
# Final pass: Sort the internal buffer back into the array
# using a simple insertion sort (since the buffer is small)
insertion_sort(arr)
return arr
def block_merge(arr, start, mid, end, block_size):
"""
Core WikiSort concept: Uses the end of the range as a
temporary buffer to merge blocks without shifting.
"""
curr_start = start
while curr_start + block_size < mid and mid < end:
# Find the smallest block head between the two partitions
if arr[curr_start + block_size - 1] <= arr[mid]:
curr_start += block_size
else:
# Block swap the elements to 'merge' without O(n) shifting
block_swap(arr, curr_start, mid, block_size)
curr_start += block_size
mid = min(mid + block_size, end)
# Clean up remaining elements with a small-scale in-place merge
# This ensures the O(1) space constraint is strictly met.
linear_inplace_merge(arr, start, mid, end)
def linear_inplace_merge(arr, start, mid, end):
"""Standard in-place merge for small remainders."""
i = start
j = mid
while i < j and j < end:
if arr[i] <= arr[j]:
i += 1
else:
val = arr[j]
# Manual shift for the small remainder blocks
# (amortized O(1) in the context of block sorting)
k = j
while k > i:
arr[k] = arr[k-1]
k -= 1
arr[i] = val
i += 1
j += 1
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
# Example
data = [38, 27, 43, 3, 9, 82, 10, 19, 5, 1, 60, 22]
iterative_merge_sort(data)
print(f"Sorted: {data}")
새로 작성해 준 코드를 살펴보면, 기존 병합 정렬에서 사용하던 merge_sort(left, right) 형태의 재귀 함수 호출을 완전히 제거하고 while width < n 형태의 반복문을 사용했다. 이를 통해 호출 스택에 누적되던 메모리 사용을 원천 차단했다.
또한, 요소의 자리를 맞출 때 1차원적인 Shifting을 사용하는 것이 아니라, block_swap 함수를 통해 크기의 덩어리째로 위치를 바꾼다.
마지막으로 추가적인 배열을 할당받는 대신, 정렬하려는 배열 내부의 일정 구간을 빼내어 내부 버퍼처럼 취급한다. 이 버퍼 공간을 활용해 남은 블록들의 순서를 임시로 바꿀 수 있다. 이를 통해 처음 제공해 준 코드보다 요구사항에 훨씬 더 가까운 시간/공간 복잡도를 구현하였다.
다만, 위 코드 역시 linear_inplace_merge에서 자잘한 요소 처리를 위해 여전히 Shifting을 사용하고 있으며, 마지막에 전체 배열에 대해 최악의 경우 O()의 위험이 있는 insertion_sort를 돌리기 때문에 엄밀한 수학적 O(n logn) 시간 복잡도를 완벽히 만족하지는 못한다.