JS 实现堆排序

关于堆排序的介绍,可以参考 leetcode 某大佬的文章【动画模拟】一个破堆排我搞了四个动画,本文严重参考了该文章

代码实现

  1. /**
  2. * 堆排序
  3. *
  4. * 因为大顶堆(或小顶堆)一定是一颗完全二叉树,当我们用数组表示完全二叉树时,有如下规律:
  5. *
  6. * (1)左子树在数组中的位置 = 2 * i + 1
  7. *
  8. * (2)右子树在数组中的位置 = 2 * i + 2
  9. *
  10. * 其中i为左右子树的父节点在数组中的位置
  11. *
  12. * @param {*} nums 要排序的数组
  13. * @returns
  14. */
  15. var heapSort = function (nums) {
  16. // 大顶堆
  17. const len = nums.length;
  18. // 最后一个非叶子节点,即最后一个父结点
  19. let lastParent = Math.trunc(len / 2) - 1;
  20. // 从后往前构建大顶堆
  21. for (let i = lastParent; i >= 0; i--) {
  22. sink(nums, i, len);
  23. }
  24. let temp = len - 1;
  25. while (temp > 0) {
  26. // 将大顶堆的根结点交换到数组的最后一个位置
  27. swap(nums, 0, temp--);
  28. // 将新的根结点下沉到合适的位置,重新构建大顶堆
  29. sink(nums, 0, temp);
  30. }
  31. return nums;
  32. };
  33. /**
  34. * 下沉节点
  35. * @param {*} nums 数组
  36. * @param {*} parentIndex 父节点的下标
  37. * @param {*} end 下沉结束的位置
  38. * @returns
  39. */
  40. function sink(nums, parentIndex, end) {
  41. // 左子节点对应的下标
  42. let lIndex = parentIndex * 2 + 1;
  43. // 右子节点对应的下标
  44. let rIndex = lIndex + 1;
  45. // 如果左子节点不存在,那么右子节点一定也不存在,当前节点已经是叶子节点了,无需下沉
  46. if (lIndex > end) return;
  47. // 默认认为左子结点的值大于右子节点的值,即nums[lIndex] > nums[rIndex]
  48. let maxIndex = lIndex;
  49. // 找到左右子节点中的较大值
  50. if (rIndex <= end && nums[lIndex] < nums[rIndex]) {
  51. maxIndex = rIndex;
  52. }
  53. // 如果左右子节点中的较大值 > 父节点的值,那么交换较大值和父节点
  54. if (nums[maxIndex] > nums[parentIndex]) {
  55. // 交换nums[lIndex] 和nums[parentIndex]
  56. swap(nums, maxIndex, parentIndex);
  57. // 交换节点值之后,将交换后的子节点作为新的父节点继续下沉,直到合适的位置
  58. sink(nums, maxIndex, end);
  59. }
  60. }
  61. function swap(nums, i, j) {
  62. // 通过[a,b]=[b,a]的方式交换a,b的值
  63. [nums[i], nums[j]] = [nums[j], nums[i]];
  64. }
JavaScript

测试

  1. console.log(heapSort([5, 2, 3, 1, 4, 7, 8, 6]));
  2. // [ 1, 2, 3, 4, 5, 6, 7, 8]
JavaScript
编程笔记 & 随笔杂谈