您的位置:首页 > 编程语言 > C语言/C++

关于OJ上STL库使用的一点经验记录

2017-02-14 16:01 239 查看
1.关于去重与排序问题

一般性方法:sort()+unique()+erase().  

这三种泛型算法组合可以解决去重和排序的绝大多数问题,第一个排序算法根据容器自动选择排序算法进行排序,后两者结合起来可以实现去重。需要注意的是:unique和remove/remove_if类似,不是真正的去重(删掉重复的元素),而是将重复的元素移到容器的末尾,并返回第一个重复元素的位置(迭代器),然后再利用erase的范围删除版本删除所有重复元素,示例如下:

vector<int> vec_int={0,1,1,2,4,0,7,4,5};
sort(vec_int.begin(),vec_int.end());
auto it3=unique(vec_int.begin(),vec_int.end());
vec_int.erase(it3,vec_int.end());
for(auto e:vec_int){
cout<<e<<endl;
}
如果只要求去重不排序(保持原序),此方法不通,因为unique必须保证有序。

对于一些特殊情况,还可以采用以下方法:

(1)去重+排序:map和set均为有序容器(底层RBT,平衡后的二叉排序树),map和set在加入元素时会自动排序:

map默认按key升序排列,set直接升序排列元素(key即value)

map去重效果示例如下:

//map的去重效果(使用insert的第一个版本)
map<int,int> map_values;
int num,val;
for(int i=0;i<5;i++){
cin>>num>>val;
auto ret=map_values.insert(pair<int,int>(num,val));
if(ret.second==false)//map对于关键字重复的情况会过滤
cout<<num<<"is already existed"<<endl;
}
for(auto e:map_values){
cout<<e.first<<" "<<e.second<<endl;
}


注:因此千万别妄想对map或set指定位置(迭代器)插入元素,插入元素时会根据key排序和去重,对于输出格式要求严格的OJ,一般输出都有顺序要求,map和set用的较少,一旦使用很难保持原序。除非结果本身要求排序。

(2)只去重(保持原序):可以使用find实现:

vector<int> vec_int={0,1,1,2,4,0,7,4,5},vec_temp;
for(auto e:vec_int){
if(find(vec_temp.begin(),vec_temp.end(),e)==vec_temp.end()){
vec_temp.push_back(e);//不重复的元素添加进来
}
}
for(auto e:vec_temp){
cout<<e<<endl;
}
如果还需要排序,另外调用sort()即可。

另外,有些编程实现中应用去重的技巧可以简化问题,比如牛客网上华为OJ的一道统计字符个数的问题,如果直接使用count()遍历每一个字符并统计,会对重复的字符统计多次,比如一个字符'a'在string中出现3次,遍历调用count()并将结果存入vector<int>则会出现3个3,其实上同一个字符出现了3次。可以拷贝一个string副本先去重,将去重版本的string的每个字符在未去重的string中count(),这样便解决了重复字符的多次统计问题:

string temp=str;
auto it=unique(temp.begin(),temp.end());
temp.erase(it,temp.end());
vector<int > vec_cnt;
for(auto c:temp){
vec_cnt.push_back(count(str.begin(),str.end(),c));
}


2.关于遍历删除问题

一般性方法:remove/remove_if()+erase()

这两种泛型算法组合能完成绝大多数的条件删除操作(容器中符合条件的全部去掉)。与unique同理,remove必须和erase结合使用才能真正删除元素。

bool isOdd(int val)
{
if(val%2==0)
return true;
return false;
}

//int main()中如下使用:
vector<int> vec_int={0,1,1,2,4,0,7,4,5};
auto it4=remove_if(vec_int.begin(),vec_int.end(),isOdd);
vec_int.erase(it4,vec_int.end());
for(auto e:vec_int){
cout<<e<<endl;
}

对于更常见的条件:等值时删除,直接调用remove()即可:

auto it=remove(vec_int.begin(),vec_int.end(),5);


如果需要自行实现条件删除操作,需要考虑迭代器失效问题。可如下:

vector<string> vec_str={"hello","hi","world"};
auto it=vec_str.begin();
while(it!=vec_str.end()){
if(it->size()==5)//指定位置删除示例
it=vec_str.erase(it);//返回的it自动指向下一个位置
else
it++;//未删除应移到下一个位置,否则陷入死循环
}
for(auto e:vec_str)
cout<<e<<endl;
关于STL容器的迭代器失效问题比较头疼,如果不注意经常神出鬼没,为此自己专门做了点总结。

