转自https://blog.csdn.net/qq_37941471/article/details/81984417
序列式容器
一. vector :
vector采用一段连续的内存来存储其元素,向vector添加元素的时候,如果容量不足,vector便会重新malloc一段更大的
内存,然后把原内存中的数据memcpy到新的内存中,并free原内存块,然后将新元素加入。
vector的元素插入性能跟以下几个要素关系重大:1. 插入的位置
头部插入:将所有元素后移,然后将新元素插入
中间插入:将插入点后面的元素后移,然后插入新元素
尾部插入:将新元素直接插入尾部总结:
尾部插入无疑是最快的,头部插入最慢,中间插入次之,(尾部插入>中间插入>头部插入)
慢的原因:在于插入前要移动后面的所有元素。删除元素也是同样的道理。2. 保留空间大小
如果插入元素是,空间不足将导致重新malloc以及一系列的内存拷贝。如果使用者能对容量有预期,
那么采用reserve()来预先分配内存,将大大的提高性能。综述,vector适用于尾部插入,但是此时无法兼顾查找的性能,因为二分查找的vector要求重新排序,
或者要求vector在插入时就保持有序,这样就无法做到尾部插入。但是vector作为动态数组的替代,已经足够优秀。
常用接口:
头文件: #include <vector>
//1.定义和初始化
vector<int> vec1; // 默认初始化,vec1为空
vector<int> vec2(vec1); // 使用vec1初始化vec2
vector<int> vec3(vec1.begin(),vec1.end()); // 使用vec1初始化vec2
vector<int> vec4(10); // 10个值为0的元素
vector<int> vec5(10,4); // 10个值为4的元素
//2.常用操作方法
vec1.push_back(100); // 尾部添加元素
int size = vec1.size(); // 元素个数
bool isEmpty = vec1.empty(); // 判断是否为空
cout<<vec1[0]<<endl; // 取得第一个元素
vec1.insert(vec1.end(),5,3); // 从vec1.back位置插入5个值为3的元素
vec1.pop_back(); // 删除末尾元素
vec1.erase(vec1.begin(),vec1.begin()+2); // 删除vec1[0]-vec1[2]之间的元素,不包括vec1[2](左闭右开)
cout<<(vec1==vec2)?true:false; // 判断是否相等==、!=、>=、<=...
vector<int>::iterator iter = vec1.begin(); // 获取迭代器首地址
vector<int>::const_iterator c_iter = vec1.begin(); // 获取const类型迭代器
vec1.clear(); // 清空元素
//3.遍历
// 1. 下标法
int length = vec1.size();
for(int i=0;i<length;i++)
{
cout<<vec1[i]<<" ";
}
cout<<endl;
// 2. 迭代器法
vector<int>::iterator iter = vec1.begin();
for(;iter != vec1.end();iter++)
{
cout<<*iter<<" ";
}
cout<<endl;
二. list:
list很简单,它就是个双向链表。每一个节点的内存都是独立的。理论上,其优点是:任何位置的插入删除元素操作都是非常快的。
缺点是:不适合用于元素的查找,因为他只能是扫描的方式。单链表和双向链表的优缺点:
1. 结构上而言:双向链表复杂,占用的空间大一点
2. 插入和删除:双向链表简单,更实用(因为在删除和插入时,需要找到前一个prev,而双向链表不用找)
3. 时间复杂度:双向链表O(1);单链表O(n)既然双向链表的优点很多,为什么单链表也很重要?
因为:单链表会在哈希表的拉链法中会用到,还有图中会用到,应用还是很广的。
如果单用链表时,双向链表会比较好。
常用的接口:
常用的接口:
头文件: #include <list>
// 1.定义和初始化
list<int> lst1; // 创建空list
list<int> lst2(3); // 创建含有三个元素的list
list<int> lst3(3,2); // 创建含有三个元素为2的list
list<int> lst4(lst2); // 使用lst2初始化lst4
list<int> lst5(lst2.begin(),lst2.end()); // 同lst4
// 2.常用操作方法
lst1.push_back(10); // 末尾添加值
lst1.pop_back(); // 删除末尾值
lst1.begin(); // 返回首值的迭代器
lst1.end(); // 返回尾值的迭代器
lst1.clear(); // 清空值
bool isEmpty1 = lst1.empty(); // 判断为空
lst1.erase(lst1.begin(),lst1.end()); // 删除元素
lst1.front(); // 返回第一个元素的引用
lst1.back(); // 返回最后一个元素的引用
lst1.insert(lst1.begin(),3,2); // 从指定位置插入个3个值为2的元素
lst1.rbegin(); // 返回第一个元素的前向指针
lst1.remove(2); // 删除链表中所有为2的元素
lst1.reverse(); // 反转
lst1.size(); // 含有元素个数
lst1.sort(); // 排序
lst1.unique(); // 删除相邻重复元素
//3.遍历
//迭代器法
list<int>::iterator iter = lst1.begin();
for(;iter != lst1.end();iter++)
{
cout<<*iter<<" ";
}
cout<<endl;
去重:先排序sort( ),再unique( )
三 . vector和list的区别:
相同点:
1. 都可以存储一组类型相同的元素
2. 尾部插入和删除,性能差不多
不同点:1. 存储顺序:
vector :是顺序表,表示的是一块连续的内存,元素被顺序存储;
list : 是双向链表,在内存中不一定连续。2. 扩容的开销:
vector : 当数值内存不够时,vector会重新申请一块足够大的连续内存,把原来的数据拷贝到新的内存里面
list : 因为不用考虑内存的连续,因此新增开销比vector小。3. 头部或者中间的插入和删除:
vector :vector需要进行元素的移动
list : 性能比vector好
4. 随机访问:vector : 直接通过下标就可以访问,时间复杂度为O(1)
list : 需要遍历,时间复杂度为O(n)
所以:vector的随机访问效率更高
总而言之:
1. 已知需要存储的元素时,vector要好
2. 如果需要任意位置插入元素,list要好