使用施瓦茨变换(Schwartzian Transform)进行高速排序

首先假设一个需求:

一个文件中有若干合法的身份证号,每行一个。现在需要对这些身份证号进行升序排序,要求综合出生年、月、日进行排序,其中年、月、日重要性依次递减。

司空见惯的低效排序

很可能一个Perl 程序员看到这样的需求,马上就能作出下面的实现:

#!/usr/bin/env perl
package main;
use strict;
use warnings;
use 5.010;

print sort {my @a = split //, $a;
            my @b = split //, $b;
            (join "", @a[6..9]) <=> (join "", @b[6..9])
              or (join "", @a[10..11]) <=> (join "", @b[10..11])
              or (join "", @a[12..13]) <=> (join "", @b[12..13])}
        <DATA>;

1;

__DATA__ 110000198109067427 110000198710239963 110000198105197982 110000198103242380 110000198711279963 110000198703279963 110000198005262597 110000198710159963 110000198108077237 110000198011113097 110000198710179963

诚然这样的程序可以使用规矩的思维和较短的代码完成需求,但是也带来了排序算法的低效。

看看我们做了多少次splitjoin操作吧:

每一次元素比较,这两个函数都需要被调用。假设要排序的身份证号为N 个,而perl sort 函数的时间复杂度O(NlogN)。每次比较,需要调用2 次split和6 次join,所以两个函数都被调用了O(NlogN) 次,如果拿掉大O标记,这个结果会更加难看。

使用施瓦茨变换的高效排序

施瓦茨变换(Schwartzian Transform)由Perl 黑客Randal L. Schwartz 所创造,提供了一种高效排序的方法,其基本思路如下:

  1. 首先将原始数据变换为一个由列表引用组成的列表,其中每个列表引用的第一个元素是原始数据的一个排序单位(例如本例中是一个身份证号),其后的元素为排序单位处理后得到的标量。
  2. 使用列表引用中排序单位的处理结果对得到的列表进行排序。
  3. 返回排序后由每个列表引用的第一个元素组成的列表,即为排序后的列表。

上面的思路因为是用自己的语言组织的,可能描述的不是很清晰,下面是一个使用施瓦茨变换高速排序的例子,对于同样的需求,返回了同样的排序结果,但是用了更少的时间。

#!/usr/bin/env perl
package main;
use strict;
use warnings;
use 5.010;

print map {$_->[0]}
        sort {$a->[1] <=> $b->[1]
                or $a->[2] <=> $b->[2]
                or $a->[3] <=> $b->[3]}
        map {@_ = split //;
              [$_,
                (join "", @_[6..9]),
                (join "", @_[10..11]),
                (join "", @_[12..13])]}
        <DATA>;

1;

__DATA__ 110000198109067427 110000198710239963 110000198105197982 110000198103242380 110000198711279963 110000198703279963 110000198005262597 110000198710159963 110000198108077237 110000198011113097 110000198710179963

在排序算法的时间复杂度已经确定的情况下,我们可以从splitjoin函数的调用次数上来看为什么施瓦茨变换耗时更少:

可以看到这两个函数没有出现在排序的过程中,而是在之前将原始数据变换为列表的过程中被调用。这里假设要排序的身份证号为N 个,每个身份证需要一次变换,每次变换调用了1 次split和3 次join,这两个函数都被调用了O(N) 次,如果拿掉大O标记,这个差异和上一节的传统排序方式相比将会更加明显。

最后施瓦茨变换是一种排序思路,可以用在任何语言当中。在排序算法的时间复杂度确定的情况下,使用施瓦茨变换会明显提高排序的整体速度——特别是从原始数据中提取排序元素很复杂的情况下。

你可能感兴趣的:(使用施瓦茨变换(Schwartzian Transform)进行高速排序)