插入排序比shell排序快得多

时间:2014-02-27 07:10:10

标签: java algorithm performance sorting shellsort

我正在阅读关于在Sedgewick的“算法”中排序的章节。在此过程中,我编写了3种基本排序算法:选择,插入和shell排序。该书说,尽管所有三个都具有二次最坏情况复杂性,但shell排序应该比随机数据上的插入排序快得多。在书中,它们的性能提升了600倍。

但是我在笔记本电脑上得到了以下乘数(几乎不随着数组大小的增加而改变):

  • 选择:5.5x
  • 插入:1x
  • shell:1.8x!

困扰我的问题是 - 为什么shell排序几乎比插入排序慢两倍?!

我想,我的shellort实现有问题。但我几乎从书中复制了它:

class ShellSort extends Sort {
    //precalculate sequence: 1, 4, 13, 40, 121, 364, 1093, ...
    //(3^20 - 1)/2 is enough for any array I sort
    private static final int[] SEQUENCE = new int[20];
    static {
        for(int i = 1; i <= SEQUENCE.length; i++)
            SEQUENCE[i - 1] = (int)(Math.pow(3, i) - 1) / 2;
    }

    public void sort(int[] a) {
        int length = a.length;

        int seqLen = SEQUENCE.length;
        int nth;
        int j;

        for(int seqi = seqLen - 1; seqi >= 0; seqi--) {
            if(SEQUENCE[seqi] < length / 3) {
                nth = SEQUENCE[seqi];
                for(int n = 0; n < length; n+=nth) {
                    j = n;
                    while(j > 0 && a[j] < a[j - nth]) {
                        exch(a, j, j-nth);
                        j -= nth;
                    }
                }
            }
        }
    }
}

那些希望在他们的机器上运行测试的人的其余代码(使用JVM加热的双倍阵列大小测试对结果没有显着影响,所以这个简单的测试对N&gt;来说已经足够了。 ~200 000)

主要

int N = 500_000;
Random rand = new Random();
int[] a = new int[N];
for(int i = 0; i < N; i++)
    a[i] = rand.nextInt();

//insertion sort
int[] aCopy = Arrays.copyOf(a, a.length);
long start = System.nanoTime();
new InsertionSort().sort(aCopy);
System.out.println("insert:\t" + (System.nanoTime() - start));

//shell sort
aCopy = Arrays.copyOf(a, a.length);
start = System.nanoTime();
new ShellSort().sort(aCopy);
System.out.println("shell:\t" + (System.nanoTime() - start));

InsertionSort和Sort类:

class InsertionSort extends Sort {
    public void sort(int[] a) {
        int length = a.length;
        int j;
        int x;
        for(int i = 1; i < length; i++) {
            j = i;
            x = a[i];
            while(j > 0 && x < a[j-1]) {
                a[j] = a[--j];
            }
            a[j] = x;
        }
    }
}
abstract class Sort {
    abstract public void sort(int[] a);

    protected static final void exch(int[] a, int i, int j) {
        int t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}

4 个答案:

答案 0 :(得分:3)

通过快速浏览,您可以看到通过拥有更多循环,shell排序看起来更慢。 蛮力,您可以将system.out.println放在最里面的循环中,以查看进行了多少次比较。

3次炮弹循环

  • for(int seqi = seqLen - 1; seqi&gt; = 0; seqi - )
  • for(int n = 0; n&lt; length; n + = nth)
  • while(j> 0&amp;&amp; a [j]&lt; a [j - nth])

2次插入

  • for(int i = 1; i&lt; length; i ++)
  • while(j&gt; 0&amp; x&amp; x&lt; a [j-1])

答案 1 :(得分:3)

您的实现已中断,并且仅输出已排序的数组,因为最后一步为1,而您的两个内部循环在步骤为1时执行基本插入排序。 当步长大于1时,实现中的两个内部循环除了对数组进行步骤排序之外什么做任何事情,所以你实现的是它在外循环的所有迭代中对数组进行洗牌,然后在最后一次插入中对它进行排序外循环的迭代。当然,只需插入一次就可以花费更长的时间。

重用您的序列正确的shell排序实现应如下所示:

public void sort( int[] a ) {
    int length = a.length;

    int stepIndex = 0;
    while ( stepIndex < SEQUENCE.length - 1 && SEQUENCE[ stepIndex ] < length / 3 ) {
        stepIndex++;
    }

    while ( stepIndex >= 0 ) {
        int step = SEQUENCE[ stepIndex-- ];
        for ( int i = step; i < length; i++ ) { // DIFF: i++ instead of i+=step
            for ( int j = i; j >= step && a[ j ] < a[ j - step ]; j -= step ) {
                exch( a, j, j - step );
            }
        }
    }
}

此实现与您的实现之间存在两个主要差异:

