stl 在 acm中的应用总结

总结一些在acm中常用的小技巧,小函数

之前尝试着总结过很多次。都失败了,因为总是担心不全,理解的也不是很透彻。这次再来一次。。。其实之前保存了很多的草稿就不发布了,当然,下面说的很不全面,路过的大牛求指点,求补充。

标签: 代码姿势


有关stl模板

函数模板

例子:

#include<iostream>

#include<string>

using namespace std;

//定义函数模板

template<class T>   //template 是关键字,T 表示一种待实例化的类型

                    //template<typename T>  也是对的

T MAX(T a, T b)//函数模板,函数名为 max,此函数有2个T类型的参数,返回类型为T

{

  return (a>b)?a:b; 

}

//在此例实例化的时候,T可以是多种类型的,int,char,string…

int main()

{

int x=2,y=6;

    double x1=9.123,y1=12.6543;

    cout<<"把T实例化为int:"<<MAX(x,y)<<endl;//实例化函数模板,把T实例化为int

    cout<<"把T实例化为double:"<<MAX(x1,y1)<<endl;  //把T实例化为double

}

类模板

例子:

#include<iostream>

using namespace std;

//定义名为ex_class的类模板

template < typename T>  class ex_class

{

    T value;

public:

    ex_class(T v) { value=v; }

    void set_value(T v) { value=v; }

    T get_value(void) {return value;}

};

//main()函数中测试ex_class类模板

int main()

{

    //测试int类型数据

    ex_class <int> a(5),b(10);

    cout<<"a.value:"<<a.get_value()<<endl;

    cout<<"b.value:"<<b.get_value()<<endl;

    //测试char类型数据

    ex_class <char> ch('A');

    cout<<"ch.value:"<<ch.get_value()<<endl;

    ch.set_value('a');

    cout<<"ch.value:"<<ch.get_value()<<endl;

    //测试double类型数据

    ex_class <double> x(5.5);

    cout<<"x.value:"<<x.get_value()<<endl;

    x.set_value(7.5);

    cout<<"x.value:"<<x.get_value()<<endl;

}

总结一下:

模板的主要作用是在一个函数要用到多重数据类型的时候可以通过这种形式使得代码量减少。代码看上去更加整洁。

有关stl容器

STL在ACM中的应用
STL 提供三种类型的组件:容器、迭代器和算法,它们都支持泛型程序设计标准。在ACM中充分利用STL可以大大的简化程序,提高解题效率。
1、容器主要有两类:顺序容器和关联容器。顺序容器(vector/list/deque/string)等是一系列元素的有序集合。关联容器(set/multiset/map/multimap)包含查找元素的键值。
2、迭代器的作用是遍历容器。
3、STL算法库包含四类算法:排序算法,不可变算法,变序算法和数值算法。

容器

  • vector
vector 基本操作
vector<int> v;  
v.begin();   //容器的起始位置  
v.end();    //容器最后一个位置后的位置  
v.front();v.back();   //返回第一个元素(最后一个元素,但不判断时候存在  
v.empty();    //返回是否容器为空  
v.clear();    //清空容器  
v.erase(m);    //删除m位置的数据,并返回下一个数据的地址(m是迭代器)  
v.erase(m,n);     //删除m到n之间的数据,并返回下一个数据的地址  
v2.assign(8,1);   // 重新给vec2赋值,8个成员的初始值都为1
v.push_back(element);    //压入一个元素到末端  
v.pop_back();    //弹出最后一个元素  
v.reserve(100);v.resize(101);    //resize已经创建空间如果再v.push_back();空间就会到101,而reserve只是预留空间并没有真正创建,v.push_back();只是在第1位  
v.size();v.capacity();       //size表示的是已经创建的空间大小也可以表示元素个数可用v[]的形式直接访问,capacity容器容量,是预留空间并没有实际创建  
swap(a,b);      //交换两个元素的位置如:swap(v[0],v[1]);  
vector<int>  v(10);    //创建一个前十个元素为int的容器  
vector<string> v(10,string("I"));  //使容器的前10个元素都为string型,并且都初始化为I  
vector<string> v1(v2);    //对于已经存在的v2创建一个v1副本  
v.insert(place,element);  
v.insert(place,n,element);     //在place(迭代器)位插入n个元素  
//注:对vector元素的访问可以用类似c语言的v[],但是最好用v.at(),它会检查是否越界更安全  
v[0];    // A
v.at[0];  // B 这样越界的时候比较安全

关于v.reserve(100);v.resize(101);我的理解并不深刻,也没有自己用到过,这里有一个讲解的很好的博客,收存

vector 遍历

vector 有两种遍历方法
一种是使用迭代器

