C++ 实用主义STL速查:常用算法,string,vector, set & map等增删改查(ACM、OI等算法竞赛适用)

常用算法

排序sort和顺序检索lower_bound

#include <algorithm>
using namespace std;

bool cmp() { ... }

sort(It_first, It_last, cmp);
lower_bound();//大于等于
upper_bound();//大于

关于这两个bound的理解是,lower_bound是闭区间下界,upper_bound 是闭区间上界,所以如果都站在x轴正方向,从右往左找,那么会发现我们的区间一个是大于等于,一个是大于

其他的一些遍历算法

有时为了让代码更简洁,或许可以用用。当然,不是很高效;速度优先啦……

累加算法

#include <numeric>
#include <vector>
using namespace std;

vector<int> v{ 1, 2, 3, 4 };  
int sum = accumulate( v.begin(), v.end(), 0 );     // sum = 10

for_each

for_each中的f是const函数

template<class InIt, class Fun>
Fun for_each(It_first, It_last, Fun f);

更一般的查找: find

由于是通用算法,所以不能对容器本身性质有要求,复杂度为O(n)。相当于是for循环遍历。

#include <algorithm>
using namespace std;

bool cmp() { ... }

find(It_first, It_last);
find_if(It_first, It_last, cmp);

其中cmp可以是带有重载括号bool运算符的函数对象,使得判断谓词中门限值等可变;

template<class T>  struct Less_than 
{
	T val;   // 判定门限值
	Less_than( T& x ) : val( x ) { }
	bool operator()( const T& x ) const { return x < val; }
};

count和count_if

类似find,用于计数

最值min_element和max_element

返回最值元素处的前向迭代器。

min_element(FwdIt_first, FwdIt_last);

删除remove和去重unique

两者均不减小size
remove的本质是将等于某个值的元素去掉,然后将后续元素直接向前复制,元素数量并没有减少。
unique是将两个迭代器之间的元素去重。
unique_copy结合输出流迭代器可以实现去重输出。

#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
using namespace std;
int main()
{
    int a[5] = {1, 2, 3, 2, 5};
    int b[6] = {1, 2, 3, 2, 5, 6};
    ostream_iterator<int> oit(cout, ",");//输出流迭代器,将数据copy到它上,就等同于cout输出

    cout << "1) ";
    int *p = remove(a, a + 5, 2);
    copy(a, a + 5, oit);
    cout << endl;
    //输出  1) 1,3,5,2,5,
    cout << "2) " << p - a << endl; 
    //输出最后一个删除掉的元素所在位置的迭代器  2) 3
    
    vector<int> v(b, b + 6);//利用数组初始化vector
    cout << "3) ";
    remove(v.begin(), v.end(), 2);
    copy(v.begin(), v.end(), oit);
    cout << endl;
    //输出  3) 1,3,5,6,5,6,
	//remove的本质是将等于某个值的元素去掉,然后将后续元素直接向前复制,元素数量并没有减少。
    cout << "4) ";
    cout << v.size() << endl;
    //v中的元素没有减少,输出  4) 6
    
    cout << "5) ";
    unique(v.begin(), v.end());
    copy(v.begin(), v.end(), oit);
    cout << endl;
    //unique对无序区间并不尽如人意 输出  5) 1,3,5,6,5,6,

    cout << "6) ";
    sort(v.begin(), v.end());
    unique_copy(v.begin(), v.end(), oit);
    cout << endl;
    //输出  6) 1,3,5,6,
    cout << "7) ";
    cout << v.size() << endl;
    //unique_copy可以实现去重输出 仍输出 7) 6

    cout << "8) ";
    unique(v.begin(), v.end());
    copy(v.begin(), v.end(), oit);
    //输出 8) 1,3,5,6,6,6,
    cout << endl;
    cout << "9) ";
    cout << "after deleting : " << v.size() << endl;
    //unique和remove工作原理相同,输出 9) after deleting : 6

    return 0;
}

变序

permutation

next_permutation可以生成下一个全排列数,与之相对的还有prev_permutation
不仅代码简洁,且效率非常高:见一篇博文

#include <bits/stdc++.h>//万能头文件,囊括所有
using namespace std;
int main()
{
    char cache[10] = {'1','2','3','4','5','6','7','8','9'};
    cout << cache;
    while (next_permutation(cache, cache+9))
        cout << ", " << cache;
    return 0;
}

打乱random_shuffle()

random_shuffle()可以于打乱一个区间上的元素,用之前要初始化伪随机数种子:

#include <ctime>
srand(unsigned(time(NULL)));     
random_shuffle(It_first, It_last);

