【探索C++中的链表】手动实现list容器

在这里插入图片描述

write in front
所属专栏: C++学习
️博客主页:睿睿的博客主页
️代码仓库:VS2022_C语言仓库
您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
关注我,关注我,关注我你们将会看到更多的优质内容!!


文章目录

  • 前言
  • 一.list的算法:
    • 1.reverse:
    • 2.sort:
    • 3.remove:
    • 3.splice:
    • 4.unique:
    • 5.merge:
  • 二.list的模拟实现:
    • 1.迭代器的封装:
    • 2.模板的进阶使用:
    • 3.几个易错点
  • 总结

前言

  尊敬的读者们,今天我们将一起深入探讨C++中list(链表)的模拟实现。list是C++标准库中的一种双向链表容器,它允许我们在任意位置高效地插入和删除元素。为了更好地理解list的内部工作原理,我们将手动实现一个简化版的list,并介绍它的基本操作。

  这里是list的功能库:list功能库

一.list的算法:

【探索C++中的链表】手动实现list容器_第1张图片

1.reverse:

【探索C++中的链表】手动实现list容器_第2张图片

  简单的说,list里面的逆置其实是没有必要的,因为算法库里面也提供了逆置:

	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);

	lt.push_front(10);
	lt.push_front(20);
	//list容器里面提供的逆置
	lt.reverse();
	//算法库里面提供的逆置
	reverse(lt.begin(),lt.end());

2.sort:

【探索C++中的链表】手动实现list容器_第3张图片

  那么,sort函数也是白写的吗?这就涉及到迭代器的匹配问题了。况且我们也知道,库里面的sort排序是快排,不能排链表。list容器里面的排序使用的是归并,空间复杂度远高于高于库里面的。我们可以做以下实验:

void op()
{
	list<int> a1;
	//list a2;
	srand((unsigned int)time(0));
	const int N = 10000000;

	for (int i = 0; i < N; i++)
	{
		a1.push_back(rand());
	}
	list<int> a2(a1);

	//用list容器里面的sort直接排序:
	cout << "用list容器里面的sort直接排序时间:";
	int begin = clock();
	a1.sort();
	int end = clock();
	cout << end-begin << endl;

	//把list拷贝到vector,在使用算法库里面的sort排序时间:
	
	
	cout << "把list拷贝到vector,在使用算法库里面的sort排序时间:";
	begin = clock();
	vector<int> a3(a2.begin(), a2.end());
	sort(a3.begin(), a3.end());
	int i = 0;
	for (auto &e : a2)
	{
		e = a3[i++];
	}
	end = clock();
	cout << end - begin;
	

}

【探索C++中的链表】手动实现list容器_第4张图片
  由此可见,对于链表,如果我们要对少量数据排序,那么list容器里面的sort函数是足够的,但是对于大量数据,为了降低时间复杂度,我们使用先将其拷贝到vector容器,用算法库快排排序,在将结果拷贝回去的方法来排序

3.remove:

【探索C++中的链表】手动实现list容器_第5张图片

  这个函数和erase()函数很像,但是erase是根据位置删除数据,remove是根据val(删除数据的值)删除数据.

3.splice:

【探索C++中的链表】手动实现list容器_第6张图片
  这个函数就和我们之前做的oj题很像,将一个链表的一些元素转移到另一个链表的某个位置。