vector<int> ::iterator it;
for(it = v.begin(); it!=v.end(); it++)
{
    cout<<(*it)<<endl;
}

还有就是直接用下标的方式访问

v[0];    // A
v.at[0];  // B 这样越界的时候比较安全
vector 算法

(1) 使用reverse将元素翻转:需要头文件#include

reverse(vec.begin(),vec.end());将元素翻转(在vector中,如果一个函数中需要两个迭代器,

一般后一个都不包含.)

(2)使用sort排序:需要头文件#include

sort(vec.begin(),vec.end());(默认是按升序排列,即从小到大).

可以通过重写排序比较函数按照降序比较,如下:

定义排序比较函数:

bool Comp(const int &a,const int &b)
{
    return a>b;
}
调用时:sort(vec.begin(),vec.end(),Comp),这样就降序排序。

注:vector是顺序容器,没有find函数、

这里一定要强调的一种用法就是vector实现二维数组,因为在图论中用vector存图也是一种很好的方法。vector可以代替head数组。

之前网络流有一个题用vector建边,自己看看就懂了,和head是一样原理的

  • deque双端队列

容器类与vector类似,支持随机访问和快速插入删除,它在容器中某一位置上的操作所花费的是线性时间。与vector不同的是,deque还支持从开始端插入数据:push_front()。此外deque也不支持与vector的capacity()、reserve()类似的操作。

#include<queue>
deque <int> deq;//初始化对象为空
deque <int> deq1(10,6);//对象初始化有10个值为6的元素
deque <int>:: iterator it;
for(it = deq.begin(); it!=deq.end(); it++){
    cout<<*it<<endl;
}
deq.push_back(ele);//从队列尾部插入
deq.push_front(ele);//从队列头部插入
deq.insert(deq.begin()+1,3,9);//从队列中间插入三个9
//和vector一样,双向队列也可以用下标的形式访问,也可以用at
deq.at(n);//返回的是n这个下标的值
//也可以直接deq[n]

deq1.at(1)=10;
deq1[2]=12;
//从deq1序列的前后各移去一个元素
deq1.pop_front();
deq1.pop_back();

deq.erase(deq.begin()+1);//清除deq的第二个元素

//对deq2赋值并显示
deq2.assign(8,1);
cout<<"deq2.assign(8,1):"<<endl;
put_deque(deq2,"deq2");
//erase(),assign()是大多数容器都有的操作
  • 集合set(特点是没有重复元素且元素时有序的)

注:set是STL中一种标准关联容器(vector,list,string,deque都是序列容器,而set,multiset,map,multimap是标准关联容器),它底层使用平衡的搜索树——红黑树实现,插入删除操作时仅仅需要指针操作节点即可完成,不涉及到内存移动和拷贝,所以效率比较高。set,顾名思义是“集合”的意思,在set中元素都是唯一的,而且默认情况下会对元素自动进行升序排列,支持集合的交(set_intersection),差(set_difference) 并(set_union),对称差(set_symmetric_difference) 等一些集合上的操作,如果需要集合中的元素允许重复那么可以使用multiset

关于set,必须说明的是set关联式容器。set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。默认的是从小到大的排序
应该注意的是set中数元素的值不能直接被改变。
C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也成为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。

同样,给出一个set用法学习的网站

其实数组的去重也是很容易的
但是注意数组的去重是在数组排序的基础上实现的,它去重的原理也不是删除这些元素,而是将重复的元素放到数组的最后面,返回的是数组最后一个有效元素的地址,所以我们经常这么做

int mp[N];
sort(mp,mp+n);
int len = unique(mp,mp+n)-mp;
set 基本操作 遍历 及其对应算法
set<int> s;
s.insert(element);//插入元素
//遍历整个几何(不重复的有序序列)
set<int>:: iterator it;
for(it = s.begin(); it!=s.end(); it++){
    cout<<(*it)<<endl;
}
s.size();//返回元素个数
s.find(ele);//返回元素的下标,没找到的话返回s.end();
//获得两个set的并
set<int> s1;
set<int> s2;
set<int> s3;//存结果
set_union(s1.begin(),s1.end(),s2.begin(),s2.end(),insert_iterator<set<int> >(s3,s3.begin()));
//输出也可以用下面的形式
copy(s3.begin(),s3.end(),ostream_iterator<int>(cout," "));
cout<<endl;
//获得两个set的交,注意进行集合操作之前接收结果的set要调用clear()函数清空一下
s3.clear();
set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(),insert_iterator<set<int> >(s3.s3.begin()));
copy(s3.begin(),s3.end(),ostream_iterator<int>(cout," "));
cout<<endl;

