C++拾取——使用stl标准库实现排序算法及评测
今天看了一篇文章,讲各种语言的优势和劣势。其中一个观点:haskell非常适合写算法,因为使用者不用去关心具体的计算机实现,而只要关注于操作语义。这让它在专心研究算法的人中非常受欢迎。所以很多时候,语言的争论没有太多的意义,有意义的是它适不适合某些场景或者某些人。(转载请指明出于breaksoftware的csdn博客)
目前在网上讨论排序算法更多是C语言实现的。因为C语言可以展现出一些细节。但是从某种角度说,这也让“算法思想的光辉”被计算机操作细节所遮蔽。本文将使用C++的标准库去实现一些排序算法,我们从中将会发现它掩盖了很多计算机操作细节,而让算法的光辉得以显现。
实现
选择排序
template<class ForwardIt>
void selection_sort(ForwardIt begin, ForwardIt end) {for (ForwardIt i = begin; i != end; ++i) {std::iter_swap(i, std::min_element(i, end));}
}
min_element返回两个索引之间最小元素的索引;iter_swap将最小索引和不停迭代的索引进行交换。
这就是选择排序:逐位替换之后(包含自身)序列中最小的元素。
合并排序
template<class ForwardIt>
void merge_sort(ForwardIt begin, ForwardIt end)
{if (end - begin > 1) {ForwardIt middle = begin + (end - begin) / 2;merge_sort(begin, middle);merge_sort(middle, end);std::inplace_merge(begin, middle, end);}
}
inplace_merge会将有序序列[begin,middle)和有序序列[middle,end)合并成一个有序队列[begin,end)。因为merge_sort会递归下去,所以可以从最低粒度开始保证上述是有序的。
插入排序
template<class ForwardIt>
void insertion_sort(ForwardIt begin, ForwardIt end) {for (ForwardIt i = begin; i != end; ++i) {std::rotate(std::upper_bound(begin, i, *i), i, std::next(i));}
}
upper_bound返回已排序[begin,i)序列中第一个大于i所指向值的迭代器j。rotate把i翻转到j,[j,i)之间的数据往后移动。
由于i是从begin开始迭代,所以可以保证[begin,i)区间是有序的。
rotate后两参数——i和std::next(i)构成了[i,i+1)区间,即只有迭代器i。
快速排序
template <class ForwardIt>
void quick_sort(ForwardIt first, ForwardIt last) {if (first == last) return;auto pivot = *std::next(first, std::distance(first, last) / 2);ForwardIt middle1 = std::partition(first, last,[pivot](const auto& em) { return em < pivot; });ForwardIt middle2 = std::partition(middle1, last,[pivot](const auto& em) { return !(pivot < em); });quick_sort(first, middle1);quick_sort(middle2, last);
}
pivot指向容器中位置处于中间的迭代器所指向的值。
partition可以保证按照lambda表达式的结果,将序列分成两个区间,并返回第二个区间的首元素迭代器。
middlle1的左边元素都是小于pivot,右边的都是大于等于pivot的。
middle2指向的是大于pivot的元素区间首个迭代器。
[middle1,middle2)就是所有等于pivot的元素。
然后递归调用自身,分别处理[first,middle1)和[middle2,last)区间。
由于partition是不稳定的,如果希望使用稳定的版本,可以使用partition_stable替代。
堆排序
template<class ForwardIt>
void heap_sort(ForwardIt first, ForwardIt last) {std::make_heap(first, last);std::sort_heap(first, last);
}
由于C++对此封装太多,所有我们没法从名称上看到其算法光辉。
评测
class UtSort:public ::testing::Test
{
protected:virtual void SetUp() {_data.resize(_data_count);std::iota(_data.begin(), _data.end(), 1);_orded_data.assign(_data.begin(), _data.end());std::random_device rd;std::mt19937 g(rd());std::shuffle(_data.begin(), _data.end(), g);} template<class ForwardIt>void test_the_same(ForwardIt begin, ForwardIt end) const {auto same_count = std::inner_product(begin, end,_orded_data.begin(), 0,std::plus<>(), std::equal_to<>());ASSERT_EQ(same_count, std::distance(begin, end));}protected:std::vector<int> _data;decltype(_data) _orded_data;size_t _data_count = 1024 * 256;
};
我们使用gtest测试框架。
第7行,我们构建了按1递增的数列_data,它是有序的。第9行将这个排序的数据保存到_orded_data中以供之后比较。第13行,我们将_data中的元素顺序打乱。
第18行,将计算两个序列中,相同位置的值相等的格式。如果我们算法正确,则个数和传入的迭代器个数一致。
为了测试每个排序的时间,我还设计了Perform用于统计时间
#include <gtest/gtest.h>
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
#include <array>
#include <numeric>
#include <set>
#include <chrono>
#include <random>using duration_mil = std::chrono::duration<double, std::milli>;class Perform {
public:Perform() {_start = std::chrono::high_resolution_clock::now();}~Perform() {_end = std::chrono::high_resolution_clock::now();duration_mil ms = _end - _start;std::cout << ms.count() << "ms" << std::endl;}
private:decltype(std::chrono::high_resolution_clock::now()) _start;decltype(std::chrono::high_resolution_clock::now()) _end;
};
于是之前介绍的几种排序的测试代码是
TEST_F(UtSort, quick_sort) {{Perform perform;quick_sort(_data.begin(), _data.end());}test_the_same(_data.begin(), _data.end());
}TEST_F(UtSort, heap_sort) {{Perform perform;heap_sort(_data.begin(), _data.end());}test_the_same(_data.begin(), _data.end());
}TEST_F(UtSort, insertion_sort) {{Perform perform;insertion_sort(_data.begin(), _data.end());}test_the_same(_data.begin(), _data.end());
}TEST_F(UtSort, merge_sort) {{Perform perform;merge_sort(_data.begin(), _data.end());}test_the_same(_data.begin(), _data.end());
}TEST_F(UtSort, selection_sort) {{Perform perform;selection_sort(_data.begin(), _data.end());}test_the_same(_data.begin(), _data.end());
}
除了这几种排序外,STL标准库还提供了其他几种方法
- 使用partial_sort进行局部排序
- 使用sort函数
- 使用关系容器,比如set
这三种的测试代码如下
TEST_F(UtSort, partial_sort) {{Perform perform;std::partial_sort(_data.begin(), _data.end(), _data.end());}test_the_same(_data.begin(), _data.end());
}TEST_F(UtSort, stl_sort) {{Perform perform;std::sort(_data.begin(), _data.end());}test_the_same(_data.begin(), _data.end());
}TEST_F(UtSort, set_sort) {std::set<int> ordered_data;{Perform perform;std::copy(make_move_iterator(_data.begin()), make_move_iterator(_data.end()), std::inserter(ordered_data, ordered_data.begin()));}test_the_same(ordered_data.begin(), ordered_data.end());
}
这儿特别提一句,如果我们不需要全排序,只需要前N个元素是排序的,则可以优先考虑partial_sort。因为它的效率很高,比如下面代码测试排序最小的10个元素
TEST_F(UtSort, partial_sort_head_10) {{Perform perform;std::partial_sort(_data.begin(), std::next(_data.begin(), 10), _data.end());}test_the_same(_data.begin(), std::next(_data.begin(), 10));
}
我们看下测试结果
[==========] Running 8 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 8 tests from UtSort
[ RUN ] UtSort.quick_sort
135.274ms
[ OK ] UtSort.quick_sort (168 ms)
[ RUN ] UtSort.heap_sort
194.824ms
[ OK ] UtSort.heap_sort (225 ms)
[ RUN ] UtSort.insertion_sort
2637.53ms
[ OK ] UtSort.insertion_sort (2667 ms)
[ RUN ] UtSort.merge_sort
135.243ms
[ OK ] UtSort.merge_sort (165 ms)
[ RUN ] UtSort.selection_sort
337051ms
[ OK ] UtSort.selection_sort (337083 ms)
[ RUN ] UtSort.partial_sort
195.34ms
[ OK ] UtSort.partial_sort (225 ms)
[ RUN ] UtSort.stl_sort
84.7456ms
[ OK ] UtSort.stl_sort (118 ms)
[ RUN ] UtSort.set_sort
294.94ms
[ OK ] UtSort.set_sort (456 ms)
[ RUN ] UtSort.partial_sort_head_10
2.51487ms
[ OK ] UtSort.partial_sort_head_10 (31 ms)
[----------] 8 tests from UtSort (340973 ms total)[----------] Global test environment tear-down
[==========] 8 tests from 1 test case ran. (340973 ms total)
[ PASSED ] 8 tests.
完整排序中,std::sort是最快的,其次是quick_sort和merge_sort。heap_sort和partial_sort差不多。最差的是selection_sort。
同时,我们看使用partial_sort只选出并排列最小的10个元素的耗时是2.51487毫秒。这比任何一个排序都要快两个数量级。
所以根据不同场景,选择合适的排序非常重要。
相关代码见:https://github.com/f304646673/stl_example/tree/master/sort
相关文章:
几行代码构建全功能的对象检测模型,他是如何做到的?
作者 | Alan Bi 译者 | 武明利,责编 | Carol 出品 | AI科技大本营(ID:rgznai100) 如今,机器学习和计算机视觉已成为一种热潮。我们都看过关于自动驾驶汽车和面部识别的新闻,可能会想象建立自己的计算机视觉模型有多酷。…

