Skip to content

Powersort

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 AA of length nn, reorder it such that:

A[0]A[1]A[n1] A[0] \le A[1] \le \dots \le A[n-1]

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 runs

The key function is power computation.

compute_power(run):
    let run cover indices [l, r]

    return number of leading equal bits
    in binary representation of midpoints

This approximates how runs should be merged in a balanced tree.

Example

Let:

A=[1,2,3,7,8,4,5,6,9,10] A = [1,2,3, 7,8, 4,5,6, 9,10]

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:

[1,2,3,4,5,6,7,8,9,10] [1,2,3,4,5,6,7,8,9,10]

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

casetime
bestO(n)O(n)
worstO(nlogn)O(n \log n)

Space:

O(n) O(n)

The number of comparisons is close to the theoretical lower bound for comparison sorting.

Properties

propertyvalue
stableyes
adaptiveyes
merge optimalitynear optimal
in-placeno

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