文章目录
常用算法
排序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的优势:
- O(1)任意存取,双向迭代:沿袭了数组的任意访问的特性,支持任意类型的元素。同时进行范围检查,近似最优的高效访问。
- 作为数据类型,可以引用传参:通过传递引用的方式进行数据传递,效率也和数组相差无几
- 支持任意类型,长度动态增减:元素个数运行时动态可变,且可以在操作系统允许的情况下容纳的相同类型的数据元素。只要该对象具有复制构造函数即可。
由于任意类型兼容由于指针仅有指向处和内存长度决定。所以可以通过储存任意类型数据的指针来实现任意对象的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');