IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 模拟实现list -> 正文阅读

[数据结构与算法]模拟实现list

一:list的节点和构造函数

<1>:_list_node

  • list的底层是带头双向循环链表,所以每个节点中除了存储val和next,还需要存储前一个节点prev
template <class T>
struct _list_node
	{
		T _val;
		_list_node<T>* _next;
		_list_node<T>* _prev;

		_list_node(const T& val = T())
			:_val(val)
			,_next(nullptr)
			,_prev(nullptr)
		{}
	};

请添加图片描述

<2>: private成员与构造函数

  • 因为list是带头双向循环链表,所以我们只用一个成员_head记录哨兵位的头就可以
  • 所以构造函数我们只需要构造哨兵位的头节点,就可以完成list的构造,其余节点可以通过push_back,push_front,insert插入
  • 当然我们还可以通过迭代器直接构造一个链表,这个我们等讲完迭代器再说
  public:
		list()
		{
			CreateHead();
		}
  private:
		void CreateHead()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		node* _head;
	

二:迭代器(iterator)

  • 回想一下,再模拟实现string和vector时,我们迭代器都是原生指针进行封装
  • char*T*,const迭代器就是const char*,const T*
  • 现在list还能用原生指针么,显然不行,光是++这个操作原生指针就不支持,_list_node不是连续的,++一下不知道跑哪去了,所以我们需要用一个类封装list的迭代器,并在里面重载++,–,!=,==,*,->等操作符
  • 以*为例,可能你想这么做
template <class T>
	struct _list_iterator
	{
		typedef _list_node<T> node;
		typedef _list_iterator<T> self;
		node* _pnode;
		_list_iterator(node* pnode)
			:_pnode(pnode)
		{}
		T& operator*()
		{
			return _pnode->_val;
		}
	};

  • 这样做确实没错,那么const迭代器呢,像下面这样重载一个const版本?
template <class T>
	struct _list_iterator
	{
		typedef _list_node<T> node;
		typedef _list_iterator<T> self;
		node* _pnode;
		_list_iterator(node* pnode)
			:_pnode(pnode)
		{}
		T& operator*()
		{
			return _pnode->_val;
		}
		const T& operator*() const
		{
			return _pnode->_val;
		}
	};
  • 如果这么做,那就戳啦
  • 后面的const修饰的是this指针,也就是迭代器自己,而不是修饰迭代器指向的值
  • 所以这样不是const版本的迭代器,迭代器指向的值仍然能被修改,只是迭代器不能被修改
  • 那我们就在封装一个类叫const _list_iterator,然后把返回值改成const T&?
  • 这样做的确没问题,但是代码很冗余,有没有办法通过一个类同时封装两种迭代器呢?
  • 的确可以,这就是前辈们的智慧了
template <class T,class Ref,class Ptr>
	struct _list_iterator
	{
		typedef _list_node<T> node;
		typedef _list_iterator<T,Ref,Ptr> self;
		node* _pnode;
		_list_iterator(node* pnode)
			:_pnode(pnode)
		{}
		self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}
		self& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}
		self operator++(int)
		{
			self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}
		self operator--(int)
		{
			self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}
		bool operator!=(const self&s)
		{
			return _pnode != s._pnode;
		}
		bool operator==(const self& s)
		{
			return _pnode == s._pnode;
		}
		Ref operator*()
		{
			return _pnode->_val;
		}
		Ptr operator->()
		{
			return &_pnode->_val;
		}
	};
  • 我们将模板参数改为3个,第一个还是T,后面两个一个是Ref,一个是Ptr
  • 这样有啥用呢?
  • 前面我们重载operator*的返回类型就可以写成Ref,而不是T&了
  • 对于const迭代器和普通迭代器,我们可以通过传不同的模板参数来控制
	typedef _list_iterator<T,T&,T*> iterator;
	typedef _list_iterator<T,const T&,const T*> const_iterator;
    iterator begin()
	{
		return iterator(_head->_next);
	}
	iterator end()
	{
		return iterator(_head);
	}
	const_iterator begin() const
	{
		return const_iterator(_head->_next);
	}
	const_iterator end() const
	{
		return const_iterator(_head);
	}
  • 对于const迭代器就传const T&,const T*,普通迭代器就传T&,T*,完美解决
  • 而begin就直接返回哨兵位的下一个位置,end就更妙了,直接返回哨兵位,这是它循环的特性造成的,走到最后一个节点后就会走回哨兵位
  • 至于具体如何实现这几个重载,就不赘述,代码十分简单,相信你一看就懂
  • 唯一需要注意的是operator->,它的返回类型是T*,而如果T是一个非内置类型
  • 比如Date,我们想访问里面的year,是不是得要两个->,就像这样it->->year,第一个->返回的是Date*,我们还需要再一个->才能访问到year
  • 编译器对此做了优化,实际上使用时我们只需要一个->就能访问到year

三:insert与erase

<1>: insert

  • insert通过传入迭代器来进行插入,注意的是它是在pos位置之前插入,这样头插因为有哨兵位存在也可以进行,而如果在pos位置之后插入,尾插就得特殊考虑了
void insert(iterator pos, const T& x)
		{
			assert(pos._pnode);//节点不为空才插入
			node* cur = pos._pnode;
			node* prev = cur->_prev;
			node* newnode = new node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev=newnode;
		}
  • 通过复用insert,很快就能实现push_back和push_front
		void push_back(const T& x)
		{
			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(),x);
		}

<2>: erase

  • erase的使用会使迭代器失效,我们需要返回删除后的节点位置保证迭代器仍然有效
  • 注意:哨兵位的头不能删!
	iterator erase(iterator pos)
	{
		assert(pos._pnode);
		assert(pos != end());//节点不为空并且不能删哨兵位
		node* cur = pos._pnode;
		node* prev = cur->_prev;
		node* next = cur->_next;
		delete cur;
		prev->_next = next;
		next->_prev = prev;
		return iterator(next);
	}
  • 复用erase实现pop_back和pop_front
    void pop_back()
	{
		erase(--end());
	}
	void pop_front()
	{
		erase(begin());
	}

四:拷贝构造与operator=

<1> 拷贝构造

  • 有了迭代器就很好实现拷贝构造了,构造头结点后使用迭代器遍历另一个链表,将其每个节点push_back进当前链表
    list(const list<T>& lt)
	{
		CreateHead();
		for (const auto& e : lt)
		{
			push_back(e);
		}
	}

<2> operator=

  • 利用拷贝构造和swap完成operator=
	list<T>& operator=(list<T> lt)
	{
		swap(_head, lt._head);
		return *this;
	}

<3> 利用iterator进行构造

  • 这里不直接使用iterator作为参数类型,而是使用模板参数,是为了支持所有类型的迭代器
	template<class Iterator>
	list(Iterator first, Iterator end)
	{
		CreateHead();
		while (first != end)
		{
			push_back(*first++);
		}
	}

五:析构和size

<1>: 析构函数

  • 析构函数是先通过clear释放所有的节点,再释放哨兵位的头,完成析构
	void clear()
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);//erase会返回下一个节点
		}
	}
	~list()
	{
		clear();
		delete _head;
		_head = nullptr;
	}

<2>:size

  • 遍历链表进行计算节点个数,时间复杂度O(n),不推荐频繁调用
	size_t size()
	{
		size_t sz;
		iterator it = begin();
		while (it != end())
		{
			sz++;
			it++;
		}
		return sz;
	}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:52:38  更:2022-09-21 00:53:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 21:21:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码