排序算法系列——八大排序算法对比分析

本系列最后一篇,综合分析下前面介绍的八种排序算法的效率,以及各自的适用情况。

下面先看看八种排序算法的时间复杂度表格:

图中八种排序被分成了两组,一组时间复杂度为O(n^2),另一组相对高效些。

下面先对第一组O(n^2)的四种排序算法进行对比,分别取数组长度为100,1000,10000,100000四个数量级,各个元素在0-10000000之间随机获取。下面看下结果的分析。

排序算法 长度=100 长度=1000 长度=10000 长度=100000
直接插入排序 535 2,198 135,773 16,554,053
希尔排序 308 703 3,639 39,769
直接选择排序 382 3,219 232,495 24,201,891
冒泡排序 525 5,377 475,865 62,703,335

从上表可以看出,以上四种算法效率最高的绝对是希尔排序,其次是直接插入排序,再就是直接选择排序,最后才是冒泡排序。事实证明冒泡排序真的不适合实际应用,实际使用中最优先选择希尔排序,但是如果要求稳定性的话就选择直接插入排序,但是效率差太多了。

下面主要对另外四个排序算法进行对比,不过通过上面的结果,所以在这里把希尔排序也加进来一起比较。由于基数排序对数组最大位数敏感,所以这里会分别对最大值为3位、4位、5位、6位、7位五种情况分别对应长度为10000,100000,1000000,10000000四种情况进行比较,长度太小体现不出O(nlgn)的优势。

排序算法 长度=10000 长度=100000 长度=1000000 长度=10000000
希尔排序 3996 36439 876530 12129001
堆排序 3451 38077 767878 10459868
快速排序 2446 37998 2566811 673399814
归并排序 2549 23595 356314 8325651
基数排序 4162 12724 272851 10866036

上表是MaxValue=1000时的结果,从表中可以看出当长度为10000时快速排序和归并排序效果最好,基数排序效果最差,但是当长度达到10W和100W基数排序效果最好,不过归并排序效率就比快速排序高很多了,同时快速排序当排序数组长度达到100W时效果变得很差,远远落后其他四个排序算法。所以当待排序数组最大值为1000时,数组长度为10000时使用快速和归并,当长度为1000W以内使用基数排序,或者归并排序。

排序算法 长度=10000 长度=100000 长度=1000000 长度=10000000
希尔排序 16349 201486 2800650 53033110
堆排序 3622 130898 3091654 51502613
快速排序 2366 83221 985588 71016772
归并排序 2544 60744 841400 20983723
基数排序 4815 42903 962442 15133291

以上是MaxValue=10000时的结果,从表中可以看出基数排序的效果最好,其次是归并排序,然后快速排序。所以当待排序数组最大值为10000时使用基数排序还是很不错的,虽然长度在10000时效果不如归并排序。当然归并排序也是个不错的选择。

排序算法 长度=10000 长度=100000 长度=1000000 长度=10000000
希尔排序 5107 203601 3401635 89081661
堆排序 5900 140530 4087559 76957182
快速排序 2873 89582 1553479 30429666
归并排序 3215 81579 1238016 21442519
基数排序 15017 65216 1116738 16308437

以上是MaxValue=100000时的结果,从表中可以看出结果同MaxValue=10000时的结果差不多。

排序算法 长度=10000 长度=100000 长度=1000000 长度=10000000
希尔排序 3701 246185 3496864 79870999
堆排序 3999 122765 3563651 69155734
快速排序 14974 45710 1351332 19350824
归并排序 3718 47956 1375705 23364515
基数排序 15001 37974 1290274 16083427

以上是MaxValue=1000000时的结果,结果还是一样,基数排序效果最好。

排序算法 长度=10000 长度=100000 长度=1000000 长度=10000000
希尔排序 4063 235374 3524810 92989117
堆排序 11444 155166 3461969 63201731
快速排序 7501 61635 1435866 19843452
归并排序 3376 42739 1322604 20859039
基数排序 6159 106208 1535261 25146006

以上是MaxValue=10000000时的结果,基数排序的效率已经没有归并排序好了,应该由于归并的次数增加了。

这里有个地方很奇怪,可以从上面MaxValue=1000时的表中可以看到当长度=100W时,快速排序的时间超过其他四个排序一个数量级,但是当MaxValue=10000,甚至更大之后快速排序的时间都和其它排序是一个数据量,而且MaxValue=1000时耗时大于MaxValue=10000以上。具体原因未知,大家可以自己测试,也许重复元素太多会影响快速排序的效率?

