排序-7-希尔排序

667 阅读4分钟

从本质上讲,希尔排序是插入排序的升级版

插入排序的平均时间复杂度为O(n2),这个排序算法并不复杂,但是并不是一个高效的排序算法

我们可以针对于插入排序的两个特点进行优化:

1. 在大多数元素已经有序的情况下,插入排序的工作量较小

如果一个数组中的大部分元素已经有序,那么数组中的元素自然不需要频繁的进行比较和交换

2. 在元素数量较小的情况下,插入排序的工作量较小

插入排序的工作量和 n 的平方成正比,如果 n 较小,那么排序的工作量自然要小得多

初识希尔排序

希尔排序的思想就是:对原数组进行一些"预处理",使原数组大部分元素变得有序

如下原数组:

1. 让元素两两一组,同组两个元素之间的跨度,都是数组总长度的一半,也就是 4

如图所示,5 和 9 一组,8 和 2 一组,6 和 1 一组, 3 和 7 一组,接下来对每一组排序,排序方法直接使用插入排序,由于每一组元素数量很少,只有两个,所以插入排序的工作量很少,每组排序完成后的数组如下:

这样一来,数组整体的有序性得到显著提高,这样的做法可以理解为对原数组的"粗略调整"

2. 进一步缩小分组跨度,重复以上工作,把跨度缩小到原本的一半,也就是跨度为 2,重新对元素进行分组

如上所示,5、1、9、6 一组,2、3、8、7 一组,再次对每组进行插入排序,完成后原数组如下:

3. 进一步把分组跨度缩小,让跨度为 1,也就等同于直接做插入排序,经过一系列粗略调整,直接插入排序的工作量减少了很多,排序结果如下:

整个排序过程如下:

像这样初步分组进行初调,再直接进行插入排序的思想,就是希尔排序,根据该算法的发明者,计算机科学家 Donald Shell 的名字所命名

上边示例所使用的分组跨度(4,2,1),被称为希尔排序的增量,增量的选择有很多种,这里使用的逐步折半的方法,是在 Donald Shell 在发明希尔算法时提出的一种朴素方法,被称为希尔增量

代码实现

    function sort(arr: Array<number>) {
        let d = arr.length;
        while(d > 1) {
            d = Math.floor(d / 2);
            for (let x = 0; x < d; x++) {
                for (let index = x + d; index < arr.length; index+=d) {
                    let temp = arr[index];
                    let j = index - d;
                    for (; j >= 0 && arr[j] > temp; j-= d) {
                        arr[index] = arr[j];
                    }
                    arr[j+ d] = temp;
                }
            }
        }
        return arr
    }
    function main() {
        const arr = [5, 3, 9, 12, 6, 1, 7, 2, 4, 11, 8, 10];
        console.log(sort(arr))
    }

存在问题及优化

问题:

希尔排序利用分组粗调的方式,减少了直接插入排序的工作量,使得算法平均复杂度低于O(n2)

但是在某些极端的情况下,希尔排序甚至比直接插入排序O(n2)更慢

示例:

例如这个数组,如果照搬之前的思路,无论是以 4 为增量,还是以 2 为增量,每组内部的元素都没有任何交换,直到把增量缩减为 1,数组才会按照直接插入排序的方式进行调整,对于这样的数组,希尔排序非但没有减少直接排序的工作量,反而白白增加了分组操作的成本

解决:

为了保证分组粗调没有盲区,每一轮的增量需要彼此"互质",也就是没有除了 1 之外的公约数

于是人们提出了很多增量方式,其中最具代表性的为 Hibbard增量Sedgewick增量

Hibbard增量序列如下:

1,3,,7,15....

通项公式:2^k + 1

Sedgewick增量序列如下:

1,5,19,41....

通项公式:9*4^k - 9*2^k + 1 或者 4^k - 3*2^k + 1

希尔排序为不稳定排序

示例如下:

原始数组:

按照希尔增量分组,第一轮粗调增量为 4,绿色 5 会和 4 交换,排到橙色 5 后边

第二轮(增量为2):

最终排序结果

摘要总结公众号:程序员小灰 文章