SQL操作全集
SQL操作全集 下列语句部分是Mssql语句,不可以在access中使用。 SQL分类: DDL—数据定义语言(CREATE,ALTER,DROP,DECLARE) DML—数据操纵语言(SELECT,DELETE,UPDATE,INSERT) DCL—数据…

css3媒体查询实现网站响应式布局
响应式建筑设计、响应式家具设计、响应式办公设计,这些词可能是已有的专业名词,也可能是我自己想出来的一些名词。因为在生活中,我们常常会见到很多让人惊叹的设计,为什么同一套东西经过不同的方式变化之后会给人不同的使用感受和…
流行于机器学习竞赛的Boosting,这篇文章讲的非常全了
作者 | AISHWARYA SINGH译者 | 武明利,责编 | Carol出品 | AI科技大本营(ID:rgznai100)你能说出至少两种机器学习中的 Boosting 吗?Boosting 已经存在了很多年,然而直到最近它们才成为机器学习社区的主流。那么&#x…
并行计算——OpenMP加速矩阵相乘
OpenMP是一套基于共享内存方式的多线程并发编程库。第一次接触它大概在半年前,也就是研究cuda编程的那段时间。OpenMP产生的线程运行于CPU上,这和cuda不同。由于GPU的cuda核心非常多,可以进行大量的并行计算,所以我们更多的谈论的…

