• 从零开始写STL-容器-list


    从零开始写STL-容器-list

    • List 是STL 中的链表容器,今天我们将通过阅读和实现list源码来解决一下问题:
    • List内部的内存结构是如何实现的?
    • 为什么List的插入复杂度为O(1)?
    • 为什么List的size()函数复杂度为O(n)?

    list 容器的幕后英雄 - list 结点

    作为一个链表首先要维护数据(模板元素的实例内容),指向前一个节点的指针 和 指向后一个节点的指针。List 的 结点作为list 容器中 元素内容 和 容器组织逻辑的一个中间层。

    • 为什么不能搞成单向链表?
      如果单向链表的话,请您试想一下要删除迭代器pos指向的元素应该怎么操作?我们必须知道前驱后继才能正确进行删除
    for(auto it = begin(); it != end(); it++)
    {
    	if(it->next == pos)
    	{
    		//...逻辑代码
    	}
    }
    

    这样的单向链表每次寻找一个节点的前驱 后继 都要经过最坏复杂度为O(n)的查询,所有应该实现为双向链表。

    list 容器的 迭代器

    • 维护一个指向list_node 的指针
    	template<class T>
    	struct list_iterator : public bidirectional_iterator<T>
    	{}//继承bidirectional_iterator类 便于类型萃取 和 特定算法应用
    

    list 容器的 逻辑结构

    • 环状链表
      node 表示尾部的一个空白结点,其next 指向list的头节点, pre指向list的尾节点

    为什么这样设计?
    首先list作为双向链表需要提供向前和向后的迭代能力,这样设计可以在O1的时间获得首尾元素,而且可以避免链表为空时的边界检查(只需要看一下node->pre == node,则为空)

    		node_ptr get_node()
    		{
    			return data_allocator.allocate(1);
    		}
    		node_ptr new_node(const T& x)
    		{
    			node_ptr p = get_node();
    			construct(&p->data, x);
    			p->next = p->pre = nullptr;
    			return p;
    		}
    

    构造函数

    		list()
    		{
    			//注意 typedef  std::allocator<list_node<T>> Data_allocator;
    			node.ptr = data_allocator.allocate(1);
    			node.ptr->next = node.ptr->pre = node.ptr;
    		}
    		list(const self& rhs) :list(rhs.begin(), rhs.end())
    		{
    	
    		}
    		list(std::initializer_list<T> li):list(li.begin(),li.end())
    		{
    
    		}
    		template<class c>
    		list(const c l,const c r)
    		{
    			node.ptr = data_allocator.allocate(1);
    			node.ptr->next = node.ptr->pre = node.ptr;
    			for (auto it = l; it != r; it++)
    			{
    				push_back((*it));//逐个插入到list的末端
    			}
    		}
    		list(size_type n, const T& val)
    		{
    			node.ptr = data_allocator.allocate(1);//初始化node
    			node.ptr->next = node.ptr->pre = node.ptr;
    			while (n--)
    				push_back(val);
    		}
    

    析构函数

    遍历列表 销毁数据实例

    		~list()
    		{
    			if (!empty())
    			{
    				for (iterator it = begin(); it != end(); )
    				{
    					data_allocator.destroy(&it.ptr->data);
    					it++;
    				}
    			}
    		}
    

    Modify

    • 注意指针操作的先后顺序
    		void push_front(const T& x)
    		{
    			node_ptr p = new_node(x);
    			node.ptr->next->pre = p;
    			p->next = node.ptr->next;
    			node.ptr->next = p;
    			p->pre = node.ptr;
    		}
    		void push_back(const T& x)
    		{
    			node_ptr p = new_node(x);
    			node.ptr->pre->next = p;
    			p->pre = node.ptr->pre;
    			p->next = node.ptr;
    			node.ptr->pre = p;
    		}
    		void pop_front()
    		{
    			node_ptr tmp = node.ptr->next;
    			node.ptr->next = node.ptr->next->next;
    			node.ptr->next->pre = node.ptr;
    			data_allocator.deallocate(tmp,sizeof(list_node));
    		}
    		void pop_back()
    		{
    			node_ptr tmp = node.ptr->pre;
    			node.ptr->pre = tmp->pre;
    			tmp->pre->next = node.ptr;
    			data_allocator.deallocate(tmp, sizeof(list_node));
    		}
    		iterator erase(iterator pos)
    		{
    			pos.ptr->pre->next = pos.ptr->next;
    			pos.ptr->next->pre = pos.ptr->pre;// 前驱 后继 结点的指针操作
    			node_ptr tmp = pos.ptr->next;
    			destroy(&pos.ptr->data);//销毁
    			data_allocator.deallocate(pos.ptr,sizeof(list_node));// 回收内存
    			return iterator(tmp);
    		}
    		iterator erase(iterator first, iterator last)
    		{
    			first.ptr->pre->next = last.ptr;
    			last.ptr->pre = first.ptr->pre;
    			for (auto it = first; it != last; it++)
    				destroy(&it.ptr->data);
    			return first;
    		}
    		//The list container is extended by inserting new elements before the element at position.
    		iterator insert(iterator pos, const T& x)
    		{
    			node_ptr p = new_node(x);
    			pos.ptr->pre->next = p;
    			p->pre = pos.ptr->pre;
    			p->next = pos.ptr;
    			pos.ptr->pre = p;
    			return pos;
    		}
    
    		void insert(iterator pos, size_type sz, const T& x)
    		{
    			while (sz--)
    				insert(pos, x);
    		}
    

    Splice 函数

    		/* 将 first到last的元素移动到 pos 之前 */
    		void transfer(iterator pos, iterator first, iterator last)
    		{
    			if (pos != last)
    			{
    				last.ptr->pre->next = pos.ptr;
    				first.ptr->pre->next = last.ptr;
    				pos.ptr->pre->next = first.ptr;
    				auto tmp = pos.ptr->pre;
    				pos.ptr->pre = last.ptr->pre;
    				last.ptr->pre = first.ptr->pre;
    				first.ptr->pre = tmp;
    			}
    		}
    		void splice(iterator pos, list<T>& rhs)
    		{
    			if (*this != rhs)
    				transfer(pos, rhs.begin(), rhs.end());
    		}
    		void splice(iterator pos, list<T>& rhs, iterator first, iterator last)
    		{
    			if (*this != rhs)
    			{
    				transfer(pos, first, last);
    			}
    		}
    		void splice(iterator pos, list<T>& rhs, iterator it)
    		{
    			it.ptr->pre->next = it.ptr->next;
    			it.ptr->next->pre = it.ptr->pre;
    			pos.ptr->pre->next = it.ptr;
    			it.ptr->pre = pos.ptr->pre;
    			pos.ptr->pre = it.ptr;
    			it.ptr->next = pos.ptr;
    		}
    

    merge 函数

    注意链表的merge函数 和算法库中含义有所不同,在这里会将参数链表全部合并进来(也就是说在调用完这个函数之后,other 参数应该是空)

    		void merge(list<T> &other)
    		{
    			auto p1 = begin(), p2 = other.begin();
    			while (p1 != end() && p2 != other.end())
    			{
    				if (*p1 < *p2)
    					p1++;
    				else if (*p1 >= *p2)
    				{
    					auto tmp = p2;// 注意保存迭代器下一个位置,如果不保存,直接再调用splice之后p2++会怎样?首先p2!=other.end()这个条件永远不会触发,因为p2已经到了this的链表中
    					tmp++;
    					splice(p1, other, p2);
    					p2 = tmp;
    				}
    			}
    			if (!other.empty()) {
    				splice(end(), other);
    			}
    		}
    		
    

    list 归并排序的非递归形式(难点)

    非递归形式的 归并排序,因为List并不支持随机存取迭代器,sort不能用于排序list。
    归并的思路如下:先建立64个桶,然后从前向后遍历,每次从要排序的list头取出一个元素插入桶中,第一个桶要存的元素最多为1个,第二个桶要存的最多为2个,第K个桶最多为2^K个元素。在插入元素的时候从前向后插入,如果到达当前桶的上限就向后归并。
    具体的算法实现极为巧妙!
    个人感觉参考了位运算的进位性质

    		void sort()
    		{
    			if (size() <= 1)
    			{
    				return;
    			}
    			self carry;
    			self counter[64];
    			int fill = 0;
    			while (!empty())
    			{
    				carry.splice(carry.begin(), *this, begin());
    				int i = 0;
    				while (i < fill && !counter[i].empty())
    				{
     					counter[i].merge(carry);
    					carry.swap(counter[i++]);
    				}
    				carry.swap(counter[i]);
    				if (i == fill)
    				{
    					++fill;
    				}
    			}
    			for (int i = 1; i < fill; i++)
    			{
    				counter[i].merge(counter[i - 1]);
    			}
    			swap(counter[fill - 1]);
    		}
    
  • 相关阅读:
    SQL 数据库中将某表中的一列数据拆分作为查询条件
    SQL数据库导入数据时提示未在本地计算机上注册“Microsoft.ACE.OLEDB.12.0”提供程序。 (System.Data)
    SQL常用内置函数
    SQL常用语句
    关于网页中鼠标双击文字选中设置
    SQL数据库查询列的类型及长度
    ASP. NET MVC项目 使用iTextSharp将网页代码生成PDF文件
    eslint-config-airbnb vs prettier vs standard
    windows批处理(bat脚本)
    python日志库loguru
  • 原文地址:https://www.cnblogs.com/joeylee97/p/8549835.html
Copyright © 2020-2023  润新知