希尔排序(ShellSort)及优化

659 阅读3分钟

冒泡排序及优化

选择排序(SelectionSort)

堆排序(HeapSort)

插入排序及优化

归并排序(MergeSort)

快速排序(QuickSort)

希尔排序(ShellSort)及优化

核心思想

假如有以下一组序列,需要使用希尔排序对其进行升序。

我们按照某个增量将序列分为多列。比如上面这个需要我们可以按照4、2、1将其分为2列、4列、8列。然后分别对其每列上的元素进行插入排序。这个增量是一系列数。希尔本身推荐的增量系数为1、2、4、8、16...等2的k次方。

如果增量序列是1、2、4、8...我取其反序列。优先使用大的。比如说4。我们按照每间隔4个元素分为一列。

如上图我们可以将原序列分为2行,其实就是4列。然后我们对没列进行插入排序。排完原序列就变成

我们再按照增量是2进行分列:

排序完 原序列就是 最后再按照增量为1分为1列,此时其实就是上面的序列。再进行一轮插入排序。 最终得到升序序列

我们看到希尔排序的其实是使用了插入排序。不过插入排序最外层使用的是for循环所以它的平均复杂度是O(N²)。而我们最外层是用的2的k次方来获取增量,所以平均复杂度是O(NlogN)。

也可以认为希尔排序是插入排序的一种优化写法

代码实现

  1. 获取增量序列

    /**
     * shell推荐的Step列
     *
     * @return step列
     */
    private List<Integer> shellStepQueue() {
        ArrayList<Integer> queue = new ArrayList<>();
        int step = array.length;
        while ((step >>= 1) > 0) {
            queue.add(step);
        }
        return queue;

    }

  1. 按着增量分列,然后对每列进行插入排序。

    private void sort(Integer step) {
        // col:第几列   结尾是step
        for (int col = 0; col < step; col++) {
            //将原有数据模拟的分为以step列
            //需注意欢迎每列在原来数组的位置:
            //对每列数据进行插入排序
            for (int begin = col + step; begin < array.length; begin += step) {
                int cur = begin;
                while (cur > col && cmp(cur, cur - step) < 0) {
                    swap(cur, cur -= step);
                }
            }
        }
    }

所有代码如下:


public class ShellSort<E extends Comparable<E>> extends Sort<E> {
    @Override
    protected void sort() {
        //获取步队列
        List<Integer> stepQueue = shellStepQueue();
        //对所有列执行插入排序
        for (Integer step : stepQueue) {
            sort(step);
        }
    }

    private void sort(Integer step) {
        // col:第几列   结尾是step
        for (int col = 0; col < step; col++) {
            //将原有数据模拟的分为以step列
            //需注意欢迎每列在原来数组的位置:
            //对每列数据进行插入排序
            for (int begin = col + step; begin < array.length; begin += step) {
                int cur = begin;
                while (cur > col && cmp(cur, cur - step) < 0) {
                    swap(cur, cur -= step);
                }
            }
        }
    }

    /**
     * shell推荐的Step列
     *
     * @return step列
     */
    private List<Integer> shellStepQueue() {
        ArrayList<Integer> queue = new ArrayList<>();
        int step = array.length;
        while ((step >>= 1) > 0) {
            queue.add(step);
        }
        return queue;

    }
  } 

优化

希尔排序复杂度是 O(NlogN),其实的logN就是增量队列获取方式导致的。我们只要优化增量序列获取方式就能优化时间复杂对。

下面这种写法是目前公认最优的增量序列获取方式。

 /**
     * 目前公认最优的Step获取方式
     *
     * @return step
     */
    private List<Integer> sedgewickStepSequence() {
        List<Integer> stepSequence = new LinkedList<>();
        int k = 0, step = 0;
        while (true) {
            if (k % 2 == 0) {
                int pow = (int) Math.pow(2, k >> 1);
                step = 1 + 9 * (pow * pow - pow);
            } else {
                int pow1 = (int) Math.pow(2, (k - 1) >> 1);
                int pow2 = (int) Math.pow(2, (k + 1) >> 1);
                step = 1 + 8 * pow1 * pow2 - 6 * pow2;
            }
            if (step >= array.length) break;
            stepSequence.add(0, step);
            k++;
        }
        return stepSequence;
    }

只需要将第一种写法中的增量序列获取方法替换即可。当然插入排序部分还可以优化

优化完的时间复杂度为O(n^(1.3—2))。