🤖 浅谈前端需要知道的排序算法(7个)

87 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

排序算法

插入排序

1.直接插入排序

遍历arr,比较当前值arr[i]与之前的已排序区域arr[0]~arr[i-1]的值,进行插入。

平均时间复杂度:O(n^2)

空间复杂度:O(1)

2.希尔排序

通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止,也叫缩小增量排序。

平均时间复杂度:O(n^(1.3-2))

空间复杂度:O(1)

选择排序

1.直接选择排序

遍历arr,每趟挑选出最小(最大)的值。

平均时间复杂度:O(n^2)

空间复杂度:O(1)

2.堆排序

1.可以想象成完全二叉树。

2.从最后的非叶子节点开始,通过一个自下而上,自右向左比较,造成一个大根堆。将顶端元素和末尾元素交换,即选择出最大元素。

4.进行剩余元素的排序即(步骤2+步骤3)。

平均时间复杂度:O(nlog2n)

空间复杂度:O(1)

附上代码:

var sortArray = function (nums) {
  const len = nums.length;
  // 先根据数组,形成一个最大堆
  // 从第一个非叶子节点开始,其元素位置为Math.floor(len / 2) - 1
  for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
    maxHeapify(nums, i, len);
  }
  // 提取堆顶元素,进行剩余元素的排序
  for (let i = len - 1; i > 0; i--) {
    // 交换堆顶元素至尾部
    swap(nums, i, 0);
    maxHeapify(nums, 0, i);
  }

  return nums;
};

// 形成最大堆操作
function maxHeapify(nums, index, len) {
  let mid = index;
  // 当前节点的左节点位置为 i*2+1
  let i = index * 2 + 1;
  // 当前节点的右节点位置为 i*2+2
  let j = index * 2 + 2;
  if (i < len && nums[index] < nums[i]) {
    index = i;
  }
  if (j < len && nums[index] < nums[j]) {
    index = j;
  }
  // 如果需要进行交换操作
  if (mid !== index) {
    // 进行交换,提取最大值至顶部
    swap(nums, mid, index);
    // 对被交换的元素进行处理
    maxHeapify(nums, index, len);
  }
}

// 交换操作
function swap(nums, i, j) {
  const temp = nums[i];
  nums[i] = nums[j];
  nums[j] = temp;
}

交换排序

冒泡排序

遍历arr长度-1次,通过前后两两交换,每轮可以得到最大的数。

平均时间复杂度:O(n^2)

空间复杂度:O(1)

快速排序

通过先治再分的操作,取出一个元素作为pivot基准值,划分出大于基准值和小于基准值的区域,再对两个区域进行同样操作。

平均时间复杂度:O(nlog2n)

空间复杂度:O(1)

附上代码:

var sortArray = function (nums) {
    quickSort(nums, 0, nums.length - 1)
    return nums
};
// 相当于二叉树前序遍历
function quickSort(arr, left, right) {
    // 如果left大于right 无需分治
    if (left >= right) {
        return
    }
    // 取得基准元素位置
    let mid = partition(arr, left, right)
    // 对基准值左侧区域进行排序
    quickSort(arr, left, mid - 1) 
    // 对基准值右侧区域进行排序
    quickSort(arr, mid + 1, right)
}
// 分治处理
function partition(nums, left, right) {
    // 默认左值为基准值
    const pivot = nums[left]
    // 进行”填坑“操作,先从最右侧开始
    while (left < right) {
        // 如果右侧元素值大于基准值,向左寻找
        while (left < right && nums[right] >= pivot) {
            right--;
        }
        // 如果右侧元素值小于基准值,向left指向进行”填坑“,第一次填坑,默认是覆盖基准元素
        if (left < right && nums[right] < pivot) {
            nums[left] = nums[right];
        }
        // 如果左侧元素值小于基准值,向右寻找
        while (left < right && nums[left] <= pivot) {
            left++;
        }
        // 如果左侧元素值大于基准值,向right指向进行”填坑“。
        if (left < right && nums[right] < pivot) {
            nums[right] = nums[left];
        }
    }
    // 此时left等于right,left的坑位为空,将基准值放入
    nums[left] = pivot;
    return left;
}

暂无分类

归并排序

通过先分后治的操作,先分解出一个个最小子集,对最小子集进行排序处理,再对2个有序的最小子集进行合并排序,重复这个操作,最后可以合并成最大集合。

平均时间复杂度:O(nlog2n)

空间复杂度:O(n)

附上代码:

var sortArray = function (nums) {
  mergeSort(nums, 0, nums.length - 1);
  return nums;
};

// 相当于二叉树后序遍历
function mergeSort(arr, left, right) {
  // 如果left大于right,无需分治
  if (left >= right) {
    return;
  }
  // 取出中间元素进行划分区域
  const mid = Math.floor((right - left) / 2) + left;
  // 治理左边子集
  mergeSort(arr, left, mid);
  // 治理右边子集
  mergeSort(arr, mid + 1, right);
  
  // 治理当前子集:
  // l指向已经有序的左边子集的开头、r指向已经有序的右边子集的开头
  let l = left;
  let r = mid + 1;
  // 存放此子集的排序结果
  const temp = [];
  // 因为子集都有序,直接循环,取相对小的值插入排序结果中。
  while (l <= mid && r <= right) {
    if (arr[l] < arr[r]) {
      temp.push(arr[l]);
      l++;
    } else {
      temp.push(arr[r]);
      r++;
    }
  }
  // 防止左边子集个数大于右边子集个数,依次插入左边的剩余元素
  while (l <= mid) {
    temp.push(arr[l]);
    l++;
  }
  // 防止右边子集个数大于右边子集个数,依次插入右边的剩余元素
  while (r <= right) {
    temp.push(arr[r]);
    r++;
  }
  // 最后将此次排序结果,插入最终的结果集中
  arr.splice(left, right - left + 1, ...temp);
}