C++11时代的标准库快餐教程(4) - 排序算法的应用

简介: 二分法是针对一个排序后的容器的用法,如果是多个有序容器,我们就可以快速地在其基础上进行集合的求子集,交集,并集与差集等运算。

排序算法的应用

用排序做集合运算 - 子集,交集,并集与差集

上一节我们讲了排序算法,包括快速排序sort,堆排序partial_sort和归并排序stable_sort。并且讲了排序的第一个用法,二分法差找。
二分法是针对一个排序后的容器的用法,如果是多个有序容器,我们就可以快速地在其基础上进行集合的求子集,交集,并集与差集等运算。

我们还是先看一下图,排序相关算法都有哪些内容:

sort2.png

子集std::includes

std::includes算法用于判断第一个迭代器是否包含第二个迭代器中的所有元素。

我们构造一个小的素数的集合,看看它是不是[1,100]的子集:

    std::vector<int> prime_numbers;
    prime_numbers.push_back(2);
    prime_numbers.push_back(3);
    prime_numbers.push_back(5);
    prime_numbers.push_back(7);
    prime_numbers.push_back(11);
    prime_numbers.push_back(13);
    
    std::sort(odd_even.begin(),odd_even.end());
    std::sort(prime_numbers.begin(),prime_numbers.end());
    
    if(std::includes(odd_even.cbegin(), odd_even.cend(), prime_numbers.cbegin(), prime_numbers.cend())){
        std::cout<<"odd_even includes prime_numbers";
    }else{
        std::cout<<"odd_even does not include prime_numbers";
    }
AI 代码解读

唯一提醒一点,用std::includes算法之前,一定要确保两个容器都是同样有序的。

集合合并

集合合并就是简单将两个排序好的组结合并到一起。所以,被合并到的结构,一定要有足够的空间。
为了避免空间不足,我们可以使用list容器。
不过,我们都知道,std::list是一个链表,是不支持随机访问的迭代器的。这样,std::sort算法无法应用到list容器上。但是,面向对象语言的好处在此时又发挥出来了,list容器本身提供了一个更高效的sort函数。
后面我们会专题讲算法和容器的方法的关系,总体来说,算法是为了通用性,不考虑内部实现,为同类容器提供同一类算法。而容器自己的方法则提供针对这个容器的最优实现。
多说几句,这就是算法从面向对象的方法中脱离出来的好处。对象的方法,只在针对这个容器有较优化的实现时才会定义,比如vector的push_back很快,于是这个方法就存在。但是push_front不快,于是就没有这个方法。deque和list才有。针对于array,vector和deque,它们都支持随机访问的迭代器,所以可以将通用的sort, partial_sort和stable_sort算法应用到它们的结构上,所以它们不需要再提供这么多方法。
以后我们凡是发现某一具体容器提供了与算法同名或者功能相同的方法,应该优化使用容器定义的,因为没有特殊好处,是不会定义它的。要么是通用算法不管用,要么是通用算法效率低。如果容器没有定义,再从算法库中选一个最优的。

