插入排序 原理
- 它的基本思想是将数组分为有序区间和无序区间,默认数组第一个元素为有序区间第一个元素,后面的为无序区间。
- 使用双重循环,外循环从第一个元素后开始,内循环对当前元素拿去与有序区间进行比较插入。
- 先拿当前元素(待排序)与已排好序的数组末位进行比较,若小于数组末位则进行交换,继续向前比较
js实现
insertionSort = (arr) => {
// 边界判断
if (arr == null || arr.length < 2) {
return arr
}
for (let i = 1; i < arr.length; i++) {
// 默认数组第一位元素已经排好序,
let temp = i // 无序区间首位末位是i,即temp
// 把无序区间队首拿去和有序区间比较,从有序区间尾开始向前比较,若小于则交换位置继续向前比较,直到temp大于比较的元素结束当前循环
while (temp > 0) {
if (arr[temp] < arr[temp - 1]) {
//es6新语法,可实现无第三方交换位置
[arr[temp], arr[temp - 1]] = [arr[temp - 1], arr[temp]]
/* let x = arr[temp]
arr[temp] = arr[temp - 1]
arr[temp - 1] = x */
temp--
} else {
break
}
}
}
return arr
}
let arr = [4, 2, 1, 4, 7, 7, 0, 9]
console.log(insertionSort(arr));
tip
两个元素值互换用了es6的语法
let a = 1,
b = 2;
[a, b] = [b, a]
// a=2,b=1
算法分析
空间复杂度
- 在上述插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较N- 1次,时间复杂度为O(N)
- 最坏的情况是待排序数组是逆序的,此时需要比较次数最多,总次数记为:1+2+3+…+N-1,所以,插入排序最坏情况下的时间复杂度为O(N^2)
- 平均来说,A[1…j-1]中的一半元素小于A[j],一半元素大于A[j]。插入排序在平均情况运行时间与最坏情况运行时间一样,是输入规模的二次函数
空间复杂度
- 空间复杂度为常数阶O(1)
不足
- 每次只能将数据移动一位,低效。当数据量大或者数据逆序时有些乏力
优化-希尔排序
希尔排序(缩小增量排序)Diminishing Increment Sort
优化依据
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
个人理解
- 先取一个小于n的整数d1作为增量,把元素全部记录分组,所有距离为d1的倍 数为一组。在各组中采取直接插入排序。然后选取第二个增量d2。。。。。。
- 比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换
- 算法先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量对它进行分组,在每组中再进行排序。
- 当增量减到1时,整个要排序的数被分成一组,排序完成。
- 一般的初次取序列的一半为增量,以后每次减半,直到增量为1。
代码实现
let arr1 = [2, 3, 1, 5, 6, 4, 8, 7]
diminishingIncrementSort = (arr) => {
let len = arr.length
// 初始将数组长度的一半设为增量d,每次大循环减半d = d/2,直到增量d为1,即d>0
for (let d = Math.floor(len / 2); d > 0; d = Math.floor(d / 2)) {
// 开始内层循环,将数组按照增量d分为若干数组,开始为d个小数组,逐渐变为1个
for (var i = d; i < len; i++) {
for (var j = i - d; j >= 0; j -= d) {
// 插入排序
if (arr[j] > arr[d + j]) {
var temp = arr[j];
arr[j] = arr[d + j];
arr[d + j] = temp;
}
}
}
}
console.log(arr);
return arr
}
diminishingIncrementSort(arr1)
算法分析
希尔排序中对于增量序列的选择十分重要,直接影响到希尔排序的性能。我们上面选择的增量序列{n/2,(n/2)/2…1}(希尔增量),其最坏时间复杂度依然为O(n^2),
一些经过优化的增量序列如Hibbard经过复杂证明可使得最坏时间复杂度为O(n^(3/2))
流程实例
// let arr = [8, 7, 1, 5, 6, 4, 2, 3]
// 上上面数组arr有8个元素,首先设定增量d,数组长度除以2,向下取整。此时d为4
// 以此划分初始的4个小数组(此处是按照第几个元素,并不是数组真实索引,需要-1)
// 1+4*0, 1+4*1 1,5 [8,6]
// 2+4*0, 2+4*1 2,6 [7,4]
// 3+4*0, 3+4*1 3,7 [1,2]
// 4+4*0, 4+4*1 4,8 [5,3]
// 结果为以下数组
// [8,6],[7,4],[1,2],[5,3]
// 对每个数组进行直接插入排序
// [8,6]=>[6,8] [7,4] =>[4,7] [1,2] =>[1,2] [5,3] => [3,5]
// [6,8] [4,7] [1,2] [3,5]
// 此时经过第一轮排序(注意索引,)后数组变成[6,4,1,3,8,7,2,5]
// 增量d = d/2,下一轮d为4/2=2,以2为增量划分数组
// 1,3,5,7 [6,1,8,2]
// 2,4,6,8 [4,3,7,5]
// 直接插入排序
// [6,1,8,2]=> [1,6,8,2] => [1,6,2,8]=>[1,2,6,8]
// [4,3,7,5]=[3,4,7,5]=>[3,4,5,7]
// =>[1,3,2,4,6,5,8,7]
// =>直接插入排序
// [1,3,2,4,6,5,8,7]=>[1,2,3,4,6,5,8,7]=>[1,2,3,4,5,6,8,7]=>[1,2,3,4,5,6,7,8]