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等内存空间连续的容器。这可能是因为连续空间的访问效率比不连续空间的要快。

相关推荐
C+-C资深大佬19 小时前
C++ 中的 constexpr与 const区
java·开发语言·c++
8Qi819 小时前
LeetCode 32:最长有效括号 —— 栈 + 标记法 题解
java·数据结构·算法·leetcode·职场和发展··括号匹配
Tairitsu_H19 小时前
[LC优选算法#3] 滑动窗口 | 将x减到0的最⼩操作数 | ⽔果成篮 | 字⺟异位词
c++·算法·leetcode·滑动窗口
YM52e19 小时前
鸿蒙HarmonyOS ArkTS 实战:教师座椅出入记录 APP 从零到一
学习·华为·harmonyos·鸿蒙系统
c++之路19 小时前
CMake 系列教程(一):CMake 基础知识
c语言·开发语言·c++
踏着七彩祥云的小丑19 小时前
嵌入式测试第 32 天:升级测试:固件OTA升级、断点续传、回滚测试
单片机·嵌入式硬件·学习
Irissgwe19 小时前
C++ STL bitset 和位图详解
开发语言·c++·stl·位图·bitset
小陈phd20 小时前
Text2SQL智能体学习笔记(二)——NL2SQL落地的隐形基石:元数据库
数据库·笔记·学习
洛水水20 小时前
【力扣100题】76.搜索插入位置
数据结构·算法·leetcode
踏着七彩祥云的小丑20 小时前
Go学习第4天:条件、循环语句+函数
学习·golang·go