JavaScript实现排序算法(4)——直接插入排序和希尔排序

插入排序

1 直接插入排序

直接插入排序的核心思想是,待排数列一个一个地插入有序数列,不断扩大有序数列,直到待排数列为空.

1.1 算法过程
(从小到大排序)
1. 单个数字一定有序,因此数组首项可以看做一个有序数列,剩余的项组成待排数列;
2. 从头到尾依次扫描待排数列,将扫描到的每个元素插入有序数列的适当位置.(如果待插入的元素与有序数列中的某个元素相等,则将待插入元素插入到相等元素的后面)
1.2 排序演示

对下面数列进行直接插入排序:

5, 3, 2, 4, 1

将数列首项5作为有序数列,待排数列为[3,2,4,1],取待排数列首项3作为当前元素,与有序数列[5]从后往前进行比较

5, 3, 2, 4, 1
↑  ↑

3小于5,5向后移一位,腾出位置给3,此时有序数列为[3, 5],待排数列为[2, 4, 1]

3, 5, 2, 4, 1

取待排数列首项2作为当前元素,与有序数列[3, 5]从后往前进行比较

3, 5, 2, 4, 1
   ↑  ↑

2小于5,5向后移一位,腾出空位.2继续往前,与3作比较

3, 空位, 5, 4, 1
↑  当前元素2

2小于3,3向后移一位,腾出位置给2,此时有序数列为[2, 3, 5],待排数列为[4, 1]

取待排数列首项4作为当前元素,与有序数列[2, 3, 5]从后往前进行比较

2, 3, 5, 4, 1
      ↑  ↑

4小于5,5向后移一位,腾出空位.4继续往前,与3作比较

2, 3, 空位, 5, 1
   ↑  当前元素4

4不小于3,当前元素4填入空位.此时有序数列为[2, 3, 4, 5],待排数列为[1]

以此类推,最后一个元素1会插入到有序数列的,最终得到的有序数列[1, 2, 3, 4, 5]即为排序完成的结果.

1.3 代码实现
const arr = [5, 3, 2, 4, 1, 7, 10, 9, 8, 6]
function insertSort(arr) {
    // 缓存数组长度
    let len = arr.length,
        curValue;
    // 外层循环,遍历待排数列
    for (let i = 1; i < arr.length; i++) {
        // curValue 待排数列的当前元素
        curValue = arr[i];
        // 内层循环,从后往前遍历有序数列
        for (let j = i - 1; j >= 0; j--) {
            // curValue小于有序数列当前值,则有序数列当前值后移一位,curValue插入
            if (curValue < arr[j]) {
                arr[j + 1] = arr[j];
                arr[j] = curValue;
            } else {
			   // 如果curValue不小于有序数列当前值,则不再往后比较,直接将curValue插入当前位置
                break;
            }
        }
    }
    return arr;
}


1.4 时间复杂度
  • 最好情况下:原数列有序,此时内层循环只走一次,整体复杂度取决于外层循环,时间复杂度是O(n).
  • 最坏情况下:原数列逆序,此时内层循环每次都要比较和移动有序数列里所有的元素,时间复杂度是两层循环的O(n^2);
  • 平均时间复杂度:O(n^2).
1.5 稳定性

从插入排序的排序过程分析,从未排序数列取首项,与有序数列从后往前比较时,如果两个元素相等,则有序数列的值不腾出位置.两个元素的先后位置并未改变,因此插入排序是稳定的.

2 希尔排序

希尔排序是对直接插入排序的一种改进.

2.1 算法过程
(从小到大排序)
1. 选择一个递减的增量序列t1, t2, ... , tk,其中序列尾项,即tk必为1
2. 以t1为增量,将待排数列划分为t1个子序列,分别对这t1个子序列进行直接插入排序,得到新的待排数列
3. 以t2为增量,将待排数列划分为t2个子序列,分别对这t2个子序列进行直接插入排序,得到新的待排数列
4. 以此类推,直到以tk为增量,即增量为1,此时待排数列本身进行直接插入排序,得到排序后的数列
2.2 排序演示

