List的学习

List

我们当前学习的主要是带头双向循环链表。其中一个原因是循环链表可以实现时间复杂度为O(1)的插入、删除等操作。

1、基本用法

无参构造:

cpp 复制代码
list<int> l1;

initializer_list构造:

cpp 复制代码
list<int> l2 = { 1,2,3,4 };

我们就可以用迭代器和范围for的方式,打印list序列:

2、插入、删除

由于list没有规定find()查找方法,我们就借用算法库的find()。

cpp 复制代码
list<int> l3 = { 1,2,3,4 };
auto it2 = find(l3.begin(), l3.end(), 3);

插入:

插入后,迭代器it2没有失效,因为list不存在扩容。

删除:

但是删除后,迭代器it2失效了,因为erase()删除的本质,是释放指定节点的空间。此时vs2022环境下会报警告:

3、一些方法

reverse():逆置

merge():合并

合并算法,类似于我们在数据结构算法中,将两个有序序列,合成一个有序序列的算法题。

只不过这里给出的序列,不一定是有序的,需要先排序:

unique():去重

去重算法,也要排序:

remove():删除指定数

splice():转移

splice()可以不同序列之间进行转移:

也可以同一序列内进行转移:

sort():排序

这里就有人要问了:为什么不用算法库的sort()?

发生了编译错误。

我们转向文件algorithm的第8403行,可以发现一点点问题产生的原因:

我们发现,算法库sort(),其底层使用的迭代器,支持减操作。

要认识这个问题,我们就要简单了解迭代器按照功能的分类。

3.1、迭代器分类的简单了解

迭代器按照功能,可以分为三类:

  • 单向:只能向后走一步(++)
  • 双向:可以向前、向后,但是只能一步(++、--)
  • 随机:可以向前、向后多步(++、--、+、-)

迭代器的种类,是由所属容器决定的:

我们可以理解为:List的迭代器,如果实现成随机迭代器,由于链表节点的空间不是连续的,那么需要遍历,代价变大。

而诸如vector之类容器的迭代器,由于顺序表空间是连续的,所以支持随机迭代器,代价就不那么大。

所以List的迭代器,实现成了代价更小的双向迭代器。

算法库里的方法,对传入的迭代器也有要求:


按照不同功能迭代器的说明,我们不难理解:双向迭代器可以被当作单向迭代器使用、随机迭代器可以被当作单向迭代器和双向迭代器使用。

而单向迭代器不可以被当作双向迭代器和随机迭代器使用,双向迭代器也不可以被当作随机迭代器使用。

也就是说,算法库的sort()不支持List的双向迭代器,所以开发者实现了支持双向迭代器的List成员sort()

有时,我们也会看到这样的迭代器类型:

其实,迭代器还可以分为:

  • 只读(OutputIterator)
  • 只写(InputIterator)

现阶段我们只需要认为,只写迭代器,包括了单向、双向、随机迭代器。

3.2、List下的成员sort的性能讨论

首先我们要明白:

  • List下的成员sort,使用的是处理后的归并排序
  • 算法库中的sort,使用的是快速排序

这两个排序的时间复杂度,都是O(NlogN),但是有差异。

我们简单设计程序,印证这一点。

分别在List、vector中放入1000000个数据,然后记录、比较排序的用时:

cpp 复制代码
void test_op1()
{
	vector<int> v;
	list<int> l;

	const int n = 1000000;
	for (int i = 0; i < n; ++i)
	{
		int x = rand() % 100 + 1;
		v.push_back(x);
		l.push_back(x);
	}

	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();
	int begin2 = clock();
	l.sort();
	int end2 = clock();

	printf("vector:>%d\n", end1 - begin1);
	printf("list:>%d\n", end2 - begin2);
}

我们当前是在x64的release版本下进行测试,而不是debug版本。因为debug版本编译、链接时要做很多处理,而release不需要。

我们可以发现,同样时间复杂度的排序方式,List的sort明显比算法库的sort慢了十几倍。

我们再进行一个测试:创建两个List,放入1000000个数据,其中一个List的数据被vector拷贝,vector经算法库sort排序,再拷贝回List;另一个List直接排序。

cpp 复制代码
void test_op2()
{
	list<int> l1;
	list<int> l2;

	const int n = 1000000;
	for (int i = 0; i < n; ++i)
	{
		int x = rand() % 100 + 1;
		l1.push_back(x);
		l2.push_back(x);
	}
	
	int begin1 = clock();
	vector<int> copy(l1.begin(), l1.end());
	sort(copy.begin(), copy.end());
	l2.assign(copy.begin(), copy.end());
	int end1 = clock();

	int begin2 = clock();
	l2.sort();
	int end2 = clock();

	printf("list1:>%d\n", end1 - begin1);
	printf("list2:>%d\n", end2 - begin2);
}

可以看到,两种排序方式的用时几乎一致,有时拷贝并使用算法库的sort,还优于List的sort。

所以,List的sort,在大量访问数据的情况下,效率普遍低于算法库的sort。所以我们在大量访问数据的情况下,尽可能使用算法库的sort,其实就是借助vector等内存空间连续的容器。这可能是因为连续空间的访问效率比不连续空间的要快。

相关推荐
6Hzlia1 小时前
【Hot 100 刷题计划】 LeetCode 23. 合并 K 个升序链表 | C++ 顺序合并
c++·leetcode·链表
今夕资源网2 小时前
Visual C++运行库合集 V104.0 一个github免费开源的项目VisualCppRedist AIO
开发语言·c++·dll修复工具·dll修复·运行库·修复软件
syagain_zsx2 小时前
剖析“继承”,清晰易懂
开发语言·c++
Season4502 小时前
C++中论在类中成员变量定义顺序的重要性
开发语言·c++
拳里剑气2 小时前
C++算法:前缀和
开发语言·c++·算法·前缀和
三克的油2 小时前
YOLOV5数据学习
人工智能·学习·yolo
zhangrelay2 小时前
复盘《用智能大模型复盘课程博客停更案例》
笔记·学习
sjsjsbbsbsn2 小时前
RAG 基础学习总结
java·数据库·学习
啊我不会诶2 小时前
Codeforces Round 1091 (Div. 2) and CodeCraft 26
c++·算法