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的模拟实现

本质就是双向带头循环链表,模拟实现的重点是它的迭代器,因为list不是一块连续的空间,不能像string和vector一样通过下标来访问,它的迭代器是iterator是 bidirectional iterator双向迭代器(它不支持随机访问),那应该怎么办呢?---->思路:c++的operator可以解决这个问题(运算符重载),可以重载++,–,* 和 ->


在模拟实现之前,我们先来看个问题:

list和vector的区别与联系

  • 首先list和vector是互补的,vector是一块连续的地址空间,这是优点同样也是缺点,他不适合频繁的头插头删,但是支持随机访问(可以去实现很多操作)
  • vector大部分的编译器扩容一般是在2倍左右,可能会存在部分的空间浪费
  • list支持实时申请内存,减少了内存浪费,但是最主要的是支持O(1)的插入和删除,但是不能支持下标的随机访问(空间不连续)

定义节点

我们知道list是一个双向带头循环的链表,它的节点结构就是一个指向上一个节点的指针,一个指向后一个节点的指针和数据

	template<class T>
	struct __list_node //节点
	{
		__list_node<T>* _prev;//指向前一个节点
		__list_node<T>* _next;//指向后一个节点
		T _date;//数据
		
		__list_node(const T& date=T())//构造函数
			:_prev(nullptr)
			,_next(nullptr)
			,_date(date)
		{}
	};

定义迭代器

	//给定不同的模板参数可以减少冗余的代码
	//如果只有一个模板参数,那么一个const的迭代器,它如果想要实现读取数据的操作是不被允许的
	template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef __list_iterator<T,Ref,Ptr> self;
		typedef __list_node<T> list_node;
		//成员变量
		list_node* _node;
        
		__list_iterator(list_node* node)
			:_node(node)
		{}
		// it2 = it1 浅拷贝
		// 拷贝构造和赋值重载是否需要我们自己实现
		// 析构呢?-> 迭代器是借助节点的指针访问修改链表
		// 节点属于链表,不属于迭代器,所以他不管释放
		// 都不需要自己实现,默认生成的即可
		Ref operator*() //指向内置类型可以用解引用
		{
			return _node->_date;
		}
		Ptr* operator->() 
        //如果迭代器指向的是一个结构,要取每一个成员变量就需要重载->
		//所有类型只要想重载->都是这样实现都会自动省略一个->
        //例子如果是日期类为节点类型,返回的就是Date*所以使用应该是it->->
        
		{
			//&的优先级比->低
			//相当于&(_node->_date);
			return &_node->_date;
		}
		//前置++
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		//后置++
		self operator++(int)
		{
			self temp(*this);
			_node = _node->_next;
			return temp;
		}
		//前置--
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//后置++
		self operator--(int)
		{
			self temp(*this);
			_node = _node->_prev;
			return temp;
		}
		bool operator==(const self& x) const
		{
			return _node == x._node;
		}
		bool operator!=(const self& x) const
		{
			return _node != x._node;
		}
	};
	//这个是反向迭代器,reverse_iterator
	template<class InputIterator, class Ref, class Ptr>
	struct __list_reverse_iterator
	{
		typedef __list_reverse_iterator<InputIterator, Ref, Ptr> self;
		InputIterator node;
		__list_reverse_iterator(InputIterator it)
			:node(it)
		{}
		Ref operator*()
		{
			//首先要知道rbegin它指向的是end()也就是list的头结点,然后将node赋给传进来的迭代器,之前的迭代器已经实现了
			//*和--的重载,所以只需要--在解引用得到的就是值
			InputIterator prev = node;
			return *(--prev);
		}
		//operator*()返回的是list_node*,所以取地址复用即可
		Ptr operator->()
		{
			return &operator*();
		}
		self& operator++()
		{
			--node;
			return *this;
		}
		self operator++(int)
		{
			return node--;
		}
		self& operator--()
		{
			++node;
			return *this;
		}
		self operator--(int)
		{
			return node++;
		}
		bool operator!=(const self& rit) const
		{
			return node != rit.node;
		}
		bool operator==(const self& rit) const
		{
			return node == rit.node;
		}
	};

这里有段代码

//这段代码定义在list类里面(当一个测试函数)
fun()
{
    list_node* node=_head->next;
    iterator it=_head->next;
    *node;  //得到的是首元素的那个节点
    *it;	//得到的是首元素的值
    ++node;	//越界
    ++it;	//到了第二个节点位置
}

这段代码中的node和it都是4byte,list_node*是原生指针,而iterator是一个迭代器对象,对他们使用操作符的结果和意义是完全不同的,这就是类型的作用。

list的常见接口的实现

