list 的介绍
- list是序列容器,允许在序列中的任何位置执行固定O(1)时间的插入和删除操作,并在两个方向上进行迭代
- list容器使用双链表实现,双链表将每个元素存储在不同的存储位置,每个节点通过next,prev指针链接成顺序表
- list与其他基本标准序列容器(array、vector和deque)相比,list通常在容器内的任何位置插入、提取和移动元素,已经获得迭代器的情况下时间渐进复杂度O(1)
- list与其他序列容器(vector、array、deque)相比,list和forward_list(单链表实现)的主要缺点是它们不能通过位置直接访问元素,例如,要访问列表中的第六个元素,必须从已知位置(如开始或结束)迭代到该位置,需要线性时间开销
- 存储密度低,list要使用一些额外的内存空间(next、prev)来保持与每个元素相关联(前后序的线性)的链接信息,从而导致存储小元素类型(如char、short、int等)的列表存储密度低
list 的使用
int main()
{
std::list<int> ilist;
ilist.push_back(12);
ilist.push_back(23);
std::list<int>::iterator it = ilist.begin();
//it = it + 5; list 不可以 直接跳跃
std::vector<int> ivec;
std::vector<int>::iterator vit = ivec.begin();
vit = vit + 5;
}
list 中的迭代器属于双向迭代器,既可以 it++
,也可以 it--
list 的创建
int main()
{
int ar[] = { 12,23,34,45,56,67,78,89,90,100 };
std::vector<int> vec = { 1,2,3,4,5,6,7 };
std::list<int> ilist1;
std::list<int> ilist2 = { 12,23,34,45,56 };
std::list<int> list3(10, 23);//创建10个节点,每个都为23
std::list<int> ilist4(ar, ar + 10);
std::list<int> ilist5(ilist2);
std::list<int> ilist6(std::move(ilist2));
std::list<int> ilist7(vec.begin(), vec.end());
}
- list2 的构建方法与vector相似,都是将其放入初始化列表
initializer_list
中,在从初始化列表中一个一个取值进行创建 - list3 创建10个节点,每个节点都创建为23
- list4 创建中,ar是数组的首元素地址,ar+10指向最后一个元素后继,将该数组的每个元素放如今list构成双链表
- list7 属于新版本STL的能力,将vector中的元素创建在list中,但是直接传入vec是不可以的
访问、push_front、erase、unique、remove
int main()
{
std::list<int> ilist1 = { 1,2,3,4,5,6,7 };
std::list<int>::iterator it = ilist1.begin();
for (; it != ilist1.end(); ++it)
{
cout << *it << endl; //[] ,at(i)
}
for (auto& x : ilist1)
{
cout << x << endl;
}
return 0;
}
list 中没有opertaor []
和 at(i)
的方法,因为list不属于连续空间不能进行连续访问
list中的大部分方法与vector相似
同时list中即存在头部插入也有尾部插入,因为这两种方法的时间复杂程度都是1,而vector中尾部插入时间复杂度为1,而头部插入需要遍历整个容器
int main()
{
std::list<int> ilist1 = { 1,2,3,4,5,6,7 };
std::list<int>::iterator it = ilist1.begin();
cout << *it << endl;
ilist1.push_front(10);
cout << *it << endl;
return 0;
}
vector 在容量已满的情况下进行push_bcak,那么会创建新的vector 并且进行拷贝构造以及析构旧的容器内的对象,继而会导致原本的迭代器失效
但是list的插入并不会影响原本的迭代器,因为新元素的插入,会购买一个节点进行插入,并不会影响旧的元素
int main()
{
std::list<int> ilist1 = { 1,2,3,4,5,6,7 };
std::list<int>::iterator it = ilist1.begin();
it = ilist1.erase(it);
cout << *it << endl;
return 0;
}
这里对 it 元素进行删除,并且将后续元素在赋值给 it
int main()
{
std::list<int> ilist1 = { 2,4,7,4,3,1,5,4,89,8,7,56,4,2,45 };
ilist1.sort(); //排序
for (auto& x : ilist1)
{
cout << x << " ";
}
cout << endl;
ilist1.unique(); //唯一性
for (auto& x : ilist1)
{
cout << x << " ";
}
cout << endl;
}
首先要进行唯一性操作,首先必须进行排序操作
int main()
{
std::list<int> ilista = { 1,2,3,3,3,4,5,6,7,8,9 };
for (auto& x : ilista)
{
cout << x << " ";
}
cout << endl;
ilista.remove(3); //移除满足特定标准的元素
for (auto& x : ilista)
{
cout << x << " ";
}
cout << endl;
return 0;
}
对特定元素进行删除,例如将所有的 3 进行删除
list 与 vector 的区别
项目 | vector | list |
---|---|---|
底层实现 | 连续存储的容器,动态数组,在堆上分配空间 | 动态双向链表,在堆上分配空间 |
空间利用率 | 连续空间,不宜造成内存碎片,空间利用率高 | 节点不连续,易造成内存碎片,小元素使节点密度低,空间利用率低 |
查询元素 | iterator operator[],find O(n),binary_search O(logn) | iterator find O(n) |
迭代器 | 随机迭代器,迭代器检查越界,支持++,–,+,+=,<,>,!=,== | 双向迭代器,迭代器检查越界,支持++,–,==,!= |
迭代器失效 | 插入和删除元素都会导致迭代失效 | 插入元素不会导致迭代器失效、删除元素导致当前迭代器失效,不影响其他迭代器 |
插入和删除 vector
- 在最后插入(容量够):push_back(val), O(1)
- 在最后插入(容量不够): 需要内存申请和释放,以及对之前数据进行拷贝
- 在中间插入(容量够):内存拷贝
- 在中间插入(容量不够):需要内存申请和释放,以及对之前数据的拷贝 insert(it,va) O(n)
- 删除:pop_back() 在最后删除 O(1)
- 在中间删除:内存拷贝,不需要释放内存(将数据往前移动),erase(it) O(n)
- resize(10):开辟空间,还放入数据
- reserve(10):只开辟空间,不放入数据,初始化 vector 空间,提高 vector 的使用效率
插入与删除 list
- 插入:O(1),需要内存申请
- push_front(val) O(1);push_back(val) O(1); insert(it,val) O(1)
- 删除:O(1),需要内存释放
- pop_front() O(1);pop_back() O(1);erase(it) O(n)
总结
- vector 底层实现是数组,list 是双向 链表
- vector 支持随机访问,list不支持
- vector 是顺序内存,list不是
- vector 在中间节点进行插入删除会导致内存拷贝,list不会
- vector 一次性分配好内存,不够时才进行2倍扩容(或1.5倍),list每次插入新结点都会进行内存申请
- vector 随机访问性能高,插入删除性能差,list随机访问性能差,插入删除性能好
使用选择
什么时候该用vector?什么时候该用list?
- 如果需要高效的随机存取,而不在乎插入和删除的效率(很少使用插入和删除操作),选用vector
- 如果需要大量的插入和删除的操作,随机存取很少使用,选用list