算法基础:排序算法(2)

在这里插入图片描述

一、回顾

首先,让我们再回顾一遍,各种排序算法的特性

在这里插入图片描述

上一期,我们的推文已经讲述了冒泡排序、希尔排序、归并排序。本篇文章我们将继续讲述其他排序。

二、排序算法

1.插入排序

插入排序(Insertion Sort)的过程就像我们排序扑克牌一样(从左到右,从小到大)。开始时我们左手为空,然后我们从桌子上拿起一张牌并将它插入到左手中正确的位置,为了找到这个位置,我们将这张牌与左手中从右向左的每张牌进行比较,直到找到比它小或相等的牌的后面。

与排序扑克牌类似插入排序的原理是:将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素,接着取未排序区间中的元素(数组的第二个元素),在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序,重复这个过程,直到未排序区间中元素为空。

思路:

  1. 从第一个元素开始,该元素可以认为已经排序;
  2. 取出下一个元素,在已排序区间中倒序遍历;
  3. 如果已排序元素大于新元素,将已排序元素移动到下一个位置;
  4. 继续向前遍历,重复上一步骤直到找到已排序的元素小于或等于新元素,将新元素插入已排序元素的后面;
  5. 重复2-4步骤。

展示:

实现:

 function insertSort(array) {
      for (let i = 1; i < array.length; i++) {
        let target = i;
        for (let j = i - 1; j >= 0; j--) {
          if (array[target] < array[j]) {
            [array[target], array[j]] = [array[j], array[target]]
            target = j;
          } else {
            break;
          }
        }
      }
      return array;
 }

复杂度:

  • 时间复杂度:O(n2)
  • 空间复杂度:O(1)
2.快速排序

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。

思路:

  1. 选择一个基准元素target(一般选择第一个数)
  2. 将比target小的元素移动到数组左边,比target大的元素移动到数组右边
  3. 分别对target左侧和右侧的元素进行快速排序

展示:

从上面的步骤中我们可以看出,快速排序也利用了分治的思想(将问题分解成一些小问题递归求解)

下面是对序列6、1、2、7、9、3、4、5、10、8排序的过程:

实现:

  • 方法一

单独开辟两个存储空间leftright来存储每次递归比target小和大的序列

每次递归直接返回left、target、right拼接后的数组

(缺点:浪费大量存储空间;优点:写法简单)

function quickSort(array){
  if(array.length < 2 ){
    return array;
  }
  const target = array[0];
  const left = [];
  const right = [];
  for(let i=0; i<array.length; i++){
    if(array[i]<target){
      left.push(array[i]);
    }else{
      right.push(array[i]);
    }
  }
  return quickSort(left).concat([target],quickSort(right));
}
  • 方法二

记录一个索引l从数组最左侧开始,记录一个索引r从数组右侧开始

l<r的条件下,找到右侧小于target的值array[r],并将其赋值到array[l]

l<r的条件下,找到左侧大于target的值array[l],并将其赋值到array[r]

这样让l=r时,左侧的值全部小于target,右侧的值全部小于target,将target放到该位置

(缺点:思路稍复杂;优点:节省存储空间)

function quickSort(array,start,end){
  if(end - start < 1){
    return;
  }
  const terget = array[start];
  let l = start;
  let r = end;
  while(l < r){
    while(l<r && array[r] >= target){
      r--;
    }
    array[l] = array[r];
    while(l<r && array[l] < target){
      l++;
    }
    array[r] = array[l];
  }
  array[l] = target;
  quickSort(array,start,l-1);
  quickSort(array,l+1,end);
  return array;
}

复杂度:

  • 时间复杂度:平均O(nlogn),最坏O(n2),实际上大多数情况下小于O(nlogn)
  • 空间复杂度:O(logn)(递归调用消耗)
3.计数排序

计数排序是一种非基于比较的排序算法,其时间复杂度均为O(n+k),其中k是整数的范围。基于比较的排序算法时间复杂度最小是O(nlogn)的。该算法于1954年由 Harold H. Seward 提出。

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数

思路:

  1. 花O(n)的时间扫描一下整个序列 A,获取最小值 min 和最大值 max
  2. 开辟一块新的空间创建新的数组 B,长度为 ( max - min + 1)
  3. 数组 B 中 index 的元素记录的值是 A 中某元素出现的次数
  4. 最后输出目标整数序列,具体的逻辑是遍历数组 B,输出相应元素以及对应的个数

展示:

所谓“计数”,就是数一数,统计每个元素重复出现的次数

实现:

function countingSort(array) {
  let min = Infinity
  for (let v of array) {
    if (v < min) {
      min = v
    }
  }
  let counts = []
  for (let v of array) {
    counts[v-min] = (counts[v-min] || 0) + 1
  }
  let index = 0 
  for (let i = 0; i < counts.length; i++) {
    let count = counts[i]
    while(count > 0) {
      array[index] = i + min
      count--
      index++
    }
  }
  return array
}

复杂度:

  • 时间复杂度:O(n+k)
  • 空间复杂度:O(k)

三、总结

  • 计数排序适合整数排序
  • 快速排序适合大多数场景
  • 插入排序适合大部分数据离他正确的位置很近,近乎有序的情况
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值