算法过程讲起来比较抽象,来看一个希尔排序的例子.

对下面的数列进行希尔排序

3, 4, 6, 9, 5, 7, 8, 1, 2, 10

确定增量序列,一般是以待排数列的长度来决定.

这里,我们以待排数列长度除以2并向下取整,确定增量序列的首项,即

⌊10 / 2⌋ = 5

再根据t(i) = ⌊t(i-1) / 2⌋得到增量序列为

5, 2, 1

开始第一轮排序,增量为5,待排数列被划分为5a,b,c,d,e,其中a组子数列为[3, 7],b组子数列为[4, 8],c组子数列为[6, 1],d组子数列为[9, 2],e组子数列为[5, 10]

3, 4, 6, 9, 5, 7, 8, 1, 2, 10
a  b  c  d  e  a  b  c  d  e

分别对a,b,c,d,e五组子数列进行直接插入排序,得到新的待排数列

3, 4, 1, 2, 5, 7, 8, 6, 9, 10

开始第二轮排序,增量为2,待排数列被划分为2a,b,其中a组子数列为[3, 1, 5, 8, 9],b组子数列为[4, 2, 7, 6, 10]

3, 4, 1, 2, 5, 7, 8, 6, 9, 10
a  b  a  b  a  b  a  b  a  b

分别对a,b两组子数列进行直接插入排序,得到新的待排数列

1, 2, 3, 4, 5, 6, 8, 7, 9, 10

开始第三轮排序,增量为1,直接对待排数列进行直接插入排序,得到排序后的数列

1, 2, 3, 4, 5, 6, 7, 8, 9, 10
2.3 代码实现
const arr = [3, 4, 6, 11, 9, 5, 7, 8, 1, 2, 10];

const arr = [3, 4, 6, 11, 9, 5, 7, 8, 1, 2, 10];

function shellSort(arr) {
  // 缓存数组长度
  let len = arr.length;
  // 初始化增量序列首项
  let increment = Math.floor(len / 2);
  // 外层循环增量序列
  for (increment; increment > 0; increment = Math.floor(increment / 2)) {
    // 巧妙地将划分子数列和对子数列进行直接插入排序结合
    for (let i = increment; i < len; i++) {
      let curValue = arr[i];
      // 外部定义变量j,这样不用在每一次循环都把curValue插入,只需要在循环结束后插入
      let j;
      // 将arr[j] > curValue写在循环的判断条件中,这样当不满足该条件时,循环就会被break,不会再往下进行比较(实际上相当于前面直接插入排序的if-else判断)
      for (j = i - increment; j >= 0 && arr[j] > curValue; j -= increment) {
        arr[j + increment] = arr[j];
      }
      arr[j + increment] = curValue;
    }
  }
  return arr;
}
2.4 性能分析

由希尔排序的算法过程可以知道,希尔排序的执行时间依赖于增量序列.好的增量序列有如下特征:

  1. 最后一个增量必须为1;
  2. 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况;

希尔排序性能是优于直接插入排序的,为什么这么说?我们知道直接插入排序有两个特征:

  1. n值较小时,nn^2的差别较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度O(n^2)差别不大.
  2. 原始待排数列基本有序时,直接插入排序所需的比较和移动次数较少;

希尔排序开始时,增量较大,分组较多,每组子数列的项数较少,因此各组子数列直接插入排序较快,这对应了直接插入排序的第一个特征.

希尔排序到后面增量越来越小,分组数较少,各组子数列的项数较多,但是由于前面的排序已经使得这些子数列基本有序,所以新的一趟直接插入排序也较快,这对应了直接插入排序的第二个特征.

2.5 稳定性

从希尔排序的排序过程可以看到,划分子数列并对子数列进行直接插入排序时,是很有可能打乱两个相等元素的先后顺序的,因此希尔排序是不稳定的.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值