//获得两个set的差
s3.clear();
set_difference(s1.begin(),s1.end(),s2.begin(),s2.end(),insert_iterator<set<int> >(s3,s3.begin()));
cout<<"Difference:";
copy(s3.begin(),s3.end(),ostream_iterator<int>(cout," "));
cout<<endl;
//获得两个set的对称差,也就是假设两个集合分别为A和B那么对称差为AUB-A∩B
   eg3.clear();
   set_symmetric_difference(s1.begin(),s1.end(),s2.begin(),s2.end(),insert_iterator<set<int> >(s3,s3.begin()));
   copy(s3.begin(),s3.end(),ostream_iterator<int>(cout," "));
   cout<<endl;

特别注意:常用的是将set升序排列

set<int,less<int> > set1;//降序排列
set<int,greater<int> > set1;//升序排列

注意:

  1. set只能通过迭代器访问,不可以通过下标直接访问。
  2. set是有序的序列。所以可以使用stl自带的二分函数(二分函数将在后面介绍)
  • map 和 Multimap映射

可对数据进行快速高效检索,而且map支持通过下标直接访问及\(map["string"] = 4\)这种形式,可以实现两个值得对应,常见的定义形式有$map<string ,int>mapstring; $

map的基本操作
#include<map>

map<int, string> mym;
//三种插入方法
mym.insert(pair<int,string>(120,"haha"));
mym.insert(map<int,string>::value_type(312,"gaga"));
mym[120] = "haha";
//find()函数返回一个迭代器指向键值为key的元素,如果没找到就返回指向map尾部的迭代器。
map<int ,string >::iterator l_it; 
l_it=maplive.find(112);//返回的是一个指针
if(l_it==maplive.end())
    cout<<"we do not find112"<<endl;
else
    cout<<"wo find112"<<endl;

//也可以用这种方式来查找
map<int ,string> m;
m[1] = "haa";
m[44] = "xixi";
if(m[12]=="") puts("NO");

//删除一个元素
map<int,string> m;
map<int,string>::iterator it;
it = m.find(112);
if(it == m.end()){ exit;}
else m.erase(it);
//类似的也可以通过赋值为空来将一个元素删除

//Map中的swap不是一个容器中的元素交换,而是两个容器交换

begin() //返回指向map头部的迭代器 
clear()// 删除所有元素 
count()// 返回指定元素出现的次数 
empty() //如果map为空则返回true 
end() //返回指向map末尾的迭代器 
equal_range()// 返回特殊条目的迭代器对 
erase() //删除一个元素 
find() //查找一个元素 
insert() //插入元素 
lower_bound() //返回键值>=给定元素的第一个位置 
max_size() //返回可以容纳的最大元素个数 
rbegin() //返回一个指向map尾部的逆向迭代器 
rend() //返回一个指向map头部的逆向迭代器 
size() //返回map中元素的个数 
swap()// 交换两个map 
upper_bound()// 返回键值>给定元素的第一个位置 
value_comp() //返回比较元素value的函数

multimap基本用法:

这里一定要注意的是multimap

//multimap允许重复的键值插入容器
//        **********************************************************      
//        * pair只包含一对数值:pair<int,char>                       *
//        * map是一个集合类型,永远保持排好序的,                   *
//  pair  * map每一个成员就是一个pair,例如:map<int,char>           *
//        * map的insert()可以把一个pair对象作为map的参数,例如map<p> *
//        ***********************************************************
#pragma warning(disable:4786)
#include<map>
#include<iostream>
using namespace std;

int main(void)
{
 multimap<int,char*> m;
 //multimap的插入只能用insert()不能用数组
 m.insert(pair<int,char*>(1,"apple"));
    m.insert(pair<int,char*>(1,"pear"));//apple和pear的价钱完全有可能是一样的
 m.insert(pair<int,char*>(2,"banana"));
 //multimap的遍历只能用迭代器方式不能用数组
 cout<<"***************************************"<<endl;
 multimap<int,char*>::iterator i,iend;
 iend=m.end();
 for(i=m.begin();i!=iend;i++)
 {
  cout<<(*i).second<<"的价钱是"
   <<(*i).first<<"元/斤\n";
 }
 cout<<"***************************************"<<endl;
    //元素的反相遍历
 multimap<int,char*>::reverse_iterator j,jend;
 jend=m.rend();
 for(j=m.rbegin();j!=jend;j++)
 {
  cout<<(*j).second<<"的价钱是"
   <<(*j).first<<"元/斤\n";
 }
 cout<<"***************************************"<<endl;
 //元素的搜索find(),pair<iterator,iterator>equal_range(const key_type &k)const
    //和multiset的用法一样
 multimap<int,char*>::iterator s;
 s=m.find(1);//find()只要找到一个就行了,然后立即返回。
 cout<<(*s).second<<"    "
  <<(*s).first<<endl;
 cout<<"键值等于1的元素个数是:"<<m.count(1)<<endl;
 cout<<"***************************************"<<endl;
 //删除 erase(),clear()
 m.erase(1);
    for(i=m.begin();i!=iend;i++)
 {
  cout<<(*i).second<<"的价钱是"
   <<(*i).first<<"元/斤\n";
 }
    return 0;
}

  • queue与priority_queue

