算法笔记-排序01:选择排序,插入排序,希尔排序

实现两种初级的排序算法:

选择排序思路

首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果最小的就是第一个那就自己跟自己交换)。再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。

完整代码
public class Selection {

    public static void sort(Comparable[] a){
        for (int i = 0; i < a.length; i++){
            for (int j = i+1; j < a.length;j++){
                if (less(a[j], a[i])) exchange(a,i,j);
            }
        }
        show(a);
    }
    
    private static boolean less(Comparable v, Comparable w){
        return v.compareTo(w) < 0;
    }
    
    private static void exchange(Comparable[] a, int i, int j){
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    
    
    private static void show(Comparable[] a){
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i]);
        System.out.println();
    }
    
    public static boolean isSorted(Comparable[] a){
        for (int i = 1; i < a.length; i++){
            if (less(a[i],a[i-1])) return false;
        }
        return true;
    }    
}
分析

这种实现十分简单且容易理解的算法有两个特点:
1.运行时间和输入无关:即使输入了一个已经有序的数组,这个算法依然会从头到尾的比较一遍,也就是说,没有利用到数组的初始状态。
2.数据移动较小:最多只进行N次交换。

插入排序思路与分析

从序列的第二个元素开始,向前进行比较,如果第二个元素比第一个小,那么将它们对换,然后从第三个元素开始,如果第三个元素比第二个大,那么直接向后从第四个开始,如果第三个元素比第二个小,那么将它们对换,然后又比较第二个和第一个,完成后向后从第四个开始,在这种情况下,如果数组是部分有序的,这种算法就因为可以省略掉一些向前进行的比较从而加快速度。事实上,当序列中倒置的元素非常少时,这个算法可能比其他任何算法都快。

完整代码
public class Insertion {
    
    public static void sort(Comparable[] a){
        for (int i = 1; i < a.length; i++){
            for (int j = i; j > 0 && less(a[j], a[j-1]);j--) exchange(a,j,j-1);
        }
        show(a);
    }
    
    private static boolean less(Comparable v, Comparable w){
        return v.compareTo(w) < 0;
    }
     
    private static void exchange(Comparable[] a, int i, int j){
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
     
    private static void show(Comparable[] a){
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i]);
        System.out.println();
    }
   
    public static boolean isSorted(Comparable[] a){
        for (int i = 1; i < a.length; i++){
            if (less(a[i],a[i-1])) return false;
        }
        return true;
    }
}

比较两种初级的排序算法:

在实际应用中插入排序都比选择排序要快一些,往往是快一倍左右(使用尽量大的数据更容易得到这个结果,比如大小为一万的数组重复一千次)。

测试代码

1.用于计时的类

public class StopWatch {
    private final long start;
    
    public StopWatch(){
        start = System.currentTimeMillis();
    }
    
    public double elapsedTime(){
        long now = System.currentTimeMillis();
        return (now-start)/1000.0;
    }
}

用于比较两种排序算法运行时间的测试代码
public class Main {
    
    public static void main(String[] args) {
        String alg1 = args[0];
        String alg2 = args[1];
        int N = Integer.parseInt(args[2]);
        int T = Integer.parseInt(args[3]);
        double t1 = timeRandomInput(alg1,N,T);
        double t2 = timeRandomInput(alg2,N,T);
        System.out.println(alg1 + " : " + t1);
        System.out.println(alg2 + " : " + t2);
    }
    
    public static double timeRandomInput(String alg, int N, int T){
        double total = 0.0;
        Double[] a = new Double[N];
        for (int t = 0; t < T; t++){
            for (int i = 0; i < N; i++) a[i] = Math.random();
            total += time(alg,a);
        }
        return total;
    }
    
    public static double time(String alg, Double[] a){
        StopWatch timer = new StopWatch();
        if (alg.equals("Insertion")) Insertion.sort(a);
        if (alg.equals("Selection")) Selection.sort(a);
        if (alg.equals("Shell"))Shell.sort(a); 
        return timer.elapsedTime();
        
    }
}
运行命令
% javac Insertion.java Main.java Selection.java StopWatch.java
% java Main Insertion Selection 10000 1000

代码比较简单,执行用例所需的参数为两种算法的名称,数组大小,循环次数(循环进行用户指定大小的随机数组的生成及排序),输出结果是两种算法各自运行的时间。在笔者的电脑上输出是这样的

输出结果

这验证了插入排序往往比选择排序快一倍的说法。然而,这两种算法对于大规模的乱序数组都是非常慢的(等上面的输出等了老半天,不过满屏的数字很有黑客帝国的感觉)。

一种改进:希尔排序

希尔排序是对插入排序的一个改进,我认为对于这种算法比较好的一种理解方式就是假装自己是一台计算机,然后假装收到了一条需要为一个容量为10的乱序数组排序的命令,再按照下面的代码在草稿纸上一步一步地去运行,自然就明白了,参见维基百科
代码中除了sort方法于上面不同之外其他部分都一样:

public class Shell {
    public static void sort(Comparable[] a){
        int N = a.length;
        int h = 1;
        while (h < N/3) h = 3*h + 1;
        while (h >= 1){
            for (int i = h; i < N; i++){
                for (int j = i; j > h &&less(a[j], a[j-h]); j -= h)exchange(a,j,j-h);
            }
            h/=3;
        }
    }
    
    private static boolean less(Comparable v, Comparable w){
        return v.compareTo(w) < 0;
    }
     
    private static void exchange(Comparable[] a, int i, int j){
        Comparable t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
    
    private static void show(Comparable[] a){
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i]);
        System.out.println();
    }
    
    public static boolean isSorted(Comparable[] a){
        for (int i = 1; i < a.length; i++){
            if (less(a[i],a[i-1])) return false;
        }
        return true;
    }
}

测试一下改进后算法的速度:

运行命令
% javac Shell.java
% java Main Insertion Shell 10000 1000 
运行结果

希尔排序比插入排序快的不是一点点。

问:我去,这是为什么?为什么多跳了几遍会比直接排快?为什么一个小小的改变会带来这么大的差距?
答:不知道。

算法一书原文:“The study of the performance characteristics of shellsort requires mathematical arguments that are beyond the scope of this book.”(这题超纲了)

维基百科:“希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)”。

对于中等大小的问题,希尔排序的运行时间是可以接受的,他的代码量很小而且不需要额外的存储空间,砖家推荐一般先用希尔排序实现,在发现性能不行的时候再考虑改进。对于大型问题,就需要接下来的算法了。

资源以及参考

普林斯顿大学算法课程以其教材《算法》第四版
维基百科

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容