《算法》笔记 3 - 选择排序、插入排序、希尔排序

  • 排序通用代码
  • 选择排序
  • 插入排序
  • 希尔排序

排序通用代码

通用代码支持任意实现了Comparable接口的数据类型的排序,不同的排序算法的差异体现在sort方法的实现上。

public class Selection {
    public static void sort(Comparable[] a) {
        //待实现
    }

    private static boolean less(Comparable a, Comparable b) {
        return a.compareTo(b) < 0;
    }

    private static void exch(Comparable[] a, int i, int j) {
        Comparable swap = a[i];
        a[i] = a[j];
        a[j] = swap;
    }

    private static boolean isSorted(Comparable[] a) {
        for (int i = 1; i < a.length; i++) {
            if (less(a[i], a[i - 1])) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        int[] input = StdIn.readAllInts();
        Integer[] a1 = new Integer[input.length];
        for (int i = 0; i < input.length; i++) {
            a1[i] = input[i];
        }
        sort(a1);
        assert isSorted(a1);
    }
}

选择排序

选择排序的过程是:找到数组中最小的那个元素,然后将它和数组中的第一个元素交换位置,如果第一个元素就是最小的元素,就和它自己交换。接着在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置,如此反复,直到处理完数组的所有元素。

public static void sort(Comparable[] a) {
    int minIndex = 0;
    for (int i = 0; i < a.length; i++) {
        // min = a[i];
        minIndex = i;
        for (int j = i + 1; j < a.length; j++) {
            if (less(a[j], a[minIndex])) {
                // min = a[j];
                minIndex = j;
            }
        }
        exch(a, i, minIndex);
    }
}

算法特点

排序算法的开销主要包括数组元素的交换和比较两方面。
选择排序的交换次数为N,等于数组的规模。
选择排序的比较次数可以从算法的执行过程得知,外层循环N次,每次排好一个元素,下一次的内层循环的起始索引加1,则比较次数减1,所以一共比较(N-1)+(N-2)+...+(1)=(N-1)*(N-2)/2次,~N^2/2

选择排序算法的增长数量级是平方级别的,此外这种算法还有如下特点:

  • 运行时间与输入无关
    为了找出最小的元素而扫描一遍数组并不能为下一次扫描提供什么信息,所以排序随机元素组成的数组和排序元素已经有序的数组或元素值全部相同的数组所用的时间是一样的。
  • 数据移动次数是所有排序算法中最少的
    选择排序交换次数和数组的大小是线性关系,随后学习的其它排序算法都不具备这个特性,大部分的增长数量级是线性对数或者平方级别的。

插入排序

将纸牌排序时,一般采用的方法是一张一张来,把当前的这张牌插入到已经排好序的牌中的合适位置。插入排序的原理与这个类似。唯一的区别是:将一个元素插入数组中的某个位置时,这个位置之后的所有元素都需要向后移动一位。

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--) {
            exch(a, j, j - 1);
        }
    }
}

算法特点

插入排序所需的时间则是与输入数组的特点有很大关系的,最快的时候可以在线性时间内完成,最慢的时候却达到平方级别。
最好情况既输入本身已经是有序的,那么外层循环会执行N-1次,每次循环都会执行less(a[j], a[j - 1]),所以需要比较N-1次,但进入内层循环的条件都不会满足,则交换0次。
最坏的情况则是输入是倒序的,那么从索引=1开始,每个元素都会被移动,且每次比较都会对应一次移动,比较与移动的次数相等。第二个元素比较1次,第三个较2次,第4个3次,...第N个N-1此,一共约N^2/2次比较,N^2/2次移动;
那么在输入元素随机的情况下,可以认为平均每个元素都可能移动半个数组的距离,所需的比较、移动次数为最坏情况的一般,约N^2/4次比较,N^2/4次移动。
插入排序的特点,决定了它非常适用于实际应用中常见的部分有序的数组的排序。

希尔排序

希尔排序是对插入排序的改进。插入排序对于随机输入的增长数量级在平方级别,对于规模较大的问题其运行时间会比较慢。因为插入排序只会交换相邻的元素,元素只能一点一点地从当前位置移动到其应该呆的位置。
希尔排序针对这一的进行了改进,增加了元素移动的跨度。希尔排序的思想是使数组中任意间隔为h的元素都是有序的,这样的数组被称为h有序数组。一个h有序数组就是h个互相独立的有序数组编织在一起组成的数组。如下所示,h为2时,数组可以分为2个独立的子数组,将这两个子数组排序后,再进行一次排序,整个数组就是有序的了。

L   E   E   A   M   H   L   E  
L-------E-------M-------L      
    E-------A-------H-------E      

如果h很大,则可以把移动到很远的地方。希尔排序在运行时,先使用较大的h进行粗排,然后按照一定的规律逐步减小h,当h减小到1时,即完成了数组的最终排序。这样做有什么效果呢,多轮排序会不会反而速度更慢呢?实际上希尔排序的比插入排序快得多。这是因为希尔排序权衡了子数组的规模和有序性,排序之处,各个子数组都很短,排序后期子数组又都是部分有序的,插入排序在这两种情况下都特别适合。
如下为希尔排序的实现,h序列选择了最常用的3*h+1序列,即1,4,13,40,121,364,1093...

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) {
                exch(a, j, j - h);
            }
        }
        h /= 3;
    }
}

算法特点

h序列的选择对希尔排序的性能影响很大,但透彻理解希尔排序的性能至今仍然是一项挑战,有很多论文研究了各种不同的地址序列,但都无法证明某个序列是“最好的”。
可以通过比较三种算法的时间来直观地感受不同算法的差异,下面是处理10万条int数组时的时间对比:

Selection, 37.595 s
Insertion, 76.14 s
Shell, 0.136 s

由此可见,希尔排序相比选择排序和插入排序要高效得多。对于中等大小规模的数组,希尔排序的运行时间是可接受的,其它一些更高级的算法可能只会比希尔排序快2倍,但这些算法的代码却更复杂。在没有可用的系统排序算法可用,或者为嵌入式系统编写代码时,可以考虑使用希尔排序。

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

推荐阅读更多精彩内容