插入排序定义:维护一个有序区,把元素一个个插入有序区的适当位置,直到所有元素都有序为止。
插入排序工作量相对比较小:
1.数组元素较少的时候,插入排序的工作量会比较小。因为插入排序的时间复杂度是o(n²),工作量和n²成正比,如果n比较小,那么排序的工作量自然会小很多。
2.当数组大部分元素有序的时候。插入排序的工作量也会相对较小。因为在这种情况下,数组中的元素并不需要进行频繁的比较和交换。
希尔排序定义:让元素分组,让元素两两一组,同组两个元素之间的跨度,是数组总长度的一半。
例子:定义无序数组为:[12, 3, 1, 46, 0, 5]
分组跨度为3。
元素12,46为一组,元素3,0为一组,元素1,5为一组。一共三组。
我们让每组元素进行独立排序,排序方法用直接插入排序即可。由于每一种的元素只有2个,所以插入排序的工作量很少。排序之后的数组为 [12,0,1,46,3,5]
第一轮排序完,数组整体的有序程序得到了显著提高,使得后续再进行直接插入排序的工作量大大减少。这种做法,可以理解为对原始数组的"粗略调整"。
我们把分组跨度进一步减小,把跨度缩小为原来的一半后向下取整,让跨度为1,也就等同于做直接插入排序。
经过之前的一系列粗略调整,直接插入排序的工作量减少了很多。
像这样逐步分组进行粗调,再进行直接插入排序的思想,就是希尔排序。
所使用的分组跨度(3,1),被称为希尔排序的增量。
示例代码:从小到大排序
shell_sort_asc(arr) {
// 参数为需要排序的数组
// 获取初始的增量,第一轮跨度为3,第二轮跨度为1(等于插入排序)
let gap = Math.floor(arr.length / 2);
while (gap >= 1) {
// 增量小于1时结束循环
// 从gap开始遍历,因为插入排序假设第一个是有序的
for (let i = gap; i < arr.length; i++) {
let j = i; // 为插入排序从后向前排序提供条件
// 如果排序的后面的数字小于前面的,交换两个数的位置
while (arr[j] < arr[j - gap] && j - gap >= 0) {
let temp = arr[j];
arr[j] = arr[j - gap];
arr[j - gap] = temp;
// j减小gap从后向前遍历
j -= gap;
console.log("shell_sort", j, gap, i, arr);
}
console.log("shell_sort1", j, gap, i, arr);
}
console.log("shell_sort222222222222222", gap);
// 增量每次都减小一半
gap = Math.floor(gap / 2);
}
return arr;
},
示例代码:从大到小排序
shell_sort_all_desc(arr) {
// 参数为需要排序的数组
// 获取初始的增量
let gap = Math.floor(arr.length / 2);
while (gap >= 1) {
// 增量小于1时结束循环
// 从gap开始遍历,因为插入排序假设第一个是有序的
for (let i = gap; i < arr.length; i++) {
let j = i; // 为插入排序从后向前排序提供条件
// 如果排序的后面的数字大于前面的,交换两个数的位置
while (arr[j] > arr[j - gap] && j - gap >= 0) {
let temp = arr[j];
arr[j] = arr[j - gap];
arr[j - gap] = temp;
// j减小gap从后向前遍历
j -= gap;
}
}
// 增量每次都减小一半
gap = Math.floor(gap / 2);
}
return arr;
},
稳定排序定义:待排序的记录序列中可能存在两个或两个以上关键字相等的记录。排序前的序列中Ri领先于Rj(即i<j).若在排序后的序列中Ri仍然领先于Rj,则称所用的方法是稳定的。
希尔排序不是稳定排序。例子:
数组:[5,8,6,5,4,2,1,7]
按照希尔增量分组,跨度为4,5跟4为一组。
数组第0项换到去数组第4项。
第一轮粗调后,相同的元素5,在排序之后改变了次序。由此可见希尔排序是一个不稳定排序。