STL浅析
#include <iostream> #include <vector> #include <functional> #include <numeric> #include <algorithm> using namespace std; int main() { int ia[]={1,2,3,4,5}; vector<int> iv(ia,ia+5); //cout<<accumulate(iv.begin(),iv.end(),1,multiplies<int>())<<endl;//120 multiplies<int> mulObj; //cout<<mulObj(3,5)<<endl;//15 sort(iv.begin(),iv.end(),greater<int>());//大数放在前 modulus<int> modObj;//取余数(仿函数) cout<<modObj(3,5)<<endl;//3 system("pause"); return 0; }
一.STL六大组件:
1.容器:vector list deque map set multiset
2.算法(algorithm) sort search erase
3.迭代器 (iterator) 泛型指针
4.配置器 (allocator) 负责空间配置和管理
5.仿函数(functional) 重载
6.配接器 修饰容器或仿函数或迭代器接口的东西
二. 常用迭代器
序列式容器:
1.vector
vector与数组类似,维护一个连续线性空间,支持随机存取,不同点是其是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。其迭代器是普通指针,它以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端
为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量更大一些——capacity,这里缺省采用前述的alloc空间配置器,同时据此另外定义了一个data_allocator,以方便以元素大小为配置单位:typedef simple_alloc<value_type,Alloc> data_allocator
vector内存分配实现:
当push_back(x)将新元素插入vector尾端时,该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish;如果没有,就扩充空间(重新配置、移动数据、释放原空间):
调用data_allocator::allocator重新配置一块大小为原空间两倍大小(若原空间为0,则新空间配置为1)的新内存空间
——>调用uninitialized_copy将原vector的内容拷贝到新vector中,并对新元素设定初值x
——>析构并释放原vector(destroy,deallocate)
——>调整迭代器指向新的vector
由上述过程可知,针对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了
2、map:
以红黑树为底层机制。
map所有元素都会根据元素的键值自动被排序。map的所有元素都是pair,同时拥有实值value和键值key。可表示为pair<key,value>。map同样不允许两个元素有相同的键值(它的插入操作采用的是底层机制RB-tree的insert_unique()),相同键值的元素插入不会被覆盖,若想键值不唯一,可使用multimap。
不能通过map的迭代器改变map元素的键值,因为map元素的键值,关系到map元素的排列规则。如果任意改变map元素键值,会严重破坏map组织。但可以修正元素的实值。因此,map源码中迭代器既不是一种constant iterator,也不是一种mutable iterators。
与list相同。当客户端对它进行元素新增操作或删除操作时,操作之前的所有迭代器,在操作完成之后都依然有效(除了被删除元素的迭代器)。
定义形式:
map<string,int>mymap; mymap[string("jihou")]=1; mymap.insert(pair<string,int>("jimmy",3)); mymap["marry"]=6; mymap["marry"]=5;//将前值覆盖 mymap["harry"]=5;// map<string ,int>::iterator it; it=mymap.begin(); for (;it!=mymap.end();it++) { cout<<(*it).first<<"=>"<<(*it).second<<endl; }
[]操作符如simap[string(“jihou”)]既可以做左值,又可以做右值。如
int number = simap[string(“jihou”)];
因此operator[]的返回值为引用,具体代码如下:
T& operator[](const key_type& k) {
return (*((insert(value_type(k,T()))).first)).second;
}
附:
set/map底层为红黑树:
红黑树: 树根始终为黑色 外部结点均为黑色 其余节点若为红色,则其孩子节点必为黑色 从任意外部结点待根节点的沿途,黑色节点数目相同
为啥不用AVL树:
-
单从查找、删除、插入等的时间复杂度来看,两者旗鼓相当,但因为AVL树对平衡条件要求更为严格,因此当进行操作时,AVL树进行调整的频率与次数要比红黑树高,也因此对于数据量较大插入或删除时或者数据复杂的情况,红黑树需要通过旋转变色操作来重新达到平衡的频度要小于AVL,因此红黑树比AVL(平衡二叉搜索树)具有更高的插入效率,虽然查找效率会平衡二叉树稍微低一点点,但是这种查找效率的损失是非常值得的。它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除。
-
此外,需要平衡处理时。红黑树比AVL树多一种变色操作,而且变色的时间复杂度在对数量级上,但因为操作相对简单,所以在实际应用中,这种变色仍然十分快速,对效率影响较小
-
当插入一个节点引起树的不平衡时,AVL和红黑树都最多需要2次旋转操作。但删除一个节点引起不平衡之后,AVL最多需要logN次旋转操作,而红黑树最多只需要3次。任何不平衡都会在3次旋转之内解决。因此两者插入一个结点的代价差不多,但删除一个结点的代价红黑树要低一些。
-
AVL和红黑树的插入删除代价主要还是消耗在查找待操作的结点上。因此时间复杂度基本上都是与O(logN) 成正比的。