综合以上所有测试结果,总结各个排序算法的适用场景。

直接插入排序:直接用希尔排序替代就好,除非待排序数组本身就是部分有序

希尔排序: 效果最好,秒杀所有O(n^2)的排序算法,所在在数据量较小的场景下,如100000个元素以下都可考虑希尔排序

直接选择排序: 直接用希尔排序替代,或者用堆排序替代

冒泡排序: 强烈推荐不使用此排序算法

堆排序: 优于希尔排序,推荐替代希尔排序,但是如果待排序数组是部分有序的那么希尔优于堆排序

快速排序: 数组长度100W以下效率最高,100W以上可以用归并排序替代

归并排序: 不考虑基数排序的话,数组长度100W以上效率最高,100W以下可以用快速排序替代

基数排序: 适用场景要求较高,元素必须是整数,整数时长度10W以上,最大值100W以下效率较好,但是基数排序比其他排序好在可以适用字符串,或者其他需要根据多个条件进行排序的场景,例如日期,先排序日,再排序月,最后排序年 ,其它排序算法可是做不了的。

下面附上测试代码:

package com.vicky.sort;

import java.util.Random;
import java.util.Scanner;

import org.junit.Test;

public class SortComparison2 {

    /**
     * 比较全部排序算法
     */
    @Test
    public void testAll() {
        Scanner scan = new Scanner(System.in);
        int num = -1;
        int maxValue = -1;
        while (true) {
            // 从命令行输入元素数量,以及最大值,格式:10,1000,输入quit结束
            String input = scan.next();
            if ("quit".equals(input)) {
                System.exit(1);
            }
            String[] strs = input.split(",");
            num = Integer.parseInt(strs[0]);
            maxValue = Integer.parseInt(strs[1]);
            System.out.println("Sort Data Num = " + num + ", MaxValue = " + maxValue);

            Random ran = new Random();
            Integer[] data = new Integer[num];
            Integer[] data1 = new Integer[data.length];
            Integer[] data2 = new Integer[data.length];
            Integer[] data3 = new Integer[data.length];
            Integer[] data4 = new Integer[data.length];
            Integer[] data5 = new Integer[data.length];
            Integer[] data6 = new Integer[data.length];
            Integer[] data7 = new Integer[data.length];
            Integer[] data8 = new Integer[data.length];
            for (int i = 0; i < data.length; i++) {
                data[i] = ran.nextInt(maxValue);
                data1[i] = data[i];
                data2[i] = data[i];
                data3[i] = data[i];
                data4[i] = data[i];
                data5[i] = data[i];
                data6[i] = data[i];
                data7[i] = data[i];
                data8[i] = data[i];
            }
            // 插入排序
            long insertTimes = testStraightInsertionSort(data1);
            long shellTimes = testShellSort(data2);
            // 选择排序
            long selectTimes = testStraightSelectionSort(data3);
            long heapTimes = testHeapSort(data4);
            // 交换排序
            long bubbleTimes = testBubbleSort(data5);
            long quickTimes = testQuickSort(data6);
            // 归并排序
            long mergeTimes = testMergeSort(data7);
            // 基数排序
            long radixTimes = testRadixSort(data8);

            System.out.println("method       \ttime(ms)");
            System.out.println("InsertionSort\t" + insertTimes);
            System.out.println("ShellSort    \t" + shellTimes);
            System.out.println("SelectionSort\t" + selectTimes);
            System.out.println("HeapSort     \t" + heapTimes);
            System.out.println("BubbleSort   \t" + bubbleTimes);
            System.out.println("QuickSort    \t" + quickTimes);
            System.out.println("MergeSort    \t" + mergeTimes);
            System.out.println("RadixSort    \t" + radixTimes);
        }
    }