std::list<int> mylist1, mylist2;
  std::list<int>::iterator it;

  // set some initial values:
  for (int i=1; i<=4; ++i)
     mylist1.push_back(i);      // mylist1: 1 2 3 4

  for (int i=1; i<=3; ++i)
     mylist2.push_back(i*10);   // mylist2: 10 20 30

  it = mylist1.begin();
  ++it;                         // points to 2

  mylist1.splice (it, mylist2); // mylist1: 1 10 20 30 2 3 4
                                // mylist2 (empty)
                                // "it" still points to 2 (the 5th element)
                                          
  mylist2.splice (mylist2.begin(),mylist1, it);
                                // mylist1: 1 10 20 30 3 4
                                // mylist2: 2
                                // "it" is now invalid.
  it = mylist1.begin();
  std::advance(it,3);           // "it" points now to 30

  mylist1.splice ( mylist1.begin(), mylist1, it, mylist1.end());
                                // mylist1: 30 3 4 1 10 20

  std::cout << "mylist1 contains:";
  for (it=mylist1.begin(); it!=mylist1.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  std::cout << "mylist2 contains:";
  for (it=mylist2.begin(); it!=mylist2.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

当然,在这里我们也一定要考虑迭代器失效的问题!

4.unique:

【探索C++中的链表】手动实现list容器_第7张图片

  该函数就是移除链表里面相同元素的多余元素,但是但是!我们要先排序了之后才能使用这个函数。

double mydoubles[]={ 12.15,  2.72, 73.0,  12.77,  3.14,
                       12.77, 73.35, 72.25, 15.3,  72.25 };
  std::list<double> mylist (mydoubles,mydoubles+10);
  
  mylist.sort();             //  2.72,  3.14, 12.15, 12.77, 12.77,
                             // 15.3,  72.25, 72.25, 73.0,  73.35

  mylist.unique();           //  2.72,  3.14, 12.15, 12.77
                             // 15.3,  72.25, 73.0,  73.35

5.merge:

【探索C++中的链表】手动实现list容器_第8张图片
  将有两个顺序的链表合并起来并排序。其实就是归并排序。

二.list的模拟实现:

1.迭代器的封装:

  在前面的博客stl神奇的指针“迭代器”中我们提到,迭代器是连接算法和容器的桥梁,通过迭代器我们就可以通过算法来操作容器。
  在前面的vector容器的模拟实现中,我们使用了原生指针来作为其迭代器,因为指针的++--*都正好符合这些操作。但是我们的list是双向链表,物理可见不连续,如果我们用其指针来当作其迭代器,对于++--*这些操作符都是不能直接使用的。
  所以为了和其他容器的迭代器相似,要能正确使用这些操作符,我们就要通过封装来实现他的迭代器。

我们直接来看看其迭代器的模拟实现:

template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> self;
		Node* _node;

		__list_iterator(Node* node)
			:_node(node)
		{}

		Ref operator*()
		{
			return _node->_val;
		}

		Ptr operator->()
		{
			return &_node->_val;
		}

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self operator++(int)
		{
			self tmp(*this);

			_node = _node->_next;

			return tmp;
		}

		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		self operator--(int)
		{
			self tmp(*this);

			_node = _node->_prev;

			return tmp;
		}

		bool operator!=(const self& it) const
		{
			return _node != it._node;
		}

		bool operator==(const self& it) const
		{
			return _node == it._node;
		}
	};

  封装完这个类之后,我们在list类里面对其重命名一下就可以了:

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

2.模板的进阶使用:

  在上面的代码中我们可以看出,我们又套用了模板。这是因为对于const_iterator的时候,如果我们不用模板,我们的typedef会出现问题:

typedef const __list_iterator<T> const_iteraator;

  如果我们直接加const在前面,那么说明这个const_iterator不能改变,那么什么++等操作就不能完成了。这个与之前vector的原生指针不同,因为const T*是指T指向的内容不能边,但是还是可以++的。

  所以在这里为了解决这个问题,我们有两种方法:

第一种就是重新复制一遍,改一下返回值,但是很冗余。

第二种就是通过模板参数传参来实现。最后在重命名一下:

typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

3.几个易错点

  在我测试代码的时候遇到了一些问题:

问题一:

list<int>::const_iterator it = lt.begin();

在之前的学习里面用const修饰的变量接收非const变量,是权限的缩小,是可以实现的:

int a=10;
const int b=a;

但是为什么这里报错了呢?
【探索C++中的链表】手动实现list容器_第9张图片
  其实这里只是我自己个人的一个误区,以为加了const只是权限不同,但是实际上这个代码左右两边的类型都不一样!!模板类根据参数的不同生成不同的对象,这个时候两个自定义类型就已经不相同了,就不能用他赋值了。

问题二:
  为什么我们重载!===的时候参数一定要const?

因为如果不加const,我们的(it!=lt.end())就会错误。因为我们end()实现的时候,是这样实现的:

iterator end()
		{
			return _head;
		}

  这里返回的是一个临时拷贝,如果我们不用const修饰,引用肯定就会出错。当然,这里也涉及了单参数的构造函数支持隐式类型转换

总结

  这就是list容器的学习,具体代码在这里list模拟实现,欢迎大家指出我的问题!

  更新不易,辛苦各位小 伙伴们动动小手,三连走一走 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

专栏订阅:
每日一题
C语言学习
算法
智力题
初阶数据结构
Linux学习
C++学习
更新不易,辛苦各位小伙伴们动动小手,三连走一走 ~ ~ ~ 你们真的对我很重要!最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

你可能感兴趣的:(c++,c++,链表,list)