详见博客:《STL容器的遍历插入或删除(迭代器失效问题的统一解决)》

3.关于遍历插入问题

一般性方法:如果对容器需要进行多次条件插入操作,尽量使用list容器而不是vector

乍一看以为这条忠告是从效率上考虑的,其实更为重要的是可靠性问题,vector容器使用insert()可以在指定位置插入,但是遍历过程中每次插入都会导致迭代器失效,而失效后的重定位比较麻烦(不仅仅是获取insert的返回迭代器那么简单,还需要根据需要前进迭代器以保证循环),因此为避开迭代器失效问题,对于这种条件插入较多的情况应采用list更为方便(当然也会带来效率上的提高,容器内部不用移动元素了)。示例如下:

//vector遍历插入示例(此处list作为中间容器,以便示例resize和copy的配合使用)
vec_str.push_back("hello");
vec_str.push_back("hi");
vec_str.push_back("world");
list<string> list_str;
list_str.resize(vec_str.size());//resize必须,否则copy越界
copy(vec_str.begin(),vec_str.end(),list_str.begin());
for(auto it1=list_str.begin();it1!=list_str.end();it1++){
if(it1->size()==5)
list_str.insert(it1,"test");//直接插入,迭代器不会失效
}
vec_str.resize(list_str.size());//resize必须,list_str大小已变,否则copy越界
copy(list_str.begin(),list_str.end(),vec_str.begin());
for(auto e:vec_str){
cout<<e<<endl;
}
for(auto e:map_values){
cout<<e.first<<" "<<e.second<<endl;
}
关于这种情况的迭代器失效问题,详见博客:《STL容器的遍历插入或删除(迭代器失效问题的统一解决)》

4.关于map的使用

map的操作比较特殊,但是pair<>型的元素很方便,以致初学者看到键值对就想到用map,这是对map最大的误解。map是有序容器,会破坏原有顺序(插入的过程即排序,二叉排序树的基本要求),map虽然实现了在指定迭代器位置插入的insert()函数,但这个函数着实容易误导人,因为你企图在某个迭代器位置插入一个pair的想法可能并不能如愿,因为它最终输出的结果一定是排序过的!

所以,自己跳过坑后得出的忠告:

map适用于对顺序无要求或者强行要求按键值排序的问题,如果仅需要pair型元素的容器,大可使用vector<pair<>>或list<pair<>>。
另外,还有一个常见问题就是:如何自定义排序map?

map默认是按key升序排序,如果需要按value降序排列怎么办?

第一反应是利用stl中提供的sort算法实现,不幸的是:sort算法有个限制,利用sort算法只能对序列容器进行排序,就是线性的(如vector,list,deque)。map也是一个集合容器,它里面存储的元素是pair,但是它不是线性存储的(前面提过,像红黑树),所以利用sort不能直接和map结合进行排序。

 虽然不能直接用sort对map进行排序,那么我们可不可以迂回一下,把map中的元素放到序列容器(如vector)中,然后再对这些元素进行排序呢?这个想法看似是可行的。但是要对序列容器中的元素进行排序,也有个必要条件:就是容器中的元素必须是可比较的,也就是实现了<操作的。然而map中的元素是pair型的,因此还需要实现一个cmpByValue()函数:

int cmpByValue(const pair<int,int> &x,const pair<int,int> &y)
{
return x.second>y.second;
}
然后将map中元素copy到vector中再sort():

//把map中元素转存到vector中
map<int,int> map={{0,5},{2,4},{1,7},{5,9},{2,8}};
vector<pair<int,int>> vec_pair;
vec_pair.resize(map.size());
copy(map.begin(), map.end(),vec_pair.begin());
sort(vec_pair.begin(), vec_pair.end(), cmpByValue);
for(auto e:vec_pair){
cout<<e.first<<" "<<e.second<<endl;
}


总结:如果需要对map中元素自定义排序,可以转存到vector中再sort(),sort()之前需要重新实现比较函数以便sort()调用。

---------------------------------------------------------------待补   2017/2/14-----------------------------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ OJ STL 算法