倒序

其实在某些时候,可以适用逆向迭代器输出来解决。

reverse(It_first, It_last);

“一等公民”:可变长数组vector

vector看上去像是“一等公民”,因为它们可以直接赋值,还可以作为函数的参数或者返回值,而无须像传递数组那样另外用一个变量指定元素个数。——刘汝佳

刘汝佳所说的三大特性,深刻反映了vector的优势:

  1. O(1)任意存取,双向迭代:沿袭了数组的任意访问的特性,支持任意类型的元素。同时进行范围检查,近似最优的高效访问。
  2. 作为数据类型,可以引用传参:通过传递引用的方式进行数据传递,效率也和数组相差无几
  3. 支持任意类型,长度动态增减:元素个数运行时动态可变,且可以在操作系统允许的情况下容纳的相同类型的数据元素。只要该对象具有复制构造函数即可。

由于任意类型兼容由于指针仅有指向处和内存长度决定。所以可以通过储存任意类型数据的指针来实现任意对象的vector

另外还可以补充一点:自由的成员,良好的封装:对底层内存访问机制进行了良好的封装,同时加入了一定的成员函数,给了vector更好的性能。

增加元素

向容器中添加元素的唯一方式是使用它的成员函数。通用算法无法完成这一项工作。

如果不调用成员函数,非成员函数既不能添加也不能删除元素。这意味着容器对象必须通过它所允许的函数去访问,迭代器显然不行。

虽然有对应的front()但是不常用,因为vector操作头部会导致大量的移动。

最常用:push_back()

#include <vector>
using namespace std;
vector<string> vec;
vec.push_back(string("test"));
vec.push_back("test");

但是显然的问题是,无论哪种形式,尽管移动构造已经大大减少了拷贝开销。
但临时变量的销毁仍然是不可避免地浪费时间。

更高效:emplace()

为了避免以上所说的临时对象的销毁问题,后来人们引入了emplace_back直接在末尾建立新的对象

vec.emplace_back("test");//可以在尾部插入

为了眼见为实,可以看看这篇博文

emplace()支持任意处插入(当然要考虑效率啦),只需要提供相应构造所需的参数即可。

struct Foo {
    Foo(int n, double x);
};
std::vector<Foo> v;
v.emplace(someIterator, 42, 3.1416);        // 没有临时变量产生
v.insert(someIterator, Foo(42, 3.1416));    // 需要产生一个临时变量
v.insert(someIterator, {42, 3.1416});       // 需要产生一个临时变量

更灵活:insert()

这个支持在任意处添加

vec.insert(iter, (num, )val);//在iter处插入num个val num可以不填

多种类似实例见这篇博文

插入也可以借助初始化列表,或一个区间的始末迭代器。

赖皮法:resize()

不添加元素,直接任意存取,会RE,所以为了防止一个个添加的尴尬,我们可以

vec.resize(n);
for (int i = 0; i < n; i++)
	cin >> vec[i];

删除元素

成员删除函数都是可以减少元素数量的。通用算法往往不具有这个权限

pop_back()

删除一个尾部元素

std::vector<int> data(100, 99); // Contains 100 elements initialized to 99
data.pop_back(); // Remove the last element

pop_back()不仅可以删除尾部。如果不在意顺序的话,在删除头部元素的时候,可以使用swap()+pop_back(),可以减少删除带来的挪动开销。

std::swap(std::begin(data),std::end(data)-1);
// Interchange 1st element with the last data.pop_back(); 
data.pop_back(); // Remove the former first element

erase()

为了删除一个序列,可以使用erase()
仅有两种形式:

//To delete just one elem.
auto iter = data.erase(std::begin(data)+1); //Delete the second element

// To delete a sequence:
auto iter = data.erase(std::begin(data)+1,std::begin(data)+3);// Delete the 2nd and 3rd elements

清空所有clear()

vec.clear();

遍历和更改元素

遍历可以利用遍历算法。
也可以使用迭代器、下标、at()for(auto it: vec)
修改不能使用at()。
引用front()back()可以更改元素。

查找元素

只能使用遍历的通用算法。效率是O(n)的

舍得之道:集合set与映射map

这是两种关联容器,看似消耗了一部分时间来构建树,同时因为不是线性,是很浪费时间的,但因为这种有序性,set和map的查找特性非常好。这正是一种舍得的哲学。

取元素

因为二者都不是线性表:所以不能任意取,不能。
map可以通过pair的内设结构通过键取出值。set不能使用[]运算符。map也只能使用[(key)]的形式取值。