我们看例程:

    std::list<int> minus_number;
    minus_number.push_back(-1);
    minus_number.push_back(-5);
    minus_number.sort();
    
    std::sort(odd_even.begin(),odd_even.end());
    
    std::cout<<"Merged:";
    std::merge(minus_number.begin(), minus_number.end(), odd_even.begin(), odd_even.end(),
               std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;
AI 代码解读

输出如下:

Merged:-5 -1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
AI 代码解读

如果有重复元素,则重复元素就会让其重复,没有去重操作。

比如我们增加一个重复的3:

    std::list<int> minus_number;
    minus_number.push_back(-1);
    minus_number.push_back(-5);
    minus_number.push_front(3);
    minus_number.sort();
    
    std::sort(odd_even.begin(),odd_even.end());
    
    std::cout<<"Merged:";
    std::merge(minus_number.begin(), minus_number.end(), odd_even.begin(), odd_even.end(),
               std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;
AI 代码解读

输出如下:

Merged:-5 -1 1 2 3 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
AI 代码解读

并集

并集就是在merge的基础上,去掉重复的元素。比如上例中的重复的3,就会被去除掉一个。

我们再看个例子:

    std::list<int> union_number;
    union_number.push_front(1);
    union_number.push_back(2);
    union_number.push_front(3);
    union_number.push_back(100);
    union_number.push_front(-1);
    union_number.sort();
    
    std::sort(odd_even.begin(),odd_even.end());
    
    std::cout<<"Union:";
    std::set_union(union_number.begin(), union_number.end(), odd_even.begin(), odd_even.end(),
               std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;
AI 代码解读

输出如下:

Union:-1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 
AI 代码解读

有一点请大家特别注意,并集是排除了两个容器的公共元素的重复,如果一个容器本身的值是重复的,求并集可不管这个事情。

例:

    std::list<int> union_number;
    union_number.push_front(1);
    union_number.push_back(2);
    union_number.push_front(3);
    union_number.push_back(100);
    union_number.push_front(-1);
    union_number.push_back(-1);
    union_number.sort();
    
    std::sort(odd_even.begin(),odd_even.end());
    
    std::cout<<"Union:";
    std::set_union(union_number.begin(), union_number.end(), odd_even.begin(), odd_even.end(),
               std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;
AI 代码解读

因为union_numberj里存入了两个-1,做完set_union还是两个,这个set_union算法不管的啊。
输出如下:

Union:-1 -1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 
AI 代码解读

交集

两个容器中相同的部分:

    std::list<int> intersection_number;
    intersection_number.push_front(1);
    intersection_number.push_back(2);
    intersection_number.push_front(3);
    intersection_number.push_back(100);
    intersection_number.push_front(-1);
    intersection_number.sort();
    
    std::sort(odd_even.begin(),odd_even.end());
    
    std::cout<<"Intersection:";
    std::set_intersection(intersection_number.begin(), intersection_number.end(), odd_even.begin(), odd_even.end(),
                   std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;
AI 代码解读

不同于并集,交集的数目就少了,输出如下:

Intersection:1 2 3 100
AI 代码解读

差集和对称差集

差集有两种算法:一种是算第一容器中有的,减去两个容器中的交集,算法是set_difference。第二种是两个容器的并集,减出两个容器的差集,算法是set_symmetric_difference。
一般,第一种称为差集,第二种称为对称差。

名称 差集 对称差集
包含元素 第一容器 - 交集 并集 - 交集
算法 std::set_difference std::set_symmetric_difference

我们还是看个例子就很容易理解了。

    std::list<int> diff_number;
    diff_number.push_front(1);
    diff_number.push_back(2);
    diff_number.push_front(3);
    diff_number.push_back(100);
    diff_number.push_front(-1);
    diff_number.sort();
    
    std::sort(odd_even.begin(),odd_even.end());
    
    std::cout<<"Difference:";
    std::set_difference(diff_number.begin(), diff_number.end(), odd_even.begin(), odd_even.end(),
                          std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;
    
    std::list<int> diff_number2;
    diff_number2.push_front(1);
    diff_number2.push_back(2);
    diff_number2.push_front(3);
    diff_number2.push_back(100);
    diff_number2.push_front(-1);
    diff_number2.sort();
    
    std::sort(odd_even.begin(),odd_even.end());
    
    std::cout<<"Difference2:";
    std::set_symmetric_difference(diff_number2.begin(), diff_number2.end(), odd_even.begin(), odd_even.end(),
                        std::ostream_iterator<int>(std::cout," "));
    std::cout<<std::endl;
AI 代码解读
Difference:-1 
Difference2:-1 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 
AI 代码解读

因为diff_number中的数字很少,减去并集,set_difference就剩一个-1了。但是对称差就是一个很大的集合了。

内部两个已排序空间合并

比如我们本来应该用std::merge算法来合并的,结果直接调用splice去连接了。

    std::list<int> part1;
    part1.push_back(1);
    part1.push_front(2);
    part1.push_back(3);
    part1.sort();
    
    std::list<int> part2;
    part2.push_back(-1);
    part2.push_back(-2);
    part2.push_back(-3);
    part2.sort();
    
    part1.splice(part1.end(), part2);
AI 代码解读

这时的结果是这样的:

1 2 3 -3 -2 -1 
AI 代码解读

用排序当然是可以解决这个问题的。但是针对两个有序的子序间,我们有更好的办法:

    auto pos3 = std::find(part1.begin(), part1.end(), 3);
    std::inplace_merge(part1.begin(), ++pos3,part1.end());
AI 代码解读

std::inplace_merge就是在同一个容器内做merge,使其变得有序的算法。

经过上面一折腾,如果又变成有序的了:

-3 -2 -1 1 2 3 
AI 代码解读

如何判断一个容器或者区间是否有序?

我们之前学会了做子集,交集,并集,差集等各种操作。但是这样集合操作依赖于已经排序,我们怎样知道是不是已经排好序了呢?C++11又出来帮忙了,自C++11始,为我们提供了判断是否有序,是否分区等的算法。

从此以后,我们不需要上来就做排序了,可以先判断一下,没排好再排嘛:

    if(!std::is_sorted(odd_even.cbegin(), odd_even.cend())){
        std::sort(odd_even.begin(),odd_even.end());
    }
AI 代码解读

同样的函数还有is_partitioned和is_heap。

另外,我们还可以知道,是到哪个元素开始,这个有序,或者划分或堆被破坏了。算法为is_sorted_until,is_partition_until和is_heap_until.

例:看看我们刚才inplace_merge之前,是哪个元素开始破坏了有序吧:

    std::list<int> part1;
    part1.push_back(1);
    part1.push_front(2);
    part1.push_back(3);
    part1.sort();
    
    std::list<int> part2;
    part2.push_back(-1);
    part2.push_back(-2);
    part2.push_back(-3);
    part2.sort();
    
    part1.splice(part1.end(), part2);
    
    std::cout<<"First disordered item:"<<*std::is_sorted_until(part1.cbegin(), part1.cend())<<"\n";
AI 代码解读

输出为:

First disordered item:-3
AI 代码解读

因为当时,part1的值为

1 2 3 -3 -2 -1 
AI 代码解读

小结

好了,排序相关的主要算法就是这么多。

除了查找元素中的lower_bound,upper_bound,equal_range和heap算法中的push_heap和pop_heap,划分区间的partition_copy,其他主要算法我们都已经学完了。

本节我们学了两个排序之后的容器或者区间的集合的求子集,交,并,差,对称差操作。一个容器内两个区间的合并。
如果不确定是否已经排好序了,C++11为我们提供了一系列算法来进行判断。

好了,最后再更新一下大图,我们在算法地图上又进了一大步。
所有打绿勾的都是已经学过的了。

Paste_Image.png

目录
打赏
0
1
0
0
577
分享
相关文章
解读 C++ 助力的局域网监控电脑网络连接算法
本文探讨了使用C++语言实现局域网监控电脑中网络连接监控的算法。通过将局域网的拓扑结构建模为图(Graph)数据结构,每台电脑作为顶点,网络连接作为边,可高效管理与监控动态变化的网络连接。文章展示了基于深度优先搜索(DFS)的连通性检测算法,用于判断两节点间是否存在路径,助力故障排查与流量优化。C++的高效性能结合图算法,为保障网络秩序与信息安全提供了坚实基础,未来可进一步优化以应对无线网络等新挑战。
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
54 15
公司局域网管理中的哈希表查找优化 C++ 算法探究
在数字化办公环境中,公司局域网管理至关重要。哈希表作为一种高效的数据结构,通过哈希函数将关键值(如IP地址、账号)映射到数组索引,实现快速的插入、删除与查找操作。例如,在员工登录验证和设备信息管理中,哈希表能显著提升效率,避免传统线性查找的低效问题。本文以C++为例,展示了哈希表在局域网管理中的具体应用,包括设备MAC地址与IP分配的存储与查询,并探讨了优化哈希函数和扩容策略,确保网络管理高效准确。
MapReduce在实现PageRank算法中的应用
总结来说,在实现PageRank算法时使用MapReduce能够有效地进行大规模并行计算,并且具有良好的容错性和可扩展性。
120 76
企业员工数据泄露防范策略:基于 C++ 语言的布隆过滤器算法剖析[如何防止员工泄密]
企业运营过程中,防范员工泄密是信息安全领域的核心议题。员工泄密可能致使企业核心数据、商业机密等关键资产的流失,进而给企业造成严重损失。为应对这一挑战,借助恰当的数据结构与算法成为强化信息防护的有效路径。本文专注于 C++ 语言中的布隆过滤器算法,深入探究其在防范员工泄密场景中的应用。
29 8
|
10天前
|
基于 PHP 语言的滑动窗口频率统计算法在公司局域网监控电脑日志分析中的应用研究
在当代企业网络架构中,公司局域网监控电脑系统需实时处理海量终端设备产生的连接日志。每台设备平均每分钟生成 3 至 5 条网络请求记录,这对监控系统的数据处理能力提出了极高要求。传统关系型数据库在应对这种高频写入场景时,性能往往难以令人满意。故而,引入特定的内存数据结构与优化算法成为必然选择。
21 3
从第十批算法备案通过名单中分析算法的属地占比、行业及应用情况
2025年3月12日,国家网信办公布第十批深度合成算法通过名单,共395款。主要分布在广东、北京、上海、浙江等地,占比超80%,涵盖智能对话、图像生成、文本生成等多行业。典型应用包括医疗、教育、金融等领域,如觅健医疗内容生成算法、匠邦AI智能生成合成算法等。服务角色以面向用户为主,技术趋势为多模态融合与垂直领域专业化。
Dev-C++保姆级安装教程:Win10/Win11环境配置+避坑指南(附下载验证)
Dev-C++ 是一款专为 Windows 系统设计的轻量级 C/C++ 集成开发环境(IDE),内置 MinGW 编译器与调试器,支持代码高亮、项目管理等功能。4.9.9 版本作为经典稳定版,适合初学者和教学使用。本文详细介绍其安装流程、配置方法、功能验证及常见问题解决,同时提供进阶技巧和扩展学习资源,帮助用户快速上手并高效开发。
JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
Array.sort() 是一个功能强大的方法,通过自定义的比较函数,可以处理各种复杂的排序逻辑。无论是简单的数字排序,还是多字段、嵌套对象、分组排序等高级应用,Array.sort() 都能胜任。同时,通过性能优化技巧(如映射排序)和结合其他数组方法(如 reduce),Array.sort() 可以用来实现高效的数据处理逻辑。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
从第九批深度合成备案通过公示名单分析算法备案属地、行业及应用领域占比
2024年12月20日,中央网信办公布第九批深度合成算法名单。分析显示,教育、智能对话、医疗健康和图像生成为核心应用领域。文本生成占比最高(57.56%),涵盖智能客服、法律咨询等;图像/视频生成次之(27.32%),应用于广告设计、影视制作等。北京、广东、浙江等地技术集中度高,多模态融合成未来重点。垂直行业如医疗、教育、金融加速引入AI,提升效率与用户体验。

热门文章

最新文章