算法(第4版)-2.1 初级排序算法

2.1.1 游戏规则

1. 排序成本模型:在研究排序算法时,我们需要计算比较和交换的数量。对于不交换元素的算法,我们会计算访问数组的次数。

2.

· 原地排序算法:除了函数调用所需的栈和固定数目的实例变量之外无需额外内存的原地排序算法;

· 其他排序算法:需要额外内存空间来储存另一份数组副本。

2.2.2 选择排序

public class Selection {
    public static void sort(Comparable[] a) {
        // 将a[]按升序排列
        int N = a.length;    // 数组长度
        for (int i = 0; i < N; i++) {
            // 将a[i]和a[i+1..N]中最小的元素交换
            int min = i;
            for (int j = i + 1; j < N; j++)
                if (less(a[j], a[min]))    min = j;
            exch(a, i, min);
        }
    }

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

    private static void exch(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++)
            StdOut.print(a[i] + " ");
        StdOut.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;
    }

    public static void main(String[] args) {
        // 从标准输入读取字符串,将它们排序并输出
        String[] a = In.readStrings();
        sort(a);
        assert isSorted(a);
        show(a);
    }
}

Selection

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

2. 对于长度为N的数组,选择排序需要大约N^2/2次比较和N次交换。

3. 选择排序有两个很鲜明的特点:

· 运行时间和输入无关(一个已经有序的数组或主键全部相等的数组和一个元素随机排列的数组所用的排序时间竟然一样长);

· 数据移动是最少的(选择排序用了N次交换--线性级别,大部分其他算法的交换次数--线性对数或是平方级别)。

2.2.3 插入排序

public class Insertion {
    public static void sort(Comparable[] a) {
        // 将a[]按升序排列
        int N = a.length;
        for (int i = 1; i < N; i++) {
            // 将a[i]插入到a[i-1]、a[i-2]、a[i-3]...之中
            for (int j = i; j > 0 && less(a[j], a[j - 1]); j--)
                exch(a, j, j - 1);
        }
    }

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

    private static void exch(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++)
            StdOut.print(a[i] + " ");
        StdOut.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;
    }

    public static void main(String[] args) {
        // 从标准输入读取字符串,将它们排序并输出
        String[] a = In.readStrings();
        sort(a);
        assert isSorted(a);
        show(a);
    }
}

Insertion

1. 与冒泡排序的区别:

· 插入排序:将每一个元素插入到其他已经有序的元素中的适当位置;

· 冒泡排序:每次将剩余最大的元素向右边推过去。

2. 插入排序所需的时间取决于输入中元素的初始顺序。

3. 对于随机排序的长度为N且主键不重复的数组,插入排序:

· 平均情况下需要~N^2/4次比较以及~N^2/4次交换

· 最坏情况下需要~N^2/2次比较以及~N^2/2次交换

· 最好情况下需要N-1次比较和0次交换

4. 下面是几种典型的部分有序的数组:

· 数组中每个元素距离它的最终位置都不远

· 一个有序的大数组接一个小数组

· 数组中只有几个元素的位置不正确

插入排序对这样的数组很有效,而选择排序则不然。

2.1.4 排序算法的可视化

1. 插入排序所需的比较次数平均只有选择排序的一半。

2.1.5 比较两种排序算法

public class SortCompare {
    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);
        // if (alg.equals("Merge"))            Merge.sort(a);
        // if (alg.equals("Quick"))            Quick.sort(a);
        // if (alg.equals("Heap"))                Heap.sort(a);
        return timer.elapsedTime();
    }

    public static double timeRandomInput(String alg, int N, int T) {
        // 使用算法alg将T个长度为N的数组排序
        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] = StdRandom.uniform();
            total += time(alg, a);
        }
        return total;
    }

    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);    // 算法1的总时间
        double t2 = timeRandomInput(alg2, N, T);    // 算法2的总时间
        StdOut.printf("For %d random Doubles\n    %s is", N, alg1);
        StdOut.printf(" %.1f times faster than %s\n", t2 / t1, alg2);
    }
}

/*
java SortCompare Insertion Selection 1000 100
For 1000 random Doubles
    Insertion is 1.7 times faster than Selection
*/

SortCompare

这个用例会运行由前两个命令行参数指定的排序算法,对长度为N(由第三个参数指定)的

1. 对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比应该是一个较小的常数。

2.1.6 希尔排序

public class Shell {
    public static void sort(Comparable[] a) {
        // 将a[]按升序排列
        int N = a.length;
        int h = 1;
        while (h < N / 3)    h = 3 * h + 1;    // 1, 4, 13, 40, 121, 364, 1093, ...
        while (h >= 1) {
            // 将数组变为h有序
            for (int i = h; i < N; i++) {
                // 将a[i]插入到a[i-h],a[i-2*h],a[i-3*h]...之中
                for (int j = i; j >= h && less(a[j], a[j - h]); j -= h)
                    exch(a, j, j - h);
            }
            h = h / 3;
        }
    }

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

    private static void exch(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++)
            StdOut.print(a[i] + " ");
        StdOut.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;
    }

    public static void main(String[] args) {
        // 从标准输入读取字符串,将它们排序并输出
        String[] a = In.readStrings();
        sort(a);
        assert isSorted(a);
        show(a);
    }
}

Shell

1. 思想:使数组中任意间隔为h的元素都是有序的。

2. 通过提升速度来解决其他方式无法解决的问题是研究算法的设计和性能的主要原因之一。

3. 希尔排序的运行时间达不到平方级别。例如,已知在最坏的情况下算法2.3的比较次数和N^(3/2)成正比。

4. 希尔排序对于中等大小的数组它的运行时间是可以接受的。它的代码量很小,且不需要使用额外的内存空间。