JavaScript继承详解(四)
文章截图 - 更好的排版 在本章中,我们将分析Douglas Crockford关于JavaScript继承的一个实现 - Classical Inheritance in JavaScript。 Crockford是JavaScript开发社区最知名的权威,是JSON、JSLint、JSMin和ADSafe之父,是《JavaScript: The …

C语言:在屏幕上输出信息
#include<stdio.h>int main(){printf ("This is a C program.\n");printf("welcome to bit\n");return 0;}结果:This is a C program.welcome to bitPress any key to continue转载于:https://blog.51cto.com/yaoyaolx/1715542

Colly源码解析——框架
Colly是一个使用golang实现的数据抓取框架,我们可以使用它快速搭建类似网络爬虫这样的应用。本文我们将剖析其源码,以探析其中奥秘。(转载请指明出于breaksoftware的csdn博客) Collector是Colly的核心结构体,其中包含了…

未经任何测试的源代码开放
未经任何测试的源代码开放 http://files.cnblogs.com/TextEditor/TextBoxEx.rar 这个代码只是一个Demo. 请将一个Vb.net的代码放在C盘下面,并且改名为Test.txt,然后使用菜单的Open来打开文件。 有任何问题,请在这里留言。 C#的上色还没有完成…

助力企业抗疫,360金融推出免费AI语音机器人
复工潮来临之际,为帮助各大企业进行高效的内部防疫宣传、员工行程信息收集以及快速生成公司内部防疫排班表,360金融针对复工企业的需求痛点推出了AI语音机器人,以助力企业更高效的防疫、抗疫。 针对复工企业的需求痛点,360金融人…

实现strncat
函数原型char *strncat(char *front,char *back,size_t count)参数说明back为源字符串,front为目的字符串,count为指定的back中的前count个字符。 所在库名#include <string.h>函数功能把back所指字符串的前count个字符添加到front结尾处&a…

Colly源码解析——结合例子分析底层实现
通过《Colly源码解析——框架》分析,我们可以知道Colly执行的主要流程。本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现。(转载请指明出于breaksoftware的csdn博客) 递归深度 以下例子截取于Basic c : colly.NewCollecto…

无限路由 DI-624+A 详细介绍
无线路由器硬件安装设置图解1、确认宽带线路正常:无线宽带路由器可以让您将家中的计算机共享高速宽带网络连结至互联网;但在此之前,您必须先具备一部基于以太网络的Cable/DSL Modem(使用RJ-45 接头),并确定您的宽带网络在只有连接…
教你如何编写第一个爬虫
2019年不管是编程语言排行榜还是在互联网行业,Python一直备受争议,到底是Java热门还是Python热门也是一直让人争吵的话题。随着信息时代的迭代更新,人工智能的兴起,Python编程语言也随之被人们广泛学习,Python数据分析…