    /**
     *测试时间复杂度为O(n^2)的排序
     */
    @Test
    public void testBase() {
        Scanner scan = new Scanner(System.in);
        int num = -1;
        int maxValue = -1;
        while (true) {
            // 从命令行输入元素数量,以及最大值,格式:10,1000,输入quit结束
            String input = scan.next();
            if ("quit".equals(input)) {
                System.exit(1);
            }
            String[] strs = input.split(",");
            num = Integer.parseInt(strs[0]);
            maxValue = Integer.parseInt(strs[1]);
            System.out.println("Sort Data Num = " + num + ", MaxValue = " + maxValue);

            Random ran = new Random();
            Integer[] data = new Integer[num];
            Integer[] data1 = new Integer[data.length];
            Integer[] data2 = new Integer[data.length];
            Integer[] data3 = new Integer[data.length];
            Integer[] data4 = new Integer[data.length];
            for (int i = 0; i < data.length; i++) {
                data[i] = ran.nextInt(maxValue);
                data1[i] = data[i];
                data2[i] = data[i];
                data3[i] = data[i];
                data4[i] = data[i];
            }
            // 插入排序
            long insertTimes = testStraightInsertionSort(data1);
            long shellTimes = testShellSort(data2);
            // 选择排序
            long selectTimes = testStraightSelectionSort(data3);
            // 交换排序
            long bubbleTimes = testBubbleSort(data4);

            System.out.println("method       \ttime(ms)");
            System.out.println("InsertionSort\t" + insertTimes);
            System.out.println("ShellSort    \t" + shellTimes);
            System.out.println("SelectionSort\t" + selectTimes);
            System.out.println("BubbleSort   \t" + bubbleTimes);
        }
    }

    /**
     * 比较O(nlgn)左右的排序算法
     *
     * 这里把希尔加上是因为虽然希尔时间复杂度是O(n^2)但是从实际结果来看其效率还是较高的,所以拿来跟O(nlgn)一起对比
     */
    @Test
    public void testGood() {
        Scanner scan = new Scanner(System.in);
        int num = -1;
        int maxValue = -1;
        while (true) {
            // 从命令行输入元素数量,以及最大值,格式:10,1000,输入quit结束
            String input = scan.next();
            if ("quit".equals(input)) {
                System.exit(1);
            }
            String[] strs = input.split(",");
            num = Integer.parseInt(strs[0]);
            maxValue = Integer.parseInt(strs[1]);
            System.out.println("Sort Data Num = " + num + ", MaxValue = " + maxValue);

            Random ran = new Random();
            Integer[] data = new Integer[num];
            Integer[] data1 = new Integer[data.length];
            Integer[] data2 = new Integer[data.length];
            Integer[] data3 = new Integer[data.length];
            Integer[] data4 = new Integer[data.length];
            Integer[] data5 = new Integer[data.length];
            for (int i = 0; i < data.length; i++) {
                data[i] = ran.nextInt(maxValue);
                data1[i] = data[i];
                data2[i] = data[i];
                data3[i] = data[i];
                data4[i] = data[i];
                data5[i] = data[i];
            }
            // 插入排序
            long shellTimes = testShellSort(data1);
            // 选择排序
            long heapTimes = testHeapSort(data2);
            // 交换排序
            long quickTimes = testQuickSort(data3);
            // 归并排序
            long mergeTimes = testMergeSort(data4);
            // 基数排序
            long radixTimes = testRadixSort(data5);

            System.out.println("method       \ttime(ms)");
            System.out.println("ShellSort    \t" + shellTimes);
            System.out.println("HeapSort     \t" + heapTimes);
            System.out.println("QuickSort    \t" + quickTimes);
            System.out.println("MergeSort    \t" + mergeTimes);
            System.out.println("RadixSort    \t" + radixTimes);
        }
    }

