常见的排序算法——插入排序(二)
本文记述了针对插入排序微小改动的基本思想和一份参考实现代码,并在说明了算法的性能后用随机数据进行了验证。
◆ 思想
内存中的数据交换是昂贵的操作,此改动实现了不需要交换的插入排序。
将第一个元素之后的所有元素作为待排序范围,将前面的所有元素作为已排序范围。通过一一比较,逐个将已排序范围内比第二个元素大的所有元素右移一位,使第二个元素被插入到了正确的位置。然后将第二个元素之后的所有元素作为新的待排序范围,将前面的所有元素作为已排序范围。重复以上的比较、查找和交换,直至待排序范围中无元素为止。如要得到逆序的结果,则仅需改变比较的方向即可。
◆ 实现
排序代码采用《算法(第4版)》的“排序算法类模板”实现。(代码中涉及的基础类,如 Array,请参考算法文章中涉及的若干基础类的主要API)
// insertion2.hxx
...
class Insertion2
{
...
template
<
class _T,
class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
>
static
void
sort(Array<_T> & a)
{
int N = a.size();
for (int i = 1; i < N; ++i) { // #1
_T t = a[i];
int j;
for (j = i; j > 0 && __less__(t, a[j-1]); --j) // #2
a[j] = a[j-1];
a[j] = t;
}
}
...
template
<
class _T,
class = typename std::enable_if<std::is_base_of<Comparable<_T>, _T>::value>::type
>
static
bool
__less__(_T const& v, _T const& w)
{
return v.compare_to(w) < 0; // #3
}
...
反复处理待排序范围(#1),逐个将已排序范围内比待插入元素大的所有元素右移一位,使该元素被插入到了正确的位置(#2)。将 '<' 改为 '>',即得到逆序的结果(#3)。
◆ 性能
时间复杂度 | 空间复杂度 | 是否稳定 |
---|---|---|
N^2 | 1 | 是 |
◆ 验证
测试代码采用《算法(第4版)》的倍率实验方案,用随机数据验证其正确性并获取时间复杂度数据。
// test.cpp
...
time_trial(int N)
{
Array<Double> a(N);
for (int i = 0; i < N; ++i) a[i] = Std_Random::random(); // #1
Stopwatch timer;
Insertion2::sort(a); // #2
double time = timer.elapsed_time();
assert(Insertion2::is_sorted(a)); // #3
return time;
}
...
test(char * argv[])
{
int T = std::stoi(argv[1]); // #4
double prev = time_trial(512);
Std_Out::printf("%10s%10s%7s\n", "N", "Time", "Ratio");
for (int i = 0, N = 1024; i < T; ++i, N += N) { // #5
double time = time_trial(N);
Std_Out::printf("%10d%10.3f%7.1f\n", N, time, time/prev); // #6
prev = time;
}
}
...
用 [0,1) 之间的实数初始化待排序数组(#1),打开计时器后执行排序(#2),确保得到正确的排序结果(#3)。整个测试过程要执行 T 次排序(#4)。每次执行排序的数据规模都会翻倍(#5),并以上一次排序的时间为基础计算倍率(#6),
此测试在实验环境一中完成,
$ g++ -std=c++11 test.cpp std_out.cpp std_random.cpp stopwatch.cpp type_wrappers.cpp
$ ./a.out 8
N Time Ratio
1024 0.115 4.1
2048 0.457 4.0
4096 1.813 4.0
8192 7.223 4.0
16384 28.987 4.0
32768 116.038 4.0
65536 463.018 4.0
131072 1868.986 4.0
可以看出,随着数据规模的成倍增长,排序所花费的时间将是上一次规模的 4 倍。将数据反映到以 2 为底数的对数坐标系中,可以得到如下图像,
O(N^2) 代表了平方级别复杂度下的理论排序时间,该行中的数据是以 Time 行的第一个数据为基数逐一乘 4 后得到的结果(因为做的是倍率实验,所以乘 (2)^2 即 4)。
与插入排序的测试数据相比,达到了减少时间的目的。可以得出结论,针对随机数据而言,不需要交换的插入排序能减少排序时间。
◆ 最后
完整的代码请参考 [gitee] cnblogs/18120503 。
查看性能对比,了解此算法与其它排序算法的相似性和差异性。
写作过程中,笔者参考了《算法(第4版)》的插入排序、实验题 2.1.25、“排序算法类模板”和倍率实验。致作者 Sedgwick,Wayne 及译者谢路云。
受限于作者的水平,读者如发现有任何错误或有疑问之处,请追加评论或发邮件联系 green-pi@qq.com。作者将在收到意见后的第一时间里予以回复。 本文来自博客园,作者:green-cnblogs,转载请注明原文链接:https://www.cnblogs.com/green-cnblogs/p/18120503 谢谢!