09.【C++】list链表(STL中的列表容器,C++封装的带头双向链表,可实现指定类型的增删查改,迭代器操作等功能)

发布于:2025-03-19 ⋅ 阅读:(10) ⋅ 点赞:(0)

目录

一. list的介绍及使用

1.1 list的介绍

1.2 list的使用

1.2.1 list的构造

1.2.2 list iterator迭代器的使用

1.2.3 list size & empty 大小判空

1.2.4 list element access 头尾引用

1.2.5 list modifiers 增删查改

1.2.6 list的迭代器失效

1.2.7 list 排序的使用

二. list的模拟实现

2.1 模拟实现list

三. list与vector的对比


一. list的介绍及使用

1.1 list的介绍

        list文档介绍

1.2 list的使用

        list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。

1.2.1 list的构造

构造函数constructor 接口说明
(1) list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素
(2) list() 构造空的list
(3) list (const list& x) 拷贝构造函数
(4) list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造list
#include<iostream>
#include<list>
using namespace std;
// list的构造
void test_list1()
{
    list<int> l1;                         // (2) 构造空的l1
    list<int> l2(4, 100);                 // (1) l2中放4个值为100的元素
    list<int> l3(l2.begin(), l2.end());   // (4) 用l2的[begin(), end())左闭右开的区间构造l3
    list<int> l4(l3);                     // (3) 用l3拷贝构造l4

    int array[] = { 16,2,77,29 };         // (5) 以数组为迭代器区间构造l5
    list<int> l5(array, array + sizeof(array) / sizeof(int));

    list<int> l6{ 1,2,3,4,5 };            // (6) 列表格式初始化C++11

    // 用迭代器方式打印l5中的元素
    list<int>::iterator it = l5.begin();
    while (it != l5.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // C++11范围for的方式遍历
    for (auto& e : l5)
        cout << e << " ";
    cout << endl;
}
int main()
{
    test_list1();
	return 0;
}

1.2.2 list iterator迭代器的使用

        此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

示例: 

#include<algorithm>
#include<iostream>
#include<list>
#include<vector>
using namespace std;
// 打印函数
template<typename T>
void Print(T ls)
{
    for (auto a : ls)
        cout << a << " ";
    cout << endl;
}
void test_list4()
{
    int array[] = { 16,2,77,29 };         // 以数组为迭代器区间构造
    list<int> l1(array, array + sizeof(array) / sizeof(int));
    Print(l1);
    //sort(l1.begin(), l1.end());   //报错:不定义该运算符或到预定义运算符可接收的类型的转换
    reverse(l1.begin(), l1.end());  // list是BidirectionalIterator
    Print(l1);                      // 只能用BidirectionalIterator迭代器和向前兼容的迭代器支持的算法
                                    // sort函数在algorithm算法库里是RandomAccessIterator,所以list不能用

    vector<int> v1(array, array + sizeof(array) / sizeof(int));
    Print(v1);
    sort(v1.begin(), v1.end());     // vector是RandomAccessIterator
    Print(v1);                      // 能用RandomAccessIterator迭代器和向前兼容的迭代器支持的算法
    reverse(v1.begin(), v1.end());  // sort函数vector迭代器本来就支持
    Print(v1);                      // reverse函数是RandomAccessIterator迭代器向前兼容的迭代器支持的算法
    
    // 但是list库里自己定义了sort函数
    cout << endl;
    l1.sort();
    Print(l1);
}
int main()
{
    test_list4();
    return 0;
}
函数声明 接口说明
begin+end 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin+rend 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置

【注意】

        1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

        2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动 

1.2.3 list size & empty 大小判空

函数声明 接口说明
(1) empty 检测list是否为空,是返回true,否则返回false
(2) size 返回list中有效节点的个数

1.2.4 list element access 头尾引用

函数声明 接口说明
(1) front 返回list的第一个节点中值的引用&
(2) back 返回list的最后一个节点中值的引用&

示例:1.2.3、1.2.4

#include<iostream>
#include<list>
using namespace std;
// 打印函数
template<typename T>
void Print(list<T> ls)
{
    for (auto a : ls)
        cout << a << " ";
    cout << endl;
}
void test_list2()
{
    int array[] = { 16,2,77,29 };         // 以数组为迭代器区间构造
    list<int> l1(array, array + sizeof(array) / sizeof(int));
    Print(l1);
    cout << l1.size() << endl;  // (1) 返回l1的size
    cout << l1.empty() << endl; // (2) 返回l1是否为空
    cout << l1.front() << endl; // (3) 返回l1首元素的引用
    cout << l1.back() << endl;  // (4) 返回l1最后一个元素的引用
}
int main()
{
    test_list2();
    return 0;
}

1.2.5 list modifiers 增删查改

函数声明 接口说明
(1) push_front 在list首元素前插入值为val的元素
(2) pop_front 删除list中第一个元素
(3) push_back 在list尾部插入值为val的元素
(4) pop_back 删除list中最后一个元素
(5) insert 在list position 位置中插入值为val的元素
(6) erase 删除list position位置的元素
(7) swap 交换两个list中的元素
(8) clear 清空list中的有效元素
(9) splice 在L1的position前插入另一个链表L2的元素,内存上相当于move,插入后L2的元素就clear了

        list中还有一些操作,需要用到时大家可参阅list的文档说明。

示例1:(1)~(8)

#include<iostream>
#include<list>
using namespace std;
// 打印函数
template<typename T>
void Print(list<T> ls)
{
    for (auto a : ls)
        cout << a << " ";
    cout << endl;
}
void test_list3()
{
    int array[] = { 16,2,77,29 };         // 以数组为迭代器区间构造
    list<int> l1(array, array + sizeof(array) / sizeof(int));
    Print(l1);

    l1.push_front(100); // (1) 头插
    Print(l1);

    l1.pop_front();     // (2) 头删
    Print(l1);

    l1.push_back(99);   // (3) 尾插
    Print(l1);

    l1.pop_back();      // (4) 尾删
    Print(l1);
    cout << endl;

    list<int>::iterator pos = find(l1.begin(),l1.end(),77);
    l1.insert(pos, 55); // (5) 指定位置之前插入 在77之前插入55
    Print(l1);

    pos = find(l1.begin(), l1.end(), 2);
    l1.erase(pos);      // (6) 删除指定位置的元素 删除2
    Print(l1);
    cout << endl;

    list<int> l2(3, 100);
    swap(l1, l2);       // (7) 交换两个列表的元素,大小可以不同
    Print(l1);
    Print(l2);

    l1.clear();
    Print(l1);          // (8) 清空列表的元素
    cout << "clear" << endl;
}
int main()
{
    test_list3();
    return 0;
}

示例2:(9)

move链表 (1)

void splice (iterator position, list& x); 

// 把 x 的所有元素移到 position 前面

move链表的某个迭代器指向的元素(2)

void splice (iterator position, list& x, iterator i); 

// 把 的 i 迭代器指向的元素移到 position 

move链表的一段区间的元素 (3)

void splice (iterator position, list& x, iterator first, iterator last);

// 把 的从 [first, last) 迭代器之间的元素移到 position 

        注意,splice可以移动其他链表的元素,也可以移动自身链表的元素。

#include<iostream>
#include<list>
using namespace std;
// 打印函数
template<typename T>
void Print(T ls)
{
    for (auto a : ls)
        cout << a << " ";
    cout << endl;
}
void test_list5()
{
    int array1[] = {1,2,3,8,9,10 };        // 以数组为迭代器区间构造l1
    list<int> l1(array1, array1 + sizeof(array1) / sizeof(int));
    int array2[] = { 4,5,6,7 };           // 以数组为迭代器区间构造l2
    list<int> l2(array2, array2 + sizeof(array2) / sizeof(int));

    list<int>::iterator it = find(l1.begin(), l1.end(), 3);
    ++it;
    l1.splice(it, l2);      // (1) 将l2的所有元素移到l1的3元素之后
    Print(l1);
    Print(l2);
    cout << "splice" << endl;

    l1.splice(l1.begin(), l1, --l1.end());
    Print(l1);              // (2) 将l1的最后一个元素移动到第一个元素之前
    l1.splice(l1.end(), l1, l1.begin());
    Print(l1);              // (2) 将l1的第一个元素移动到最后一个元素的位置

    it = find(l1.begin(), l1.end(), 5);
    l1.splice(l1.begin(), l1, ++it, l1.end());
    Print(l1);              // (3) 将l1的从5后面到最后一个元素启动到第一个元素之前
}
int main()
{
    test_list5();
    return 0;
}

1.2.6 list的迭代器失效

        前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

1.2.7 list 排序的使用

        list自带的sort排序效果较差,不如list拷贝到vector调用算法库排序在拷贝回list效果好。

注意:排序测试不要用Debug,用Release测试出的才是真实水平。

#include<iostream>
#include<list>
#include<vector>
#include<algorithm>
using namespace std;

void test_op1()
{
	srand(time(0));
	const int N = 1000000;

	list<int> lt1;
	vector<int> v;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		lt1.push_back(e);
		v.push_back(e);
	}

	// 算法库中的sort
	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();

	// list自己的sort
	int begin2 = clock();
	lt1.sort();
	int end2 = clock();

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

void test_op2()
{
	srand(time(0));
	const int N = 1000000;

	list<int> lt1;
	list<int> lt2;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand() + i;
		lt1.push_back(e);
		lt2.push_back(e);
	}
	// list拷贝给vector,用算法库将vector排序,在把vector拷贝回list
	int begin1 = clock();
	// 拷贝vector
	vector<int> v(lt2.begin(), lt2.end());
	// 排序
	sort(v.begin(), v.end());
	// 拷贝回lt2
	lt2.assign(v.begin(), v.end());
	int end1 = clock();

	// list自带的sort
	int begin2 = clock();
	lt1.sort();
	int end2 = clock();

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

int main()
{
	test_op1();		// vector调用库函数sort排序,和list自带排序对比
	cout << endl;
	test_op2();		// list拷贝到vector排完序再拷贝回list,和list自带排序对比
	return 0;		//总结:list自带的排序效果差
}

二. list的模拟实现

2.1 模拟实现list

        【免费】C++list的模拟实现资源-CSDN文库

三. list与vector的对比

        vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下: 

vector list
底层结构 动态顺序表,一段连续空间 带头结点的双向循环链表
随机访问 支持下标随机访问,访问某个元素效率O(1) 不支持随机访问,访问某个元素效率O(N)
插入和删除 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低。 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器 原生态指针 对原生态指针(节点指针)进行封装
迭代器失效 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景 需要高效存储,支持随机访问,不关心插入删除效率 大量插入和删除操作,不关心随机访问

总结:

vector 优点 1.尾插尾删效率不错,支持高效下标随机访问
2.物理空间连续,所以高速缓存利用率高
缺点 1.空间需要扩容,扩容有一些代价(效率和空间浪费)
2.头部和中间插入删除效率低
list 优点 1.按需申请释放空间,不需要扩容
2.任意位置插入删除
缺点 1.不支持下标随机访问