Grail Sort

Block-based stable sorting family balancing merge performance with tiny extra memory.

Best O(n) Avg O(n log n) Worst O(n log n) Space O(1) Stable Yes In-place Yes Comparison-based

How it works

Block-based stable sorting family balancing merge performance with tiny extra memory.

Implementation

function grailSort(arr, stats) {
  const n = arr.length;
  const block = Math.max(4, Math.floor(Math.sqrt(n || 1)));
  function insertion(lo, hi) {
    for (let i = lo + 1; i <= hi; i++) {
      const key = arr[i];
      let j = i - 1;
      while (j >= lo) {
        compare(j, i);
        if (arr[j] <= key) break;
        arr[j + 1] = arr[j];
        write(j + 1, arr[j + 1]);
        j--;
      }
      arr[j + 1] = key;
      write(j + 1, key);
    }
  }
  function reverse(start, end) {
    while (start < end) {
      compare(start, end);
      let t = arr[start];
      arr[start] = arr[end];
      arr[end] = t;
      swap(start, end);
      start++;
      end--;
    }
  }
  function rotate(lo, mid, hi) {
    if (lo > mid || mid >= hi) return;
    reverse(lo, mid);
    reverse(mid + 1, hi);
    reverse(lo, hi);
  }
  function binarySearchLeft(start, end, target) {
    let lo = start, hi = end;
    while (lo <= hi) {
      let m = lo + Math.floor((hi - lo) / 2);
      compare(m, start);
      if (arr[m] < target) lo = m + 1; else hi = m - 1;
    }
    return lo;
  }
  function binarySearchRight(start, end, target) {
    let lo = start, hi = end;
    while (lo <= hi) {
      let m = lo + Math.floor((hi - lo) / 2);
      compare(m, start);
      if (arr[m] <= target) lo = m + 1; else hi = m - 1;
    }
    return lo;
  }
  function merge(lo, mid, hi) {
    if (lo > mid || mid >= hi) return;
    compare(mid, mid + 1);
    if (arr[mid] <= arr[mid + 1]) return;
    let len1 = mid - lo + 1;
    let len2 = hi - mid;
    let midL, midR;
    if (len1 >= len2) {
      midL = lo + Math.floor(len1 / 2);
      midR = binarySearchLeft(mid + 1, hi, arr[midL]);
    } else {
      midR = mid + 1 + Math.floor(len2 / 2);
      midL = binarySearchRight(lo, mid, arr[midR]);
    }
    const newMid = midL + (midR - 1 - mid) - 1;
    rotate(midL, mid, midR - 1);
    merge(lo, midL - 1, newMid);
    merge(newMid + 1, midR - 1, hi);
  }
  for (let i = 0; i < n; i += block) {
    insertion(i, Math.min(i + block - 1, n - 1));
  }
  for (let width = block; width < n; width <<= 1) {
    for (let i = 0; i < n; i += width << 1) {
      const lo = i;
      const mid = Math.min(i + width - 1, n - 1);
      const hi = Math.min(i + (width << 1) - 1, n - 1);
      if (mid < hi) merge(lo, mid, hi);
    }
    checkpoint(Math.min(width << 1, n), n);
  }
}