Sqrt Sort

Hybrid stable approach using square-root-sized buffers and blockwise merging.

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

How it works

Hybrid stable approach using square-root-sized buffers and blockwise merging.

Implementation

function sqrtSort(arr, stats) {
  const n = arr.length;
  const block = Math.max(2, Math.floor(Math.sqrt(n || 1)));
  const buf = new Array(block);
  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, j + 1);
        if (arr[j] <= key) break;
        arr[j + 1] = arr[j];
        swap(j + 1, j);
        j--;
      }
      arr[j + 1] = key;
    }
  }
  function reverseRange(start, end) {
    while (start < end) {
      [arr[start], arr[end]] = [arr[end], arr[start]];
      swap(start, end);
      start++;
      end--;
    }
  }
  function rotate(lo, mid, hi) {
    if (lo > mid || mid >= hi) return;
    reverseRange(lo, mid);
    reverseRange(mid + 1, hi);
    reverseRange(lo, hi);
  }
  function lowerBound(lo, hi, target) {
    while (lo <= hi) {
      const m = lo + (hi - lo >> 1);
      compare(m, m);
      if (arr[m] < target) lo = m + 1; else hi = m - 1;
    }
    return lo;
  }
  function upperBound(lo, hi, target) {
    while (lo <= hi) {
      const m = lo + (hi - lo >> 1);
      compare(m, m);
      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 leftLen = mid - lo + 1;
    let rightLen = hi - mid;
    if (leftLen <= block) {
      for (let i = 0; i < leftLen; i++) buf[i] = arr[lo + i];
      let bi = 0, ri = mid + 1, wi = lo;
      while (bi < leftLen && ri <= hi) {
        compare(wi, ri);
        if (buf[bi] <= arr[ri]) {
          arr[wi] = buf[bi++];
        } else {
          arr[wi] = arr[ri++];
        }
        write(wi, arr[wi]);
        wi++;
      }
      while (bi < leftLen) {
        arr[wi] = buf[bi++];
        write(wi, arr[wi]);
        wi++;
      }
      return;
    }
    if (rightLen <= block) {
      for (let i = 0; i < rightLen; i++) buf[i] = arr[mid + 1 + i];
      let li = mid, bi = rightLen - 1, wi = hi;
      while (li >= lo && bi >= 0) {
        if (arr[li] > buf[bi]) {
          arr[wi] = arr[li--];
        } else {
          arr[wi] = buf[bi--];
        }
        write(wi, arr[wi]);
        wi--;
      }
      while (bi >= 0) {
        arr[wi] = buf[bi--];
        write(wi, arr[wi]);
        wi--;
      }
      return;
    }
    let halfLeft = Math.floor(leftLen / 2);
    let splitL = lo + halfLeft;
    let splitR = lowerBound(mid + 1, hi, arr[splitL]);
    let newMid = splitL + (splitR - mid - 1);
    rotate(splitL, mid, splitR - 1);
    merge(lo, splitL - 1, newMid - 1);
    merge(newMid, splitR - 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);
  }
}