- list是序列容器,允许在序列中的任何位置执行固定O(1)时间的插入和删除操作,并在两个方向上进行迭代。
- list容器使用双链表实现;双链表将每个元素存储在不同的存储(内存)位置。每个节点通过next,prev指针链接成顺序表。
- list 与其他基本标准序列容器(array、vector和deque)相比,list 通常在容器内的任何位置插入、提取和移动元素(已经获得迭代器的情况下时间渐进复杂度O(1)) 。
- list 与其他序列容器(vector,array, deque)相比,list和forward _list(单链表实现)的主要缺点是它们不能通过位置直接访问元素;例如,要访问列表中的第六个元素,必须从已知位置〈如开始或结束)迭代到该位置,需要线性时间开销。
- 存储密度低,list要使用一些额外的内存空间(next,prev)来保持与每个元素相关联(前后序的线性)的链接信息,从而导致存储小元素类型(int, short, char)的列表的存储密度低。
int main()
{
list<int> list1 = { 12,23,34,45,56 };
list<int>::iterator it = list1.begin();
it + = 4;//it不可以跳着加
// 只能 ++it,it++ ,对自己设计的类型,及可能使用++it,因为效率更快。it++会产生副本
std::vector<int> vec
std:: vector<int>::iterator vec1 = vec.begin()
vec1 = vec1+5;// 完全可以,随机迭代器
}
初始化:
int main()
{
list<int> list1;
//构造10个结点,并初始化为20
list<int> list2(10, 20);
//构造10个默认结点
list<int> list3(10);
// 利用别的list的已知范围来初始化
list<int> list4(list3.begin(),list3.end());
list<int> list5(list4);
list<int> list5(std::list4)
std::vector<int> vec = {1,2,3,4,5};
list<int> list4(vec.begin(),vec.end());//新版本STL适用
// 初始化列表方案
list<int> list6({ 12, 23, 34 });
list<int> list7 = { 12,23,34 };
list<int> list8 = { 12,23,34 };
//利用已存在的list来直接赋值
list<int> list9 = list6;
return 0;
}
元素访问:
不能使用下标访问和at()访问。(因为物理地址是不连续的)
int main(void) {
list<int> ilist1{ 12,23,34,45,56 };
list<int>::iterator it = ilist1.begin();
cout << *it << endl;
for (auto& x : ilist1)
{
cout << x << endl;
}
return 0;
}
迭代器失效问题:
插入数据不会出现迭代器失效问题。
- list和vector在插入元素的时候,是完全不同的两种情况。
vector在插入一个元素的时候,如果容量不足,会重新申请一段空间,然后将原空间中的值拷贝到新的空间。原空间会被释放 - 而vector的迭代器也就失效了。
而list,是按照结点申请,只需要把新结点插入到链表中,原始的数据并没有重新申请空间。所以不存在迭代器失效问题。
int main(void) {
list<int> ilist1{ 12,23,34,45,56 };
list<int>::iterator it = ilist1.begin();
cout << *it << endl;
ilist1.push_front(10);//可以头插
cout << *it << endl;
return 0;
}
删除数据,会导致迭代器失效。
因为删除数据,会导致当前节点被释放,因此指向当前结点的迭代器会失效。
int main(void) {
list<int> ilist1{ 12,23,34,45,56 };
list<int>::iterator it = ilist1.begin();
cout << *it << endl;
ilist1.pop_front();
cout << *it << endl;
return 0;
}
删除结点前:
删除结点后:
解决办法:
使用erase,将迭代器传入,删除当前对象,返回下一个结点的地址,让迭代器指向要删除结点的下一个位置。
但是一直删除,最终也会失效。
int main(void) {
list<int> ilist1{ 12,23,34,45,56 };
list<int>::iterator it = ilist1.begin();
cout << *it << endl;
it = ilist1.erase(it);
cout << *it << endl;
return 0;
}
删除前:
删除后:
删除重复元素:
- 必需先排序
- 才能删除重复结点
int main()
{
list<int> list1 = { 2,4,7,3,9,7,5,3,1,4,6,8 };
for (auto& x : list1)
{
cout << x << " ";
}
cout << endl;
//必需先排序
list1.sort();
//才能删除重复结点
list1.unique();
for (auto& x : list1)
{
cout << x << " ";
}
}
删除指定元素
list1.remove(23);
vector和list的区别
底层实现不同
vector:连续存储的容器,是一个动态数组,在堆区上分配空间。
list:动态双向链表,靠各个结点地址连接起来,在堆区分配空间。
空间利用率
vector:连续空间,不易造成内存碎片。空间利用率高
list:结点不连续,易造成内存碎片,小元素使结点密度低。空间利用率低。
查询元素
vector:由于是连续空间,所以可以通过 iterator, operator[],find()来查询,时间复杂度为O(n),还可通过二分查询binary_serach(),时间复杂度O(log2n)。
list:空间不连续,只能通过iterator, find()来查询,时间复杂度O(n)。
插入和删除
vector:
在末尾插入:1. 容量足够,push_back()时间复杂度为O(1).
2.容量不足时,push_back()时间复杂度为O(n); 因为需要重新申请空间,再把原有数据拷贝过去。
在中间插入:1.容量够时,需要插入后需要将后面数据后移。2.容量不够时,重新申请空间,再拷贝之前数据。
insert(it, val):O(n);
删除:尾删(pop_back()),O(1);其他地方O(n);
erase(it): O(n);
list:
插入:需要申请内存,时间复杂度O(1),push_back(),push_front(),insert(it, val);
删除:需要释放内存,时间复杂度O(1),pop_back(), pop_front()
erase(it):O(n);
迭代器
vector:
随即迭代器,迭代器检查越界。支持 ++, – , +, += , > , < , == , !=
list:
双向迭代器,迭代器检查越界。支持 ++, --, ==, !=。
迭代器失效
vector:
插入和删除都会导致迭代器失效
list:
插入元素不会导致迭代器失效;删除元素使当前迭代器失效,不影响其他迭代器。
总结
在平时使用时,不能谈论某个容器的优缺点,因为每种容器都有它适合的场景,只能说哪种容器在哪种场合更实用。
vector底层实现是数组; list是双向链表。
vector支持随机访问,list不支持。
vector是顺序内存,list不是。
vector在中间节点进行插入删除会导致内存拷贝,list不会。
vector一次性分配好内存,不够时才进行2倍扩容(或1.5倍);list每次插入新节点都会进行内存申请。
vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。
什么时候该用vector? 什么时候该用list?
如果需要高效的随机存取,而不在乎插入和删除的效率(很少使用插入和删除操作)。选用vector。
如果需要大量的插入和删除的操作,随机存取很少使用。选用list.