Stable adaptive merge sort that uses power based ordering of runs for near optimal merging.
Powersort is a stable adaptive merge sort that improves how runs are merged. It builds on the same idea as Timsort, detecting natural runs in the input, but uses a principled method to decide merge order based on a notion of power derived from positions in the array.
The goal is to achieve near optimal merge trees with low overhead.
Problem
Given an array of length , reorder it such that:
while preserving stability and exploiting existing runs.
Idea
Powersort detects runs as in natural merge sort. Each run is assigned a power value that reflects its position in the array and how it should be merged.
The power approximates the depth of the run in an optimal binary merge tree.
For two adjacent runs, the power depends on their boundaries:
- earlier runs tend to have lower power
- later runs tend to have higher power
Merging is controlled by comparing powers instead of using ad hoc size rules.
Algorithm
powersort(A):
stack = empty
i = 0
while i < n:
run = detect_run(A, i)
power = compute_power(run)
while stack not empty and stack.top.power >= power:
merge top two runs on stack
push (run, power) onto stack
i = end of run
while stack has more than one run:
merge top two runsThe key function is power computation.
compute_power(run):
let run cover indices [l, r]
return number of leading equal bits
in binary representation of midpointsThis approximates how runs should be merged in a balanced tree.
Example
Let:
Detected runs:
- [1,2,3]
- [7,8]
- [4,5,6]
- [9,10]
Each run gets a power value based on position. Merging is then guided by power ordering rather than simple size rules.
Result:
Correctness
Each run is sorted. Merging preserves sorted order and stability. The power based merge strategy ensures that merges form a nearly optimal binary tree, which guarantees logarithmic depth. By induction over merges, the final array is sorted.
Complexity
| case | time |
|---|---|
| best | |
| worst |
Space:
The number of comparisons is close to the theoretical lower bound for comparison sorting.
Properties
| property | value |
|---|---|
| stable | yes |
| adaptive | yes |
| merge optimality | near optimal |
| in-place | no |
Notes
Powersort replaces the heuristic stack rules of Timsort with a mathematically grounded strategy. It produces well balanced merge trees with minimal overhead.
It is mainly of interest in high performance library implementations where predictable behavior and optimal comparison counts matter.
Implementation
This simplified version shows structure. Real implementations require careful bit level power computation.
def powersort(a):
n = len(a)
stack = []
def detect_run(i):
j = i
while j + 1 < n and a[j] <= a[j + 1]:
j += 1
return (i, j)
def compute_power(l, r):
mid = (l + r) // 2
return mid.bit_length()
def merge(l1, r1, l2, r2):
temp = []
i, j = l1, l2
while i <= r1 and j <= r2:
if a[i] <= a[j]:
temp.append(a[i])
i += 1
else:
temp.append(a[j])
j += 1
while i <= r1:
temp.append(a[i])
i += 1
while j <= r2:
temp.append(a[j])
j += 1
for k, v in enumerate(temp):
a[l1 + k] = v
return (l1, r2)
i = 0
while i < n:
l, r = detect_run(i)
p = compute_power(l, r)
while stack and stack[-1][2] >= p:
l1, r1, _ = stack.pop()
l2, r2 = l, r
l, r = merge(l1, r1, l2, r2)
p = compute_power(l, r)
stack.append((l, r, p))
i = r + 1
while len(stack) > 1:
l1, r1, _ = stack.pop()
l2, r2, _ = stack.pop()
l, r = merge(l2, r2, l1, r1)
stack.append((l, r, 0))
return a