一、list的介绍
- list是可以在常数时间内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list的底层是双向带头循环链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
- list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
- 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
- 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间用于存储指针,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
二、list的使用
对于list的使用这里就不做过多介绍了。因为STL设计的相似性,各种容器的使用方法都是类似的。
大家可以参考网站cplusplus.com中对list的使用手册,快速上手使用。
下面只介绍一些相对特殊和list专有的方法:
2.1 insert & erase
void Test1(){ int arr[] = {1,2,3,4,5};list<int> lt(arr, arr+5); list<int>::iterator it = lt.begin(); while(it != lt.end()) { //在偶数前连续插入其10倍和100倍 if(*it % 2 == 0) { lt.insert(it,*it*10); lt.insert(it,*it*100); } ++it; } for(int e : lt) { cout << e << " "; } cout <<endl;
}
运行结果:
结论:
- 由于链表中每个元素都存储在互不相关的独立节点中插入无需挪动数据,且不需要扩容;不存在迭代器移位和野指针的问题。因此
list::insert
不会造成迭代器失效。 list::insert
返回新插入的第一个节点的迭代器。
void Test2(){int arr[] = {1,2,2,3,4,4,5,6,6,6};list<int> lt(arr, arr+10); list<int>::iterator it = lt.begin();while(it != lt.end()){ //删除链表中所有的偶数//if(*it % 2 == 0) //错误写法,程序崩溃//{//lt.erase(it);//}//++it;if(*it % 2 == 0) //正确写法{it = lt.erase(it);}else{++it;}}for(int e : lt){cout << e << " ";}cout <<endl;
}
运行结果:
结论:
list::erase
删除节点并释放空间,此时的迭代器成了野指针,++it
访问野指针程序崩溃。因此list::erase
会造成迭代器失效。- 但
list::erase
返回最后删除节点的下一个节点的迭代器,可以通过接收返回值实现连续删除。
2.2 list::sort
-
默认升序排序,要排降序传greater()对象。
-
注意:list不能使用<algorithm>算法库中的sort。由于list不能通过下标访问的原因(快速排序不能进行三数取中等操作),<algorithm>库中提供的sort函数并不能排序链表。因此list类模版中定义了list专用的成员函数list::sort可以对链表进行排序,list::sort底层使用归并排序实现。
vector_sort VS list_sort
void Test5(){list<int> lt;vector<int> v1;srand(time(nullptr));size_t k = 1000000;for(size_t i = 0; i<1000000; ++i){ int e = rand();lt.push_back(e);v1.push_back(e);}//list_sort:double begin1 = clock();lt.sort();double end1 = clock();//vector_sort:double begin2 = clock();sort(v1.begin(), v1.end());double end2 = clock();//vector_copy_sort:vector<int> v2(k);double begin3 = clock();copy(lt.begin(), lt.end(), v2.begin());sort(v2.begin(), v2.end());copy(v2.begin(), v2.end(), lt.begin());double end3 = clock();cout << "list_sort: " << (end1-begin1)/CLOCKS_PER_SEC*1000 << endl; //单位mscout << "vector_sort: " << (end2-begin2)/CLOCKS_PER_SEC*1000 <<endl;cout << "vector_copy_sort: " << (end3-begin3)/CLOCKS_PER_SEC*1000 <<endl;
}
运行结果:
总结:
- vector sort<algorithm>底层采用快速排序算法 O(N*logN),list::sort 底层采用归并排序算法 O(N*logN)。两种算法的效率相差不大。
- 但由于vector存储空间连续的特点,访存速度较快。因此对于少量数据vector_sort和list_sort的效率相差不大。但对于数据量较大的排序vector_sort的效率更高。
- 在数据量很大的情况下,甚至将list数据拷贝到vector中进行排序后再拷贝回来(vector_copy_sort)都比单纯的list_sort更高。
2.3 其他接口
splice
注意:“转移”不是拷贝而是移动,即转移数据后原链表中的数据丢失。对于链表而言这样的移动数据操作非常高效,只需要改变节点间指针的链接关系即可。
advance
功能:advance是一个函数模版可以向后移动任意类型的迭代器,包括list。
注意:
不同于string和vector这类顺序存储容器的迭代器(底层实际是指针),list迭代器没有重载
operator+
和operator-
不能使用+或-直接移动迭代器。必须通过调用advance函数实现“随机访问”。注意使用advance实现的“随机访问”加引号,底层任需要遍历n个节点,效率低。
使用advance必须包含头文件<iterator>,并且展开std命名空间。
distance
功能:distance是一个函数模版,用于计算任意类型迭代器之间(左闭右开区间)的元素个数,包括list。
提示:使用distance必须包含头文件<iterator>,并且展开std命名空间。
remove & remove_if
功能:删除链表中与指定值相等的所有节点,如果节点的数据是自定义类型,在释放空间之前还会调用析构函数。
功能:功能与remove类似,只不过参数是一个返回值为bool的一元函数指针,用于条件判断。如果满足条件就从链表中删除。
unique
注意:需要在使用前先对链表进行排序。
功能:
-
第一个版本用于删除链表中的重复元素;
-
第二个版本需传入一个返回值为bool的二元函数指针,用于条件判断。如果前后两个元素满足指定的条件则删除后一个保留前一个。
merge
注意:需要在使用前先对链表进行排序。
功能:
- 第一个版本按值比较归并两个有序链表,归并结束后x为+空。
- 第二个版本需要传入指定的比较函数,如果认为第一个参数在其定义的严格弱排序中先于第二个参数,则返回
true
,否则返回false
。
reverse
功能:用于逆置链表。