讲完容器之后,我们迅速进入到算法部分。
首先看一下,我们这讲在整个算法大图的中位置:

在进入排序相关之前,我们把虽然与排序无关,但是也有关联的计数和最大值最小值部分先看一下。算是对算法部分作个预热,将来会广泛出场的lambda表达式也先借机会亮亮相。
计数的目的,是数一数,在容器里,符合某一条件的元素有多少个。
算法1: std::count,数一数跟这个值相等的对象有多少个。
我们看一个例子,数数vector中有几个1:
算法2: std::count_if,count升级版,可以提供一个条件判断的函数来进行判断。
我们看一个例子,判断是奇偶还是偶数:
count_if的第三个参数,是一个可调用对象,它可以是函数,仿函数(函数对象)和lambda表达式。
lambda表达式是一种匿名函数,特别适合用于stl的算法中。这是c++11带给算法的礼物。
lambda表达式的形式为:
如果返回值类型可以像auto一样被推断出来,就不用写了。变量捕获用不到可以为空,参数没有也可以为空。只有函数体是必要的。
我们看看上例中的这个lambda表达式:
不需要捕获变量
参数是int elem
返回值类型因为只可能是bool,就让编译器自己推断去
函数体里就跟普通函数一样,就不多说了
关于function, functor和lambda表达式,我们后面会详细再讲,目前快餐教程,大家先学会能看懂,能用。
算法3: std::max_element算法求最大值
算法4: std::min_element算法求最小值
例,求1〜100中的最大值和最小值:
在《数据结构》课中,讲完各种数据结构了之后,重头戏就变成了排序和查找。
我们上一讲主要是线性表、链表和二叉排序树。这一讲就重点说说排序和查找。
我们先来一张排序相关的总图,然后再去看每一部分的细图:
从上图中可以看到,排序相关的主要由三部分构成:
排序算法:真正做排序的算法
检查算法:确定是否是排序的,如果不是,是从哪里开始无序的
在已排序区间上的操作:排好序了,有什么用处,这些算法是消费排序带来的好处的。最重要的就是二分法查找了。
我们再聚一下焦,看看排序算法都包括哪些内容:
在stl的排序算法中,主要有三种算法会被用到:快速排序,归并排序和堆排序。
算法
sort
partial_sort
stable_sort
默认算法
快速排序
堆排序
归并排序
优点
很好的平均效率
在任何情况下都保持n*log(n)的复杂度
保持相等元素的相对顺序
不足
最差情况下效率较差
实际情况中比快速排序慢2~5倍
内存不足时复杂度提高log(n)倍
额外好处
可以只做前n个排序就停止
快速排序在不是最差情况下的平均速度最快,所以它是默认的算法:
从小到大是默认的行为:
从大到小可以通过第三个参数,传入一个比较函数来实现:
有一点需要注意,因为是要改变内容的,所以迭代器不能用常量的cbegin和cend.
stable_sort的参数与sort完全一致。可以无缝替换使用,视内存和需求决定。
例:
部分排序除了指定起点和终点之外,还需要一个参数指定排序的终点。
比如下面的例子是:只排出前三大的,后面就不管了。
排序后是这样的:
堆有两种用法,一种是随时更新堆,就像set和map一样。后面我们会介绍priority_queue容器,就是这方面的专用容器;另一种就是将线性结构一次性建个堆,不一定随时维护。这样就只有一次性的成本。
比如把上例中的vector来建个堆:
堆中的组织结构是这样的:
100是树根,左子树是92,右子树是99.
然后92的左子树是76,右子树是91. 99的左子树是98, 右子树是60. 以此类推,我们画一下前4层的图:
如果这么看起来实在太费劲了,我们可以将堆重新变成排序好的结果:
打印出来就又变成有序的了。不过,堆的结构也被破坏了,还需要重新建堆。
排好序了,最常见的应用之一就是二分法查找。
我们看一个例子:
输出当然是“found 97!”了。
因为二分查找不修改迭代器的值,所以又可以使用只读的迭代器cbegin和cend了。
比如,在上面排序的基础上,我们想把比4小的排在4前面,比4大的排在后面:
排序后的结果如下:
从上面的结果可以看到,排序的结果中,并不是完全有序的。在满足要求的情况下,并没有对4以后的数做更进一步的有序化。这样会带来性能的提升。
前面的nth_element是根据某个值分成两部分,而partition算法可以有更多的玩法。
比如下面,我们按奇偶性,把偶数排到前面:
重新排列之后变成这样:
正如排序有稳定排序一样,std::partition把顺序排乱了,我们不喜欢。这时有内部排序稳定的std::stable_partition算法出马:
排序结果如下:
这节我们学习了排序算法的主要框架:快速排序sort,稳定的归并排序stable_sort,可以部分排序的堆排序算法partial_sort.
我们可以显式建堆make_heap,也可以将堆转化成排序sort_heap。
二分查找binary_search用在有序结构上,速度比线性查找要快得多。
nth_element可以按某个值划分成两部分,partition可以应用更复杂的条件,但是不稳定。保持内部排序稳定需要用stable_partition.
count和count_if用来计数。
max_element和min_element用于不想做排序只想获取最大最小值。