  • 两个内部循环的正确初始索引
  • 中间周期的正确索引增量(+1代替代码中的+步骤)

另外,请检查http://algs4.cs.princeton.edu/21elementary/Shell.java.html以获得良好的实施和良好的步骤顺序。

答案 2 :(得分:0)

我相信理由是缓存。 Shell排序有很多(有点)随机访问,因此更多的缓存未命中。我相信使用更新的硬件会带来更糟糕的性能。插入排序几乎总是在相同的内存区域上工作,因此它表现得更好

答案 3 :(得分:0)

您对Shellsort的实现不正确。您没有将每个元素与数组中的给定步骤进行比较。您还应该仅在中等大小的数组上使用插入排序和外壳排序,以充分利用它们。您可以使用泛型删除所有警告。您也可以参考此递增顺序以获得更好的结果。

这里是要执行相同操作的shell排序:

public class ShellSort<T> {

    //Knuth's function for shell sort
    //0(n^3/2) in practice much better
    public static List<Integer> shellSortIncrementSeqFunction(int length) {
        List<Integer> res = new ArrayList<>();
        for(int i = 0; i < length; i++)
                res.add(3 * i + 1);
        return res;
    }

    //Good function tough to compete
    private List<Integer> shellSortIncrementSeqFunctionGood(int length) {
        int argForFunction1 = 0;
        int argForFunction2 = 2;
        List<Integer> res = new ArrayList<>();
        while(true) {
            int val1 = shellSortArgFunction1(argForFunction1);
            int val2 = shellSortArgFunction2(argForFunction2);
            if(val1 < val2) {
                if(val1 >= length)
                    break;
                res.add(val1);
                argForFunction1++;
            } else {
                if(val2 >= length)
                    break;
                res.add(val2);
                argForFunction2++;
            }
        }
        return res;
    }

    private int shellSortArgFunction1(int arg) {
        return (int)(9 * Math.pow(4, arg) - 9 * Math.pow(2, arg) + 1);
    }

    private int shellSortArgFunction2(int arg) {
        return (int)(Math.pow(4, arg) - 3 * Math.pow(2, arg) + 1);
    }


    @SuppressWarnings("unchecked")
    private boolean less(Comparable<T> thisComparable, Comparable<T> thatComparable) {
        return thisComparable.compareTo((T) thatComparable) < 0;
    }

    private void exchange(Object[] comparableArray, int thisIndex, int thatIndex) {
        Object temp = comparableArray[thisIndex];
        comparableArray[thisIndex] = comparableArray[thatIndex];
        comparableArray[thatIndex] = temp;
    }

    public boolean isSorted(Comparable<T>[] comparableArray) {
        boolean res = true;
        int len = comparableArray.length;
        for(int i = 1; i < len; i++)
            res &= !less(comparableArray[i], comparableArray[i - 1]);
        return res;
    }

    public void shellSort(Comparable<T>[] comparableArray) {
        int len = comparableArray.length;
        List<Integer> incrementSequence = shellSortIncrementSeqFunctionGood(len);
        int seqLen = incrementSequence.size() - 1;
        for(int i = seqLen; i >= 0; i--) {
            int step = incrementSequence.get(i);
            //h-sorting the array
            for(int j = step; j < len; j++)
                for(int k = j; k >= step && less(comparableArray[k], comparableArray[k-step]); k -= step)
                    exchange(comparableArray, k, k-step);
        }
        assert isSorted(comparableArray);
    }

    private void show(Comparable<T>[] comparableArray) {
        Arrays.stream(comparableArray).forEach(x -> System.out.print(x + ", "));
        System.out.println();
    } 

}

您可以查看此帖子here的来源。