遍历有通用算法、迭代器,for auto,均不能通过下标来遍历

增加元素

由于不是线性表,所以没有back和front的概念,增删就变得更单一。


因为是树所以增加的过程也进行了排序:
map的元素为:pair<T1, T2>,一种微型数据结构。存储两个数据。类型可以不同。初始化时建议用make_pair()
set的元素可以为函数对象,重载bool()定义排序方式。


set 类模板提供的所有成员方法中,能实现向指定 set 容器中添加新元素的,只有 3 个成员方法,分别为

  • insert()
  • emplace()(C++11)
  • emplace_hint()(C++11)

map多了一个[]


对于set来说,不涉及键值顺序的问题,更适合emplace()但对于map,为了保证异构长参数无歧义、以及键值顺序,可能会比较繁琐,仍然推荐insert,见这篇博文

返回值类型都是pair,first是插入位置的迭代器,第二个bool是是否成功

map的insert方法

1. 不指定插入位置

建议使用

	m.insert(make_pair("foo2", "bar2"));

其他

#include <iostream>
#include <map>
using namespace std;
int main()
{
    map<string, string> m;
    pair<string, string> tmp("foo", "bar"); //总归要创建一个对象
    m.insert(tmp);                          //传引用
    m.insert(pair<string, string>("foo1", "bar1"));
	m.insert(make_pair("foo2", "bar2"));//建议使用 
    m.insert({"foo3", "bar3"});//传右值引用
    m["foo4"] = "bar4";
    map<string, string> m1.insert(m.begin(), m.end());//插入一个区间
    for (auto it: m)
        cout << it.first << ' ' << it.second << endl;
    return 0;
}

2.指定插入位置(其实没啥意义)
如果成功

    m.insert(m.begin(), make_pair("foo4", "bar4"));

set的emplace方法

返回插入值的迭代器(迭代器感觉没啥用,能插入就行)

    pair<set<string, string>::iterator, bool> ret = myset.emplace("http://c.biancheng.net/stl/");

emplace_hint()相当于指定位置的insert(但没啥用,就不谈了)

set使用emplace因为不需要考虑异构长参数的问题,所以会更高效。

删除元素

与vector类似:

  • erase():删除某个值,或某个区间,或某个迭代器之后的所有元素
  • clear():清除所有元素

查询

不用lower_bound()upper_bound()枉为关联容器。
(回见文章顶部)

逃离底层:C++字符串string

string是对char的良好封装。但不简单是char型集群,好用的点就在于多种重载导致的简洁性。

构造

(其中标识符之前的类型名不是类型转换运算符,只是为了说明

string str = "foobar"; 
char cstr[10] = "foobar";//如果char *cstr = "foobar"会警告。
string s(str); // 拷贝构造生成str的复制品
string s(cstr) ;  // 将C字符串(以NULL结束)作为s的初值,没问题!

string s = "foobar"; 
string s("foobar");//临时对象(移动)构造:如果是已有string对象就是拷贝赋值

string s(str, 5, 6) ; // 将字符串str内"始于下标5且长度顶多为6"的部分作为字符串的初值 
string s = str.substr(5, 6);

和C字符串的对比

操作对比

操作 string C字符串
声明 string s; char s[100];
取第i个元素 str[i]; at(i); s[i]
获得长度 size();1 strlen(s); 不计0
读取一行 getline(cin, s); gets(s);
赋值 s = “Honour”; assign(); strcpy(s, “Honour”);
串联 s += “Van”; append(); strcat(s, “Van”);
比较 s == “Honour Van”;2 compare(str, a, b); strcmp(s, “Honour Van”);

和C字符串的转化

  • string可以转化成const char*
const char *str = s.c_str();
  • 在ISO C++中对string转化成non-const char* 会给出警告:如果一定要转换成char*,可以用string的一个成员函数strcpy实现。
strcpy(data, str.c_str());  //不能直接复制str
  • char*,char[]可以转化为string:这相当于高精兼容低精,封装兼容开放。

输出

因为C字符串是密排的所以可以通过printf()输出,如果一定要用printf()输出str

printf("%s",str.c_str());

增加元素的特别说明

在使用重载+号时,整个加式中,至少有一个是string型变量。

string str1;
string ans = str1 + "xiaomin";
ans = str1 + char('g');

注释


  1. 为了保证STL的行为一致性,我们优先使用size ↩︎

  2. compare成员可以比较某一个段落的字串是否相等。 ↩︎

猜你喜欢

转载自blog.csdn.net/weixin_45502929/article/details/106331760