5.如果你需要解决一个排序问题而又没有系统排序函数可用(例如直接接触硬件或是运行于嵌入式系统中的代码),可以先用希尔排序,然后再考虑是否值得将它替换为更加复杂的排序算法。

时间: 2024-12-20 10:06:23

算法(第4版)-2.1 初级排序算法的相关文章

深入理解排序算法(一):初级排序算法

[本系列博文会对常见的排序算法进行分析与总结,并会在最后提供几道相关的一线互联网企业面试/笔试题来巩固所学及帮助我们查漏补缺.项目地址:https://github.com/absfree/Algo.由于个人水平有限,叙述中难免存在不清晰准确的地方,希望大家可以指正,谢谢大家:)] 一.概述 我们在日常开发中经常需要对一组数据对象进行排序,这里的数据对象不仅包括数字,还可能是字符串等抽象数据类型(Abstract Data Type).由于排序是很多其他操作(比如二分查找)能够高效进行的基础,因

初级排序算法之选择排序

初级排序算法 本质是对要排序的数组进行嵌套循环,内层循环负责局部的排序,外层循环负责剩余的无序元素的递减.所以你只要理解嵌套循环和比较大小就能很快的掌握初级排序算法. 选择排序 一个无序的数组 a = [0, 4, 6, 3, 8, 2, 3, 9], 你也可以把a的元素想象成任何现实中可比较的具体物体.例如,有10根长短不一的木条,我们如何对它们进行排序?一个最直接的思想,先拿出最短的放到最前面,在剩余的木条中再拿出最短的放在第二位...直到最后一根木条.从中我们可以看出,1. 我们需要再一次

[读书笔记]算法(Sedgewick著)·第二章.初级排序算法

本章开始学习排序算法 1.初级排序算法 先从选择排序和插入排序这两个简单的算法开始学习排序算法.选择排序就是依次找到当前数组中最小的元素,将其和第一个元素交换位置,直到整个数组有序. 1 public static void sort(Comparable a[]){ 2 int N = a.length; 3 for(int i = 0; i < N; i ++){ 4 int min = i; //最小元素索引 5 for(int j = i + 1; j < N; j++){ 6 if(

算法手记(5)初级排序算法

排序是将一组对象按照一定的规则重新排列的过程.即使目前完全可以使用标准库中的排序函数,学习排序算法仍然有着较大意义:   排序算法的学习可以帮助你全面了解比较算法性能的方法: 类似的技术上能有效解决其他类型的问题: 排序算法通常是我们解决问题的第一步: 更重要的是这些算法都很经典,优雅和高效. 排序在商业数据处理分析和现代科学中占有重要的地位,其中快速排序算法被誉为20世纪科学和工程领域十大算法之一.今天我们要看的就是相对简单但很经典的初级排序算法,包括选择排序,插入排序及Shell排序. 准备

JavaScript版几种常见排序算法

今天发现一篇文章讲“JavaScript版几种常见排序算法”,看着不错,推荐一下原文:http://www.w3cfuns.com/blog-5456021-5404137.html 算法描述: * 冒泡排序:最简单,也最慢,貌似长度小于7最优* 插入排序: 比冒泡快,比快速排序和希尔排序慢,较小数据有优势* 快速排序:这是一个非常快的排序方式,V8的sort方法就使用快速排序和插入排序的结合* 希尔排序:在非chrome下数组长度小于1000,希尔排序比快速更快* 系统方法:在forfox下系

初级排序算法1-定义排序规则

初级排序算法-定义排序规则 排序就是将一组对象按照某种逻辑序列重新排列的过程. Table of contents 介绍 为什么学它 排序算法类的模板 验证 性能评估 介绍 现在计算机的广泛使用使得数据无处不在,而整理数据的第一步通常就是进行排序 所有的计算机都实现了各种排序算法以供系统和用户使用 为什么学它 即使你只是使用标准库中的排序算法,学习排序算法仍然有三大实际意义 对排序算法的分析将有助于你全面理解比较算法性能的方法 类似的技术也能有效解决其他类型的问题 排序算法常常是我们使用算法解决

算法(第四版)学习笔记(二)——初级排序算法

时间复杂度(Time Complexity): 总运算次数表达式中受n的变化影响最大的那一项(不含系数)(注:若算法中语句执行次数为一个常数,则时间复杂度为O(1)) 若T(n)/f(n)求极限可得到一常数c,则时间复杂度T(n)=O(f(n)). 一个算法中的语句执行次数称为语句频度或时间频度.记为T(n) 算法的基本操作重复执行的次数是模块n的某一个函数f(n) 随着模块n的增大,算法执行的时间的增长率和f(n)的增长率成正比,所以f(n)越小,算法的时间复杂度越低,算法的效率越高. 空间复

初级排序算法

我们关注的主要对象是重新排列数组元素的算法,其中每一个元素都有一个主键.排序算法的目标是将所有的主键按某种方式排列.排序后索引较大的元素大于等于索引较小的元素主键. 在java中,元素通常是对象,对主键的抽象描述则是通过一种内置的机制来完成的. 下面是排序算法类模版 import edu.princeton.cs.algs4.In; import edu.princeton.cs.algs4.StdOut; public class Example { //排序 public static vo

三大初级排序算法

1.冒泡排序 冒泡排序是最慢的排序算法.在实际运用中它是效率最低的算法.它通过一趟又一趟地比较数组中的每一个元素,使较大的数据下沉,较小的数据上升.它是O(n^2)的算法. 2.插入排序 插入排序通过把序列中的值插入一个已经排序好的序列中,直到该序列的结束. 3.shell排序(希尔排序) Shell排序通过将数据分成不同的组,先对每一组进行排序,然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数.平均效率是O(nlogn). 下面给出两个关于shell排序的链接: 算法系列15天速