在C++的学习中,STL容器和相关的底层数据结构、涉及的算法,是一个难点。这里我们省略了一些具体的操作和代码实现,只对其做一个大致的总结:
一、STL容器可以分为:
序列式容器:如,vector、list、deque、stack、queue、heap、priority_queue、slist。
关联式容器:如,set、map、multiset、multimap、hash_table、hash_set、hash_map、hash_multiset、hash_multimap。
二、不同容器的底层实现:
1、vector:数组 ,支持快速随机访问。
2、list:双向链表,支持快速增删。
3、deque:一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,也支持随机访问。
4、stack:list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时。
5、queue:底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和 queue其实是适配器,而不叫容器,因为是对容器的再封装)。
6、priority_queue:底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
7、set:为红黑树,有序,不重复
8、multiset:红黑树,有序,可重复
9、map:红黑树,有序,不重复
10、multimap:红黑树,有序,可重复
11、hash_set:hash表,无序,不重复
12、hash_multiset:hash表,无序,可重复 13、hash_map:hash表,无序,不重复 14、hash_multimap:hash表,无序,可重复
三、相关的知识点和操作:
1、 vector和数组的区别:
a、数组存放在栈区,vector存放在堆区,由STL库中程序负责内存分配、释放;
b、vector可以看成动态数组,可以通过相应函数改变大小;
c、数组不能直接把数组拷贝或b用一个数组给另一个数组赋值,vector可以;
d、vector扩容需要大量时间,所以数组效率更高。
2、vector的基本操作:
vector的基本操作有,加头文件#include<vector>、创建vector<int> vec、尾部插入vec.push_back()、指定位置插入vec.insert(vec.begin()+i,a)(在i+1个元素前插入a)、访问cout<<vec[0]<<endl等等。特别的,使用迭代器访问的方式为:vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
cout<<*it<<endl;
3、list:将元素按顺序储存在链表中。与vector相比, 它允许快速的插入和删除,但是随机访问却比较慢。list的基本操作有:
头文件:#include<list>、声明:list<int> a、插入push_back()和push_front()、指定位置插入:insert()、删除:pop_back()和pop_front()、逆置reverse()、交换两个链表:swap()、替换:assign()等等。
4、deque:也叫做双端队列。
deque特点:
1)支持随即存取,也就是[]操作符,
2)支持两端操作,push(pop)-back(front),在两端操作上与list效率差不多
deque的常用函数:
使用头文件#include <deque>、创建deque<int> d、尾部插入用push_back()、头部插入用push_front()、其它位置插入用insert(&pos, elem)、尾部删除用pop_back()、头部删除用pop_front()、任意迭代位置或迭代区间上的元素删除:erase(&pos)/erase(&first,&last)、删除所有元素用clear()、特别的,关于迭代器的使用:
deque<int>::iterator iter = d.begin();
for (;iter != d.end(); iter ++)
{
cout<<"d["<<iter-d.begin()<<"] = "<<(*iter)<<", ";
}
关于迭代器指针的使用:
deque<int>::iterator *pIter = new deque<int>::iterator;
if ( NULL == pIter )
{
return ;
}
for (*pIter = d.begin(); *pIter != d.end(); (*pIter)++)
{
cout<<"d["<<*pIter - d.begin() <<"]="<<**pIter<<", ";
}
if (NULL != pIter)
{
delete pIter;
pIter = NULL;
}
5、vector、list、deque的逻辑结构、物理结构:
vector维护的是一个连续的线性空间,和数组是一样的。所以不论其元素为何种型别,普通指针就可以作为vector的迭代器,但vector是个单向开口的连续线性空间, 所以从尾端插入元素效率较高,而如果从头部插入,则效率奇差;lis是一个环状双向链表; deque 是一种双向开口的连续线性空间,可以从两端插入,效率也很高。deque在实现上主要有两点:a、由一段一段的定量连续空间构成,第一个区块朝某个方向扩展,最后一个区块朝相反方向扩展;b、管理这些分段的定量连续空间,维护其整体连续的假象,并提供随机存取的接口。
6、stack、queue、priority_queue实际为适配器,适配就是改变接口而不改变底层实现方式,其中stack是一个线性表,插入和删除只在表的一端进行,为了严格遵循堆栈的数据后进先出原则,stack 不提供元素的任何迭代器操作。
stack的基本操作有:
声明一个stack: stack<int> s1或者 stack<string> s2。
stack中的操作stack<int> s;s.push(x) //无返回值,将元素x压栈
s.pop(); //退栈,无返回值
s.top(); //取栈顶元素,返回栈顶元素
s.empty(); //判断栈是否为空,如果是空,返回1,否则返回0
s.size(); //返回栈中元素的个数
在栈中没有提供清空操作的函数,但是可以间接地实现清空栈,
while(!s.empty())
{
s.pop();
}
7、queue和deque的区别:
deque是双端队列,支持push_front、pop_front、push_back、pop_back等几种操作的。queue是容器适配器。
8、priority_queue的基本操作:头文件:#include<queue>、定义:priority_queue<int> p;、优先输出大数据:priority_queue<int> p;优先输 出小数据:priority_queue<int, vector<int>, greater<int> > p; 等等。
9、set:能从一个数据集合中取出数据,在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序,但是set中数元素的值不能直接被改变。其底层结构为红黑树,其统计性能要好于一般平衡二叉树。mutiset和set最大的区别就是,它可以插入重复的元素,如果删除的话,相同的也一起删除了;如果查找的话,返回该元素的迭代器的位置,若有相同,返回第一个元素的地址;其他使用和set基本类似。
set的基本操作有:返回set容器的第一个元素:begin()、返回set容器的最后一个元素:end()、删除set容器中的所有的元素:clear()、判断set容器是否为空:empty() 、返回set容器可能包含的元素最大个数:max_size() 、返回当前set容器中的元素个数:size()、返回的值和end()(begin())相同:rend()(rbegin())。
10、map:提供一对一的数据处理能力,使用关键值Key来唯一标识每一个成员,键使用比较函数Compare比较来进行排序。搜索,删除和插入操作具有对数复杂性。multimap相对map来说能够允许重复值的存在。map的基本操作:创建:map<int, string> map2; 添加:map2.insert(pair<int, string>(11, "test11")); 删除:
while (!map2.empty())
{
map<int, string>::iterator it = map2.begin();
map2.erase(it); //删除
}
等等。
11、hash_map和map的应用场景:
使用hash_map还是map需要考虑三个要素:查找速度, 数据量, 内存使用。hash_map的查找级别是常数,map是对数。但是,使用 hash_map有hash函数的耗时,并且hash_map是以空间换时间,需要消耗大量内存。hash_map的函数和map的函数差不多。除了hash_map之外,还有hash_set, hash_multimap, has_multiset, 这些容器使用起来和set, multimap, multiset的区别与hash_map和map的区别一样。
四、相关算法 前面提到的容器中,其底层涉及的算法主要的有:heap相关的算法、红黑树、hash散列,其相关内容将在STL容器的算法篇给出。