template<class T>
	class list
	{
		typedef __list_node<T> list_node;
	public:
		typedef __list_iterator<T,T&,T*> iterator;
		//这里的T不能加上const否则在拷贝构造时,用迭代器来构造函数会出错
		//这里是支持读但是不支持写
		typedef __list_iterator<T,const T&,const T*> const_iterator;

		typedef __list_reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef __list_reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
		//构造
		list()
		{
			_head = new list_node();
			_head->_next = _head;
			_head->_prev = _head;
		}
				list(int n, const T& val = T())//改,有现成的不会去匹配模板
		{
			//构造n个T类型的节点
			_head = new list_node();
			for (size_t i = 0; i < n; ++i)
			{
				list_node* newnode = new list_node(val);
				_head->_next = newnode;
				newnode->_prev = _head;
				newnode->_next = _head;
				_head->_prev = newnode;
			}
		}
		list(int n,const T& val= T())//改,有现成的不会去优先匹配模板
		{
			//构造n个T类型的节点
			_head = new list_node();
			for (size_t i = 0; i < n; ++i)
			{
				list_node* newnode = new list_node(val);
				_head->_next = newnode;
				newnode->_prev = _head;
				newnode->_next = _head;
				_head->_prev = newnode;
			}
		}
        //下面两个函数的时候,有时候会出现编译器匹配出错
		//用list<int> 来举例子
		//1的两个参数类型一个是unsigned int,int
		//2的是两个int,会发现下面的更匹配,解决方法实现一个int的版本
		list(size_t n,const T& val= T())//1
		{
			//构造n个T类型的节点
			_head = new list_node();
			for (size_t i = 0; i < n; ++i)
			{
				list_node* newnode = new list_node(val);
				_head->_next = newnode;
				newnode->_prev = _head;
				newnode->_next = _head;
				_head->_prev = newnode;
			}
		}
        //实现用迭代器的构造
		template<class InputIterator>//2
		list(InputIterator first, InputIterator end)
		{
			_head = new list_node();
			_head->_next = _head;
			_head->_prev = _head;
			while (first != end)
			{
				push_back(*first);
				++first;
			}
		}
		//拷贝构造list<int> lt(lt1);
		list(const list<T>& lt)
		{
			//需要实现用迭代器的构造函数
			_head = new list_node();
			_head->_next = _head;
			_head->_prev = _head;
			list<T> tmp(lt.cbegin(), lt.cend());
			std::swap(_head, tmp._head);
		}
		//赋值拷贝--->直接传值传参,然后交换就好
		list<T>& operator=(list<T> it)
		{
			swap(_head, it._head);
			return *this;
		}
		//有关迭代器的接口
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		const_iterator cbegin() const
		{
			return const_iterator(_head->_next);
		}
		const_iterator cend() const
		{
			return const_iterator(_head);
		}
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			//reverse_iterator(begin());这种就是相当于去调用构造函数,而下面就是直接返回
			return reverse_iterator(begin());
		}
		bool empty() const
		{
			return (_head->_next == _head) ? true : false;
		}
		size_t size() const
		{
			//用迭代器实现
			size_t count = 0;
			list_node* start = _head->_next;
			list_node* end = _head;
			while (start != end)
			{
				++count;
				start = start->_next;
			}
			return count;
		}
		T& front()
		{
			//front返回的是第一个位置的数据
			//这里的*是被重载之后的,可以拿到T _date
			return *(begin());
		}
		const T& front() const
		{
			return *(begin());
		}
		T& back()
		{
			return *(--end());
		}
		const T& back() const
		{
			return *(--end());
		}

		void push_back(const T& val)
		{
			insert(end(), val);
			//list_node* newnode = new list_node(val);
			//list_node* prev = _head->_prev;//指向的最后一个元素
			//newnode->_prev = prev;
			//prev->_next = newnode;
			//newnode->_next = _head;
			//_head->_prev = newnode;
		}
		void push_front(const T& val)
		{
			insert(begin(), val);
		}
		void pop_front(const T& val)
		{
			erase(begin());
		}
		void pop_back(const T& val)
		{
			//尾删的时候end()指向的是最后一个元素的下一个位置,也就是头结点
			erase(--end());
		}
		iterator insert(iterator pos,const T& val=T())
		{
			//不会存在迭代器失效问题,因为迭代器指向位置不会发生改变
			list_node* newnode = new list_node(val);
			list_node* cur = pos._node;
			newnode->_next = cur;
			newnode->_prev = cur->_prev;
			cur->_prev->_next = newnode;
			cur->_prev = newnode;
			return iterator(newnode);
		}
		iterator erase(iterator pos)
		{
			//需要判断不能为头结点
			// 这里erase以后,pos是否失效?一定失效
			assert(pos != end());
			list_node* cur = pos._node;
			list_node* next = cur->_next;
			cur->_prev->_next = next;
			next->_prev = cur->_prev;
			delete cur;
			return iterator(next);
		}
		//删除除头结点以外的节点
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				//erase(it++);
				it=erase(it);
			}
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
	private:
		list_node* _head;
	};

list的成员只需要一个头结点就够了

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-15 11:42:04  更:2022-05-15 11:42:19 
 
开发: 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/26 1:29:15-

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