元素先进先出,只能充队列末端插入和删除元素

队列基本操作
#include<queue>
queue<int> q1;
q.push(x);//将元素插入到队列末尾
q.pop();//弹出队列的第一个元素(队首),并不会返回元素的值
q.front();//访问队首的元素
q.back();//访问队尾元素
q.size();//返回队列中元素的个数

头文件中,还定义了一个非常有用的模版类priority_queue(优先队列),优先队列与队列的差别在于优先队列不是按照入队的顺序出队,而是按照队列中元素的优先权顺序出队(默认为大者优先,也可以通过指定算子来指定自己的优先顺序)。

priority_queue

*基本操作和queue一样注意的就是它每次出队都是队列中最大的元素。(也可以更改成最小的)
*

priority_queue<int >q1;
priority_queue<pair<int,int> >q2;
priority_queue<int,vector<int>,greater<int> >q3;//定义小的先出队


  • stack 栈

栈是后进先出的容器,操作和queue很像

stack 基本操作

#include<stack>
stack<int> s;
s.push();//压栈
s.pop();//出栈
s.top();//获取栈顶元素
s.empty();
s.size();
s.end();
s.begin();
其实还有堆的操作。。。stl还有一些其他的容器,因为不常用,所以,我也就不列举了

下面介绍一下stl中的一些常用的算法

  • find(); count(); unique(); sort(); 这些在前面介绍了就不说了

  • copy:
    \(copy(v.begin(),v.end(),l.begin());\)将v中的元素复制到l中。

  • 堆操作
make_heap(v.begin(),v.end());//创建堆
sort_heap(v.begin(),v.end());//堆排序
push_heap(v.begin(),v.end());//堆入队
pop_heap(v.begin(),v.end());//堆出队

有一个博客:
可以参考

  • 全排列函数
    两个重载函数,第二个带谓词参数_Comp,其中只带两个参数的版本,默认谓词函数为"小于".

返回值:bool类型

分析next_permutation函数执行过程:

假设数列 d1,d2,d3,d4……

范围由[first,last)标记,调用next_permutation使数列逐次增大,这个递增过程按照字典序。例如,在字母表中,abcd的下一单词排列为abdc,但是,有一关键点,如何确定这个下一排列为字典序中的next,而不是next->next->next……

若当前调用排列到达最大字典序,比如dcba,就返回false,同时重新设置该排列为最小字典序。

返回为true表示生成下一排列成功。下面着重分析此过程:

根据标记从后往前比较相邻两数据,若前者小于(默认为小于)后者,标志前者为X1(位置PX)表示将被替换,再次重后往前搜索第一个不小于X1的数据,标记为X2。交换X1,X2,然后把[PX+1,last)标记范围置逆。完成。

要点:为什么这样就可以保证得到的为最小递增。

从位置first开始原数列与新数列不同的数据位置是PX,并且新数据为X2。[PX+1,last)总是递减的,[first,PX)没有改变,因为X2>X1,所以不管X2后面怎样排列都比原数列大,反转[PX+1,last)使此子数列(递增)为最小。从而保证的新数列为原数列的字典序排列next。

明白了这个原理后,看下面例子:

int main(){
int a[] = {3,1,2};
do{
cout << a[0] << " " << a1 << " " << a2 << endl;
}
while (next_permutation(a,a+3));
return 0;
}

输出:312/321 因为原数列不是从最小字典排列开始。

所以要想得到所有全排列

int a[] = {3,1,2}; change to int a[] = {1,2,3};

另外,库中另一函数prev_permutation与next_permutation相反,由原排列得到字典序中上一次最近排列。

所以

int main(){
int a[] = {3,2,1};
do{
cout << a[0] << " " << a1 << " " << a2 << endl;
}
while (prev_permutation(a,a+3));
return 0;
}

才能得到123的所有排列。

两个数组,一个编号一个数组的内容,按照其中一个排序

安利学习资料:
资料链接

posted on 2016-08-07 11:30  若流芳千古  阅读(3251)  评论(5编辑  收藏  举报

导航