quickselect.js 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import {ascendingDefined, compareDefined} from "./sort.js";
  2. // Based on https://github.com/mourner/quickselect
  3. // ISC license, Copyright 2018 Vladimir Agafonkin.
  4. export default function quickselect(array, k, left = 0, right = Infinity, compare) {
  5. k = Math.floor(k);
  6. left = Math.floor(Math.max(0, left));
  7. right = Math.floor(Math.min(array.length - 1, right));
  8. if (!(left <= k && k <= right)) return array;
  9. compare = compare === undefined ? ascendingDefined : compareDefined(compare);
  10. while (right > left) {
  11. if (right - left > 600) {
  12. const n = right - left + 1;
  13. const m = k - left + 1;
  14. const z = Math.log(n);
  15. const s = 0.5 * Math.exp(2 * z / 3);
  16. const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
  17. const newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
  18. const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
  19. quickselect(array, k, newLeft, newRight, compare);
  20. }
  21. const t = array[k];
  22. let i = left;
  23. let j = right;
  24. swap(array, left, k);
  25. if (compare(array[right], t) > 0) swap(array, left, right);
  26. while (i < j) {
  27. swap(array, i, j), ++i, --j;
  28. while (compare(array[i], t) < 0) ++i;
  29. while (compare(array[j], t) > 0) --j;
  30. }
  31. if (compare(array[left], t) === 0) swap(array, left, j);
  32. else ++j, swap(array, j, right);
  33. if (j <= k) left = j + 1;
  34. if (k <= j) right = j - 1;
  35. }
  36. return array;
  37. }
  38. function swap(array, i, j) {
  39. const t = array[i];
  40. array[i] = array[j];
  41. array[j] = t;
  42. }