    public static <T extends Comparable<T>> long testStraightInsertionSort(T[] data) {
        long start = System.nanoTime();
        StraightInsertionSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testShellSort(T[] data) {
        long start = System.nanoTime();
        ShellSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testStraightSelectionSort(T[] data) {
        long start = System.nanoTime();
        StraightSelectionSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testHeapSort(T[] data) {
        long start = System.nanoTime();
        HeapSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testBubbleSort(T[] data) {
        long start = System.nanoTime();
        BubbleSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testQuickSort(T[] data) {
        long start = System.nanoTime();
        QuickSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static <T extends Comparable<T>> long testMergeSort(T[] data) {
        long start = System.nanoTime();
        MergeSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }

    public static long testRadixSort(Integer[] data) {
        long start = System.nanoTime();
        RadixSort.sort(data);
        return (System.nanoTime() - start) / 1000;
    }
}

运行时需要指定JVM运行参数:-Xms1024m -Xmx1024m -Xss2048k。

以上测试可能会有偏差,

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-25 16:57:54

排序算法系列——八大排序算法对比分析的相关文章

趣写算法系列之--匈牙利算法(真的很好理解)

[书本上的算法往往讲得非常复杂,我和我的朋友计划用一些简单通俗的例子来描述算法的流程] 匈牙利算法是由匈牙利数学家Edmonds于1965年提出,因而得名.匈牙利算法是基于Hall定理中充分性证明的思想,它是部图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法. -------等等,看得头大?那么请看下面的版本: 通过数代人的努力,你终于赶上了剩男剩女的大潮,假设你是一位光荣的新世纪媒人,在你的手上有N个剩男,M个剩女,每个人都可能对多名异性有好感(-_-

【数据结构&amp;&amp;算法系列】KMP算法介绍及实现(c++ &amp;&amp; java)

KMP算法如果理解原理的话,其实很简单. KMP算法简介 这里根据自己的理解简单介绍下. KMP算法的名称由三位发明者(Knuth.Morris.Pratt)的首字母组成,又称字符串查找算法. 个人觉得可以理解为最小回溯算法,即匹配失效的时候,尽量少回溯,从而缩短时间复杂度. KMP算法有两个关键的地方,1)求解next数组,2)利用next数组进行最小回溯. 1)求解next数组 next数组的取值只与模式串有关,next数组用于失配时回溯使用. 在简单版本的KMP算法中,每个位置 j 的 n

数据结构与算法之——八大排序算法

附:关于这个主题,网上好的文章已经数不胜数,本篇是整合后的文章. 正文: 一.概述 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 本文所指八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 二.排序算法详述 1.

数据结构之排序算法(八大排序)-(八)

排序算法可以分为稳定排序和不稳定排序.在简单形式化一下,如果A[i] = A[j],A[i]原来在位置前,排序后A[i]还是要在A[j]位置前,这才能叫稳定排序.排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用.基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的.另外,如果排序算法稳定,对基于比较的排序算法而言,元素交换的次数可能会少一些(个人感觉,没有证实). 回到主题,现在分析一下常见的排序

排序算法系列——希尔排序

希尔排序同之前介绍的直接插入排序一起属于插入排序的一种.希尔排序算法是按其设计者希尔(Donald Shell)的名字命名,该算法由1959年公布,是插入排序的一种更高效的改进版本.它的作法不是每次一个元素挨一个元素的比较.而是初期选用大跨步(增量较大)间隔比较,使记录跳跃式接近它的排序位置:然后增量缩小:最后增量为 1 ,这样记录移动次数大大减少,提高了排序效率.希尔排序对增量序列的选择没有严格规定. 希尔排序是基于插入排序的以下两点性质而提出改进方法的: 插入排序在对几乎已经排好序的数据操作

三白话经典算法系列 Shell排序实现

山是包插入的精髓排序排序.这种方法,也被称为窄增量排序,因为DL.Shell至1959提出命名. 该方法的基本思想是:先将整个待排元素序列切割成若干个子序列(由相隔某个"增量"的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序. 由于直接插入排序在元素基本有序的情况下(接近最好情况),效率是非常高的,因此希尔排序在时间效率上比前两种方法有较大提高. 以n=10的一个数组49, 38, 65, 97

排序算法系列:插入排序算法

概述 直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的.记录数增1的有序表. – <大话数据结构> 版权说明 著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 本文作者:Coding-Naga 发表日期: 2016年3月24日 原文链接:http://blog.csdn.net/lemon_tree12138/article/details/50968422 来源:CSDN 更多内容:分类 &

排序算法系列:快速排序算法

概述 在前面说到了两个关于交换排序的算法:冒泡排序与奇偶排序. 本文就来说说交换排序的最后一拍:快速排序算法.之所以说它是快速的原因,不是因为它比其他的排序算法都要快.而是从实践中证明了快速排序在平均性能上的确是比其他算法要快一些,不然快速一说岂不是在乱说? 本文就其原理.过程及实现几个方面讲解一下快速排序算法. 版权声明 著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:Coding-Naga 发表日期:2016年3月1日 链接:http://blog.csdn.n

数据结构与算法系列二(复杂度分析)

1.引子 1.1.为什么要学习数据结构与算法? 有人说,数据结构与算法,计算机网络,与操作系统都一样,脱离日常开发,除了面试这辈子可能都用不到呀! 有人说,我是做业务开发的,只要熟练API,熟练框架,熟练各种中间件,写的代码不也能“飞”起来吗? 于是问题来了:为什么还要学习数据结构与算法呢? #理由一: 面试的时候,千万不要被数据结构与算法拖了后腿 #理由二: 你真的愿意做一辈子CRUD Boy吗 #理由三: 不想写出开源框架,中间件的工程师,不是好厨子 1.2.如何系统化学习数据结构与算法?