C++ STL:泛型算法lower_bound用于关联容器set等的坑

1.问题导入(背景):

我在写某道程序设计竞赛题的时候需要对set里面的元素(类型为pair<int,int>)进行查找,众所周知set自带lower_bound函数。但是我的需求是比较pair<int,int>的大小时优先考虑second的大小。而set自带的lower_bound函数无法传入一个自定义的比较函数,只能基于元素默认的比较方法(对于pair<int,int>默认优先考虑first的大小,first大小相同时才比较second的大小)。于是我就想,泛型函数lower_bound可以传入一个函数指针实现自定义比较函数(和往sort里传一个函数指针一样),那么我用泛型函数去进行二分查找不就好了吗(代码为lower_bound(S.begin(),S.end(),myCmp),其中S为set<pair<int,int>>型,myCmp是自定义的一个函数)。

2.发现异常:

然而,时间复杂度为nlogn的算法竟然在1e5范围的数据上超时了。

3.问题解决:

我的下意识解决方案是泛型函数lower_bound可能不适合用于set中(以往都是用在vector或者数组上的),于是我将原来的pair<int,int>的first和second调换了一下,并且利用set自带的lower_bound函数实现二分查找,最终成功ac。(将first和second调换的意思是:例如我用pair<int,int>存一个区间的左右端点,一开始我的first存的是左端点l,second存的是右端点r,现在我交换一下,first存的r,second存的l,这样无论是之前未交换时调用泛型lower_bound并且采用自定义myCmp还是交换后调用set自带的lower_bound,都是优先基于r比较)。当然还有另外的解决方法,例如不使用pair<int,int>,而自定义一个结构体/类,并且重载“operator<运算符”。

 

4.进一步探索:

虽然成功ac了,但是我好奇泛型函数lower_bound用于set为什么会导致超时。于是首先我通过搜索引擎搜索“泛型函数lower_bound用于set”等关键词,没有搜到相关结果(这也是为什么我要写这一篇博客),然后我去翻阅书籍:《C++Primer(第5版)》,最终找到相关表述(见下图),同时我通过搜索引擎搜索“泛型函数lower_bound用于关联容器",找到了一篇和书上内容完全相同的博客(链接:https://blog.csdn.net/KCDCY/article/details/123065792)

 

 (关于为什么这里截取博客的图而不是用书上的图,因为我没有书本的电子版,而且也不想放照片在这篇博客里,上图应该挺好的。)

解释:泛型算法不建议用于关联容器,可以理解为因为关联容器不支持像数组和vector那样的随机访问,所以使用泛型搜索算法会导致线性查找(我的猜测)

 

5.实验验证:

经过上面《C++Primer(第5版)》对“泛型函数不建议用于关联容器”的解释,我猜测对关联容器实验泛型搜索算法会导致线性查找,于是进一步做实验验证:

运行下述代码:

 1 //exeCreate 2022-5-15 10:35:11    
 2 
 3 #include<stdio.h>
 4 #include<stdlib.h>
 5 #include<iostream>
 6 #include<algorithm>
 7 #include<string>
 8 #include<string.h>
 9 #include<cmath>
10 #include<vector>
11 #include<set>
12 #include<queue>
13 #include<ctime>
14 
15 #define rep(i,a,b) for(int i=(a);i<=(b);++i)
16 #define per(i,a,b) for(int i=(a);i>=(b);--i)
17 #define fi first
18 #define se second
19 #define mp make_pair
20 #define all(x) x.begin(),x.end()
21 
22 using namespace std;
23 typedef long long ll;
24 typedef pair<ll,ll> PII;
25 typedef pair<int,int> Pii;
26 const int maxn=2e5+10,maxm=maxn;
27 const ll mod=1e9+7,inf=0x3f3f3f3f;
28 
29 set<int> S;
30 int main()
31 {
32     int n=1e4;
33     rep(i,1,n){
34         S.insert(i);
35     }
36 
37     clock_t st,ed;
38     st = clock();
39     rep(i,1,n){
40         auto iter = S.lower_bound(i);
41     }
42     ed = clock();
43     printf("STL:set自带lower_bound算法费时:%lfs\n",(double)(ed-st)/CLOCKS_PER_SEC);
44     
45     st = clock();
46     rep(i,1,n){
47         auto iter = lower_bound(S.begin(),S.end(),i);
48     }
49     ed = clock();
50     printf("lower_bound泛型算法费时:%lfs\n",(double)(ed-st)/CLOCKS_PER_SEC);
51 
52     
53     fflush(stdin);    getchar();
54     return 0;
55 }
56 //maxn改好了么?
实验测试代码

运行结果:

STL:set自带lower_bound算法费时:0.000000s
lower_bound泛型算法费时:1.074000s

=====
Used: 1060 ms, 1512 KB

 

可见两个lower_bound的时间效率差距之大,可以认为后者是线性查找,所以测试代码中后者的部分(45-50行)的时间复杂度是O(n2)的(也就解释了为什么1e4的数据要1秒才能跑完)

 

6.结论与展望:

本文探讨了将泛型函数用于关联容器的糟糕(例子为泛型lower_bound用于set),通过查阅书籍(《C++Primer(第5版)》)猜测将泛型函数用于关联容器将导致线性查找(总之运行效率很低),并通过代码大致验证了这个猜想。记录下本篇博客是因为没有搜到讲解相关问题的博客,希望能被更多有相关困惑的人看到。本文对原理的探索是比较浅显的,更进一步需要阅读STL的源码来了解泛型函数对关联容器的具体操作。

 

最后说一句:通过这次经历我又双叒叕体会到了“系统地学习”,“广泛地学习”,“积累知识”的重要性,个人对于C++的语法特性尤其是STL是比较感兴趣的,但因为忙于其他事业并没能很好地系统学习(虽然以前基本读完了某本教材),《C++Primer(第5版)》在我现在看来是一本宝藏,希望我能坚持抽空将其系统地学习。

 

posted @ 2022-05-16 19:00  Laozhu1234  阅读(974)  评论(0编辑  收藏  举报