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

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-----------------------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ctrlturtle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值