【BZOJ】3542: DZY Loves March
题意 \(m * m\)的网格,有\(n\)个点。\(t\)个询问:操作一:第\(x\)个点向四个方向移动了\(d\)个单位。操作二:询问同行同列其他点到这个点的曼哈顿距离和。强制在线。(\(n \le 10^5,m \le 10^{18}\)ÿ…

Gin源码解析和例子——路由
Gin是一个基于golang的net包实现的网络框架。从github上,我们可以看到它相对于其他框架而言,具有优越的性能。本系列将从应用的角度来解析其源码。(转载请指明出于breaksoftware的csdn博客) 本文我们将分析其路由的原理。先看个例…
一文讲透推荐系统提供web服务的2种方式
作者丨gongyouliu编辑丨zandy来源 | 大数据与人工智能(ID: ai-big-data)推荐系统是一种信息过滤技术,通过从用户行为中挖掘用户兴趣偏好,为用户提供个性化的信息,减少用户的找寻时间,降低用户的决策成本&am…

jQuery遍历json数组怎么整。。。
{"options":"[{\"text\":\"王家湾\",\"value\":\"9\"},{\"text\":\"李家湾\",\"value\":\"10\"},{\"text\":\"邵家湾\",\"value\":\"13\…

述说C#中的值类型和引用类型的千丝万缕
关于值类型和引用类型方面的博客和文章可以说是汗牛充栋了,今天无意中又复读了一下这方面的知识,感觉还是有许多新感悟的,就此时间分享一下: CLR支持两种类型:值类型和引用类型,看起来FCL的大多数类型是引用…

Gin源码解析和例子——中间件(middleware)
在《Gin源码解析和例子——路由》一文中,我们已经初识中间件。本文将继续探讨这个技术。(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数。这和绑定到一个路径下的处理函数本质是一样的。 再以Engin…

DNS简单配置
DNS的原理就不说了,这里只是做个简单的配置,也是方便自己记忆,在这里还要十分感谢redking老大的教程!要安装的bind* 、caching-nameserver 包1、/var/named/chroot/etc/named.conf这个文件需要自己创建options { listen-on…
关系抽取论文整理,核方法、远程监督的重点都在这里
来源 | CSDN 博客作者 | Matt_sh,编辑 | Carol来源 | CSDN云计算(ID:CSDNcloud)本文是个人阅读文章的笔记整理,没有涉及到深度学习在关系抽取中的应用。笔记中一部分来自个人解读,一部分来自原文࿰…

freemarker内建函数介绍
Sequence的内置函数1.sequence?first 返回sequence的第一个值。2.sequence?last 返回sequence的最后一个值。3.sequence?reverse 将sequence的现有顺序反转,即倒序排序4.sequence?size 返回sequence的大小5.sequence?sort 将sequence中的对象转化为字符串后顺序…

PowerBuilder 11.x 的重要进步和不足
PowerBuilder 11(以下简称PB)出来有一段时间了,但很多用户对PB11的到底有哪些进步还不是很清楚,由于对PB11缺乏了解和信心,目前用PB11做出像样应用的用户不多,这确实非常遗憾,这里我讲一下我对P…
超赞的PyTorch资源大列表,GitHub标星9k+,中文版也上线了
点击阅读原文,快速报名!作者 | 红色石头来源 | AI有道(ID: redstonewill)自 2017 年 1 月 PyTorch 推出以来,其热度持续上升。PyTorch 能在短时间内被众多研究人员和工程师接受并推崇是因为其有着诸多优点,…

C++拾取——Linux下实测布隆过滤器(Bloom filter)和unordered_multiset查询效率
布隆过滤器是一种判定元素是否存在于集合中的方法。其基本原理是使用哈希方法将数据映射到一个很长的向量上。在维基百科上,它被称为“空间效率和查询时间都远远超过一般的算法”的方法。由于它只保存散列的数据,所以对于很长的数据有着良好的压缩特性&a…

递归思想解决输出目录下的全部文件
刚刚了解了下递归思想 递归就是在方法内调用本方法 下面说一个实际的应用 输出目录下的全部文件,当目录中还有目录时,则进入目录输出里面的文件 import java.io.*; class ShowFile{public static void showfile(File files){if(files.isDirectory()){Fi…

实战之网马解密之shellcode篇
今天上卡卡社区发现里面发了个网马解密的链接,呵呵 顺便试试看能解出来不.呵呵. 相信各位已经对网马有点了解了吧.一般网马都是加密了的.关于什么是网马以及怎么防止网马也不是本文的重点.本文是实战shellcode网马解密.以后的博文会放出常见的网马及其解密.以及常见的解密工具的…
机器学习中的线性回归,你理解多少?
作者丨algorithmia编译 | 武明利,责编丨Carol来源 | 大数据与人工智能(ID: ai-big-data)机器学习中的线性回归是一种来源于经典统计学的有监督学习技术。然而,随着机器学习和深度学习的迅速兴起,因为线性(多…

Golang反射机制的实现分析——reflect.Type类型名称
现在越来越多的java、php或者python程序员转向了Golang。其中一个比较重要的原因是,它和C/C一样,可以编译成机器码运行,这保证了执行的效率。在上述解释型语言中,它们都支持了“反射”机制,让程序员可以很方便的构建一…