算法分析学习笔记(三) - 排序算法(上)

一. 写在前面

     要学习算法,“排序”是一个回避不了的重要话题,在分析完并查集算法和常用数据结构之后,今天我们终于可以走近排序算法这个大家族了。计算机科学发展到今天,已经有了成百上千种让你眼花缭乱,多的数不清的排序算法,而且还会有更新更厉害的算法尚未问世,它们都是人类无穷智慧的结晶,有太多太有意思的内容等待我们去思考,去品味。作为一篇入门级的学习笔记,我们不会在这里展开讨论,但是,我们将从“排序算法”这个百宝袋中,抓取几个经典的,脍炙人口的算法,作为这次我们讨论的主题。我们会先聊聊三种最基本的算法:选择,插入和Shell排序;然后我们走入分治思想的世界,聊聊归并排序;然后我们将登上世界之巅,来看看20世纪最伟大的算法之一——快速排序,了解一下这几十年来人们为了研究她都做了哪些巨大贡献;最后,我们将认识一个新朋友,一个为排序而生的数据结构——优先级队列,并以一些排序算法的实际应用作为本算法分析笔记的结束,在读完本篇文章之后,相信你会获得对排序算法有一个基础的了解。
     当然,本篇文章介绍的内容都是我在学习Princeton大学Sedgewick教授的Coursera上公开课“Algorithms”时的一些心得,文章中的一些图示,例子和代码,均来自于老教授的课件和英文原版教科书,所有这一切的知识和成果,都是他老人家辛苦整理的智慧结晶。感谢Sedgewick老师,感谢这位可敬的老人带给我算法学习中的不少乐趣。

1.1 排序算法怎么玩

     排序是一种将某元素的集合按照某种逻辑顺序进行排列的 过程。我们之所以要排序,是因为它能满足我们的某种需要,特别是对于有强迫症的人来说,排列整齐总是比杂乱无章要好的。最简单的例子是,在前面介绍并查集算法时,我们曾经聊到过二叉搜索算法,它能很快地从集合中查找元素,但前提是该集合内的元素是已排序的。实际生活中的例子还有很多:比如人员名单在中国通常按笔画数排序,在英美国家则是字母顺序;学校里老师要按照分数对考试成绩进行排序,看看前十名都有哪些人;银行要按照资产情况对客户信息进行排序,存款最多的用户也许能发展成为该行的VIP客户;超市要按照时间顺序对交易记录进行排序,以打印账单等等。几乎所有需要用计算机去处理相关事务的领域,你都会看到排序算法的身影,它们往往是重要算法的关键一环。

1.2 排序算法的抽象设计

     应用越广泛的东西,遇到的问题也就越多。如果我们只对简单的元素进行排序,那一点都不难,比如对数字进行排序,对字符串进行排序。但实际生活中我们往往要面对的是复杂的情况和复杂的对象。首先,不是所有的元素集合都能排序,比如我们爱玩的“石头剪刀布”游戏,石头干掉剪刀,剪刀干掉布,然后布又干掉石头,你把哪个放在前面都对,又都不对,无法排序;其次,对一个元素集合我们可能会按照多种标准进行排序,比如一条学生成绩记录可能包含姓名,学号,科目和分数等等,那我既可以按分数高低排序选出优秀学生,也可以按照姓名排序进行点名,更可以按照科目分数排序找出某一科的佼佼者。这些都会在实际生活中遇到,如何处理?
     对于第一个问题,我们要先弄清楚的是究竟什么样的元素集合是可以排序的。答案是:如果你能在这个元素集合上找到一个“全序”(Total Order)关系,那么它就是可以排序的。全序的定义是:1)自反(Reflexive),对所有元素e,e=e都成立;2)反对称(Antisymmetric),对所有元素v和w,v < w则w > v,w = v则v = w;3)传递(Transitive),对所有的v,w和x,v <= w且w <= x,那么v <= x。“石头剪刀布”显然就不满足,因为虽然石头能干掉剪刀,剪刀能干掉布,但石头并不能干掉布,而是被布给干掉了,不满足传递性。不过在实际编程工作中我们也不用太在意,知道有这么回事就好,我们只需要通过某种方式告诉排序算法如何判断两个元素谁大谁小就可以了。
     那么怎样告诉排序算法两个元素谁大谁小呢?我们的方法是基于“回调”机制实现,而各种不同的编程语言在“回调”的基础上建立了自己的处理方法:C语言使用函数指针,C++通过定义函对象重载函数调用操作符实现,Python语言通过FP的方式实现,Java语言和Go语言则是通过“接口”来实现。“面向接口编程”是一种重要的思想,在设计这种通用算法的时候就特别有用,这些通用的算法通过“回调”来处理具体的对象,而不需要知道对象的细节,这便是依赖倒置原则:细节依赖于抽象,而抽象并不依赖于细节。
     在Java中,只要我们的类满足Comparable接口,通过实现compareTo()函数就能告诉排序算法两个元素谁大谁小。例如一个实现了Comparable接口的Date类如下所示,这样我们就可以用排序算法对Date进行按日期的排序排序。compareTo()函数返回一个整型来表示大小关系:正数表示大于,负数表示小于,0则表示等于。
public class Date implements Comparable<Date> {
   /* ... */
    public int compareTo(Date that) {
        if (this.year  < that.year)  return -1;
        if (this.year  > that.year)  return +1;
        if (this.month < that.month) return -1;
        if (this.month > that.month) return +1;
        if (this.day   < that.day)   return -1;
        if (this.day   > that.day)   return +1;
        return 0;
    }

    /* ... */
}
     除此之外,我们还要实现两个辅助函数:less和exch,less函数用于对元素的比较进行进一步“包装”——因为compareTo返回的是整型值,而我们需要一个返回布尔值的函数;exch函数则用于交换两个元素,这些都是排序算法中所需要的。这样,我们在实现排序算法时就通过这些函数,以一种统一的形式去操作数据结构,而不去关心它们是怎么比较大小或者怎么交换元素的。
private static boolean less(Comparable v, Comparable w) {
        return (v.compareTo(w) < 0);
}

private static void exch(int[] a, int i, int j) {
        int swap = a[i];
        a[i] = a[j];
        a[j] = swap;
}
     那如果我们要对同一记录进行多种形式的排序又该怎么做呢?这就要用到Java的另一个更高级的接口——Comparator。这个接口只包含一个函数compare(),它同样通过返回一个整型值来表示大小关系:正数表示大于,负数表示小于,而0表示相等。比如我们有一个表示商业事务的类Transaction,包含客户姓名,日期和资产,我们需要对商业事务的记录按照姓名、日期和资产进行排序,那么我们就可以在Transaction中 实现三个满足Comparator接口的类:WhoOrder,WhenOrder以及HowMuchOrder。
import java.util.Arrays;import java.util.Comparator;

public class Transaction implements Comparable<Transaction> {
    private final String  who;      // customer
    private final Date    when;     // date
    private final double  amount;   // amount

    /* ... */

    /**     * Compares two transactions by customer name.     */
    public static class WhoOrder implements Comparator<Transaction> {
        public int compare(Transaction v, Transaction w) {
            return v.who.compareTo(w.who);
        }
    }

    /**     * Compares two transactions by date.     */
    public static 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值