希尔排序就是按照指定步长(间隔)将数组划分为若干子序列,然后对分组后的子序列执行插入排序,在当前子序列归于有序之后,通过缩小步长(间隔),来对下一个子序列进行排序,… ,最终当步长(间隔)为1的子序列变成有序时,则待排序数组已然变成有序。
/**
* 将数组中索引值为 i 和 j 的元素交换
* @param arr 操作数组
* @param i 元素1的索引值
* @param j 元素2的索引值
*/
function swap(arr: number[], i: number, j: number) {
const temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
// ES6 语法:利用数组的解构赋值亦可实现数组元素交换
// [arr[i], arr[j]] = [arr[j], arr[i]]
}
/**
* 对数组进行希尔排序
* @param arr 待排序的数组
* @param isAsc 是否为升序排序(从小到大)
* @return 排好序后的原数组
*/
function shellSort(arr: number[], isAsc: boolean = true): number[] {
// 获取数组的长度
const n = arr.length
// 选择不同的增量(步长)
let gap = Math.floor(n / 2)
// 1. 第一层循环:不断改变步长的过程
// 5 -> 2 -> 1 (最后一个 gap 一般为 1)
while (gap > 0) {
// 插入排序:默认第一个元素是有序的,所以从第二个元素往后才需要进行排序
// 获取到不同的 gap,使用 gap 进行插入排序:依次遍历从 gap 往后的元素
// 2. 第二层循环:找到不同的子数列集合,然后对其进行插入排序
for (let i = gap; i < n; i++) {
let j = i
const num = arr[i]
// 使用 num 向前去找到一个比 num 小的值
// j >= 0 同时 待排序元素 num < 前面的元素(指定间隔位数上的元素)
// 3. 第三层循环:while 循环,对子数列进行插入排序的过程
const calcCondition = (isAsc: boolean = true) => isAsc ? num < arr[j - gap] : num > arr[j - gap]
while (j > gap - 1 && calcCondition(isAsc)) {
// 后移
arr[j] = arr[j - gap]
j = j - gap // 下一个元素
}
arr[j] = num // 将当前待排序元素插入到当轮分组排序中其最终的位置上
}
gap = Math.floor(gap / 2)
}
return arr
}
const nums = [6, 1, 66, 88, 999, 666, 168, 12]
// 升序排序
const newNumsAsc = shellSort(nums)
console.log(newNumsAsc)
// [1, 6, 12, 66, 88, 168, 666, 999]
// 降序排序
const newNumsDesc = shellSort(nums, false)
console.log(newNumsDesc)
// [999, 666, 168, 88, 66, 12, 6, 1]
// 当前希尔排序会改变原数组,如果不想改变原数组,可以使用深度克隆,克隆一个用于排序使用的新数组
console.log(nums)
// [999, 666, 168, 88, 66, 12, 6, 1]