算法课上完了,整理一下C++的一些使用上的经验技巧,该总结主要参考了以下文章/网站,首先对他们的工作成果表示感谢!
1、 @mannhello的文章:c++容器简介与比较
2、 原作者未知的文章:C++中几个容器的比较
3、 神一般的C++库函数资料网站:www.cplusplus.com,总结超级全面的,很值得一看
再次对他们的工作成果表示感谢!
如果发现任何错漏之处还请告知,感激不尽!转载请注明出处,谢谢!
1 概述
首先是一些有关C++的使用技巧:
(1) 做算法题的时候切记换行与空格,避免低级错误;
(2) 一旦某道题出现超时,如果你是用的是cin/cout,首先在main()函数第一行添加std::ios::sync_with_stdio(false);取消同步,如果还不能解决则把所有的cin/cout转换成C风格的scanf/printf;
(3) 做算法题的时候但凡排序都可以使用库函数:#include<algorithm> sort();,其头文件是 #include <algorithm>,对于结构体的大小关系你可以为其编写专门的cmp方法传入即可;具体来说,可以重载操作符,也可以编写cmp方法:
a)重载操作符(调用 void sort( iterator start, iterator end );):
有两张方法,一种是写在结构体之中:
typedef struct Node{
……
bool operator<(const Node& in) const{
return data < in.data; //大根堆(如果应用于sort则是从小到大排序)
return data > in.data; //小根堆(如果应用于sort则是从大到小排序)
}
};
另一种是写在结构体之外:
bool operator<(const Node& a, const Node& b){
return a.data < b.data; //大根堆(如果应用于sort则是从小到大排序)
return a.data >b.data; //小根堆(如果应用于sort则是从大到小排序)
}
具体调用的时候,如有vector<Node> v;,则:sort(v.begin(), v.end());;如有数组Node[] a;,则:sort(a, a+length);
b)编写cmp方法(调用 void sort( iterator start, iterator end, StrictWeakOrdering cmp );):
int cmp(AA x,AA y){
retrun x.a > y.a; //按a降序
return x.a < y.a; //按a升序
}
具体调用的时候,需要传入cmp函数:sort(v.begin(), v.end(), cmp); 以及 sort(a, a+length, cmp);
(4) 数组初始化可以这样:
#include <memory.h>
int a[100];
memset(a, 0, sizeof(int)*100); //把数组a的每个字节都赋值为“0”
memset(a, 1, sizeof(int)*100); //这样是不行的,会把一个int的每个字节都赋值为“1”,最后数组里存储的是16843009(1 00000001 00000001 00000001);
(5) 对于浮点数比较,需要用一点技巧:
a > b:a > b + (1e-6)
a >=b:a > b – (1e-6)
a < b:a < b – (1e-6)
a <= b:a < b + (1e-6)
a == b:a > b – (1e-6) && a < b + (1e-6)
(6) 对精度要求比较高的时候,你可以这样获得 :#include <math.h> pi=acos(-1, 0);
(7) 如果需要自定义输入输出测试数据,可以重定向数据流到文件:
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
之后使用的cin/cout都会从文件读取了;
(8) 对于大数的模,为了避免溢出可以用这个公式:(X*X % M) = (X%M * X%M) % M;
(9) 字符串转换数字:
#include <stdlib.h>
char c[5];
string s;
int n = atoi(c); int n =atoi(s.c_str()); //atoi()会扫描参数nptr字符串,如果第一个字符不是数字也不是正负号返回零,否则开始做类型转换,之后检测到非数字或结束符 /0 时停止转换,返回整型数。
long n = atol(c); long n =atol(s.c_str); //atol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('/0')才结束转换,并将结果返回。
double n = atof(c); double n = atof(s.c_str); //atof()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时 ('/0')才结束转换,并将结果返回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分,如123.456或123e-2。
(10) 对于一个字符集合,你可以这样获得他的全排列:
#include<algorithm>
string str;
do{
…..//取得当前str在全排列中的下一个字符串,直接使用str即可
}while(next_permutation(str.begin(), str.begin() + str.length()));
(11) 对于两个案例之间换行或空格的要求,可以这样子:
bool ok = false;
while(case){
if(ok) cout << endl;
ok = true;
……
}
(12) 当题目出现一些类似“N个点,N-1条边,无环”“N个点,N-1条边,任意两点联通”“N个点,任意两点简单连通”,说明题目中的N就是一棵树;
(13) 如果一道题的复杂度在10^8以内,用暴力枚举也没关系;
(14) 对于搜索,如果已知最多步数,则可以使用DFS获得所有路径(递归);如果要求最少步数,则可以使用BFS(队列);
2 容器
2.1 所有容器共有函数及属性
2.1.1 容器中常用typedef
以下这些typedef常用于声明变量、参数和函数返回值:
(1) value_type 容器中存放元素的类型
(2) reference 容器中存放元素类型的引用
(3) const_reference 容器中存放元素类型的常量引用,这种引用只能读取容器中的元素和进行const操作
(4) pointer 容器中存放元素类型的指针
(5) iterator 指向容器中存放元素类型的迭代器
(6) const_iterator 指向容器中存放元素类型的常量迭代器,只能读取容器中的元素
(7) reverse_iterator 指向容器中存放元素类型的逆向迭代器,这种迭代器在容器中逆向迭代
(8) const_reverse_iterator 指向容器中存放元素类型的逆向迭代器,只能读取容器中的元素
(9) difference_type 引用相同容器的两个迭代器相减结果的类型(list和关联容器没有定义operator-)
(10) size_type 用于计算容器中项目数和检索顺序容器的类型(不能对list检索)
2.1.2 所有标准库共有函数(除适配器外):
(1) 构造器及析构器
a) C<T> a;
//默认构造函数,初始化为空
b) C<T> a(a0);
//复制构造函数,初始化微现有同类容器副本
c) C<T> a(iter1,iter2);
//复制构造函数,初始化为现有同类容器的一部分
d) ~C<T>();
//析构函数
(2) 迭代器
a) iterator begin() noexcept;
const_iterator begin() const noexcept;
//返回C<T>::iterator或者C<T>::const_iterator,引用容器第一个元素
b) iterator end() noexcept;
const_iterator end() const noexcept;
//返回C<T>::iterator或者C<T>::const_iterator,引用容器最后一个元素后面一位
c) reverse_iterator rbegin()nothrow;
const_reverse_iterator rbegin() const nothrow;
//返回C<T>::reverse_iterator或者C<T>::const_reverse_iterator,引用容器最后一个元素
d) reverse_iterator rend() nothrow;
const_reverse_iterator rend() const nothrow;
//返回C<T>:: reverse_iterator或者C<T>::const_reverse_iterator,引用容器第一个元素前面一位
e) const_iterator cbegin() constnoexcept;
//仅用于C++11,返回C<T>::const_iterator,引用容器第一个元素
f) const_iterator cend() constnoexcept;
//仅用于C++11,返回C<T>::const_iterator,引用容器最后一个元素后面一位
g) const_reverse_iterator crbegin() const noexcept;
//仅用于C++11,返回C<T>:: const_reverse_iterator,引用容器最后一个元素
h) const_reverse_iterator crend()const noexcept;
//仅用于C++11,返回C<T>:: const_reverse_iterator,引用容器第一个元素前面一位
(3) 功能函数
a) bool empty() const noexcept;
//如果容器为空,则返回true,否则返回false
b) size_type max_size() const;
//返回容器中最大元素个数
c) size_type size() const;
//返回容器当前元素个数
(4) 容器操作函数
a) iterator insert (const_iteratorposition, const value_type& val);
//插入数据到指定位置,返回新插入的数据中的第一个的迭代器,除这个插入函数外还有其他很多重载函数
b) iterator erase (const_iterator position);
iterator erase (const_iterator first, const_iterator last);
//删除容器中指定的某个值或者一个范围,注意:范围的概念是[first,last)
c) void swap (C<T>& x);
//将当前容器与传入的容器x交换数据
d) void clear() noexcept;
//删除当前容器中的所有元素,也即将size清为0
2.2 顺序容器
2.2.1 所有顺序容器共有函数
以下是所有顺序容器所共有的函数:
(1) 元素访问函数
a) reference front();
const_reference front() const;
//返回容器中第一个元素的引用,注意:和begin()方法返回迭代器不同,他返回的是元素的直接引用
b) reference back();
const_reference back() const;
//返回容器中最后一个元素的引用,注意:和end()方法返回迭代器不同,它返回的是元素的直接引用
(2) 容器操作函数
a) void push_back (constvalue_type& val);
void push_back (value_type&& val);
//插入元素到容器末尾
b) void pop_back();
//将容器最后一个元素删除
2.2.2 vector
(1) 使用比较
就是动态数组,它也是在堆中分配内存,元素连续存放,有保留内存,如果减少大小后内存也不会释放。如果需要的容量大于当前大小时才会再分配内存,对最后元素操作最快(在后面添加删除最快 )。访问方面,对任何元素的访问都是O(1),也就是是常数的。所以vector常用来保存需要经常进行随机访问的内容,并且不需要经常对中间元素进行添加删除操作。
vector的迭代器在内存重新分配时将失效(它所指向的元素在该操作的前后不再相同)。当把超过capacity()-size()个元素插入 vector中时,内存会重新分配,所有的迭代器都将失效;否则,指向当前元素以后的任何元素的迭代器都将失效。当删除元素时,指向被删除元素以后的任何 元素的迭代器都将失效。
(2) 功能函数
a) void resize (size_type n);
void resize (size_type n, value_type val);
//将容器的size变化成新的大小,如果n比当前size要小,则从容器后部开始删除元素;否则扩充size,可以指定填充的元素的初始值
b) size_type capacity() constnoexcept;
//返回分配给当前容器的存储空间大小,并不一定和size相等
c) void reserve (size_type n);
//为当前容器重新指定capacity的大小,如果n比当前capacity要大,则增加容量;否则什么事情都不做
d) void shrink_to_fit();
//仅用于C++11,硬性将容器的capacity调整为size的大小
(3) 元素访问函数
a) reference operator[] (size_typen);
const_reference operator[] (size_type n) const;
//相当于数组下标
b) reference at (size_type n);
const_reference at (size_type n) const;
//作用和数组下标一样,但是超出容量时会抛出out_of_range异常
c) value_type* data() noexcept;
const value_type* data() const noexcept;
//仅用于C++11,返回容器第一个元素的指针,类似于数组的头指针,用法也和数组头指针一样
2.2.3 deque
(1) 使用比较
双端队列,也是在堆中保存内容的。它的保存形式如下: [堆1] [堆2] [堆3]…… 每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品。deque可以让在前面或是在后面快速地添加删除元素,然后还可以有比较高的随机访问速度,但是随机访问速度比不上vector快,因为它要内部处理堆跳转。deque也有保留空间,由于deque不要求连续空间,所以可以保存的元素比vector更大,还有就是在前面和后面添加元素时都不需要移动其它块的元素,所以性能也很高。
增加任何元素都将使deque的迭代器失效。在deque的中间删除元素将使迭代器失效。在deque的头或尾删除元素时,只有指向该元素的迭代器失效。
(2) 功能函数
a) void resize (size_type n);
void resize (size_type n, value_type val);
//将容器的size变化成新的大小,如果n比当前size要小,则从容器后部开始删除元素;否则扩充size,可以指定填充的元素的初始值
b) void shrink_to_fit();
//仅用于C++11,deque的size一般来说可以比实际存放的元素数量要多,该方法强制调整内存使用量为已经存放元素的大小
(3) 元素访问函数
a) reference operator[] (size_typen);
const_reference operator[] (size_type n) const;
//相当于数组下标
b) reference at (size_type n);
const_reference at (size_type n) const;
//作用和数组下标一样,但是超出容量时会抛出out_of_range异常
(4) 容器操作函数
a) void push_front (constvalue_type& val);
void push_front (value_type&& val);
//插入元素到容器第一位
b) void pop_front();
//删除容器第一位的元素
2.2.4 list
(1) 使用比较
双向环状链表,元素也是在堆中存放,每个元素都是放在一块内存中,但list没有空间预留,所以每分配一个元素都会从内存中分配,每删除一个元素都会释放它占用的内存。由于数据结构是链表,所以不能随机访问一个元素,但是在开头、末尾和中间任何地方增加或删除元素所需时间都为常量。
增加任何元素都不会使迭代器失效。删除元素时,除了指向当前被删除元素的迭代器外,其它迭代器都不会失效。
(2) 容器操作函数
a) void push_front (constvalue_type& val);
void push_front (value_type&& val);
//插入元素到容器第一位
b) void pop_front();
//删除容器第一位的元素
c) void resize (size_type n);
void resize (size_type n, value_type val);
//将容器的size变化成新的大小,如果n比当前size要小,则从容器后部开始删除元素;否则扩充size,可以指定填充的元素的初始值
2.3 关联容器
2.3.1 所有关联容器共有函数
(1) 访问函数
a) const_iterator find (constvalue_type& val) const;
iterator find (const value_type& val);
//查找对应的值/key值,如果找到则返回其迭代器,如果找不到则返回end()
b) size_type count (constvalue_type& val) const;
//查找指定值/key值的数量,返回0或者1
c) iterator lower_bound(constvalue_type& val);
const_iterator lower_bound (const value_type& val) const;
或者
iterator lower_bound(const key_type& k);
const_iterator lower_bound (const key_type& k) const;
//返回容器中第一个值/key值大于或等于val的迭代器
d) iterator upper_bound(constvalue_type& val);
const_iterator upper_bound (const value_type& val) const;
或者
iterator upper_bound (const key_type& k);
const_iterator upper_bound (const key_type& k) const;
//返回容器中第一个值/key值大于val的迭代器
e) pair<const_iterator,const_iterator> equal_range (const value_type& val) const;
pair<iterator,iterator>equal_range(const value_type&val);
或者
pair<const_iterator,const_iterator> equal_range (constkey_type& k) const;
pair<iterator,iterator>equal_range(const key_type& k);
//返回容器中与传入值/key值相等的元素的范围,返回的是一个pair值,它的first值就是lower_bound,second值就是upper_bound
2.3.2 set
(1) 使用比较
内部数据结构为红黑树的平衡二叉检索树,按照键进行排序存储, 值必须可以进行比较, 可以理解为set就是键和值相等的map,不允许键值重复,元素默认按升序排列。此外,它所有的操作都可以在O(log n)时间复杂度内完成,效率非常高。
如果迭代器所指向的元素被删除,则该迭代器失效。其它任何增加、删除元素的操作都不会使迭代器失效。
2.3.3 multiset
(1) 使用比较
除了可以存储重复键值以外,其他情况和set相同。
2.3.4 map
(1) 使用比较
内部元素以<键,值>对的形式存储,键值不能重复,元素默认按键的升序排列。可以这样赋值一个对:m.insert(map<string, int>::value_type(“count”, 100));,或者使用更为简单的数组下表符号“[]”。读取的时候,对于一个迭代器iter,可以这样指定:iter->first; iter->second,或者这样:*iter.first;*iter.second;。
如果迭代器所指向的元素被删除,则该迭代器失效。其它任何增加、删除元素的操作都不会使迭代器失效。
(2) 访问函数
a) mapped_type& operator[](const key_type& k);
mapped_type& operator[] (key_type&& k);
//就当他是数组这样用,但要注意,一旦使用了“[]”,如果此时没有对应的key值,将会产生一次插入操作
2.3.5 multimap
(1) 使用比较
除了可以使用重复键值,其他情况和map一样。
2.4 容器适配器
2.4.1 所有容器适配器共有函数
(1) 功能函数
a) bool empty ( ) const;
//如果适配器为空,则返回true,否则返回false
b) size_type size ( ) const;
//返回适配器中已有的元素数量
2.4.2 stack
(1) 使用比较
栈适配器,它可以将任意类型的序列容器转换为一个堆栈,一般使用deque作为支持的序列容器。元素只能后进先出(LIFO),不能遍历整个stack。
(2) 功能函数
a) value_type& top ( );
const value_type& top ( ) const;
//获取栈顶元素,并不弹出
b) void push ( const T& x );
//将元素压入栈
c) void pop ( );
//将栈顶元素弹出,并不返回
2.4.3 queue
(1) 使用比较
队列适配器,它可以将任意类型的序列容器转换为一个队列,一般使用deque作为支持的序列容器。元素只能先进先出(FIFO),不能遍历整个queue。
(2) 功能函数
a) value_type& front ( );
const value_type& front ( ) const;
//获取队列首部元素,并不弹出
b) value_type& back ( );
const value_type& back ( ) const;
//获取队列尾部元素,并不弹出
c) void push ( const T& x );
//添加新元素到队列中
d) void pop ( );
//将队首元素弹出,并不返回
2.4.4 priority_queue
(1) 使用比较
优先队列适配器,它可以将任意类型的序列容器转换为一个优先级队列,一般使用vector作为底层存储方式。只能访问第一个元素,不能遍历整个priority_queue,第一个元素始终是优先级最高的一个元素。
其内部数据结构是堆,具体使用上来说,对于一个结构体,如果想要做一个堆,可以在结构体内部或者外部重载符号“<”即可:
typedef struct Node{
……
bool operator<(const Node& in) const{
return data < in.data; //大根堆(如果应用于sort则是从小到大排序)
return data > in.data; //小根堆(如果应用于sort则是从大到小排序)
}
};
bool operator<(const Node& a, const Node& b){
return a.data < b.data; //大根堆(如果应用于sort则是从小到大排序)
return a.data >b.data; //小根堆(如果应用于sort则是从大到小排序)
}
但是如果想要同时构建最大堆以及最小堆,就需要传入定制的cmp结构:
typedef struct Job{ int prio;int age};
struct cmpBigHeap{ //建立大根堆(优先值高的队首)
bool operator()(const Job& a, const Job& b){ return a.prio< b.prio;}
};
struct cmpSmallHeap{ //建立小根堆(优先值低的在队首)
bool operator()(const Job& a, const Job& b){ return a.prio >b.prio;}
};
priority_queue<Job, vector<Job>, cmpBigHeap> bigHeap;
priority_queue<Job, vector<Job>, cmpSmallHeap > smallHeap;
(2) 功能函数
a) const value_type& top ( )const;
//获取优先级队列中优先级最高的元素
b) void push ( const T& x );
//将新元素放入优先队列中
c) void pop ( );
//将优先级最高的元素弹出队列