【STL】list(六)

相对于vector的连续线性空间,list是非线性非连续空间,虽然会复杂一些,但是每次插入或者删除一个元素,只是配置或释放一个元素空间,而且对任何位置的元素插入或移除,list都是常数操作时间。

【节点结构】

list本身和list的结点是不同结构,需分开设计。
结点结构:

template <class T>
struct __list_node
{
  typedef void* void_pointer;
  void_pointer next;
  void_pointer prev;
  T data;
};

其实就是一个双向链表:
在这里插入图片描述

【迭代器】
  1. list结点不保证在存储空间中连续,所以list不能像vector一样,以普通指针作为迭代器。
  2. list迭代器必须有能力指向list的结点,并有能力进行正确的递增、递减、取值、成员存取操作。
  3. list是一个双向链表,迭代器必须具备前移、后移的能力,所以list提供Bidirectional Iterators
  4. list的插入操作和接合操作都不会造成原有的list迭代器失效。这在vector是不能力的,因为vector的插入操作可能造成内存重新配置而导致原有的迭代器全部失效。

结点与迭代器的关系如图1:
在这里插入图片描述

图1

实现源码:

template<class T, class Ref, class Ptr>
struct __list_iterator 
{
  //STL要求每个迭代器设计都要有 iterator和const_iterator,
  //这里的self声明便于下文使用。
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;
  typedef __list_iterator<T, Ref, Ptr>           self;

  typedef bidirectional_iterator_tag iterator_category;
  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __list_node<T>* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  link_type node; //迭代器内部当然要有一个普通指针,指向list的结点

  __list_iterator(link_type x) : node(x) {}
  __list_iterator() {}
  __list_iterator(const iterator& x) : node(x.node) {}

  bool operator==(const self& x) const { return node == x.node; }
  bool operator!=(const self& x) const { return node != x.node; }
  //对迭代器取值,取得是结点的数据
  reference operator*() const { return (*node).data; }

//迭代器的成员存取运算子的标准做法。
//即如果Class T是个类或者结构体时,->就是
//指向这些类或者结构体的内部元素。
#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  //下文的这些迭代器操作都比较简单
  self& operator++() { 
    node = (link_type)((*node).next);
    return *this;
  }
  self operator++(int) { 
    self tmp = *this;
    ++*this;
    return tmp;
  }
  self& operator--() { 
    node = (link_type)((*node).prev);
    return *this;
  }
  self operator--(int) { 
    self tmp = *this;
    --*this;
    return tmp;
  }
};

数据结构

SGI list不仅是一个双向链表,而且还是一个环状双向链表,所以只需一个指针,便可完整表现整个链表。
相关代码:

template <class T, class Alloc = alloc>
class list
{
  protected:
    typedef void* void_pointer;
    typedef __list_node<T> list_node;
  public:      
  	typedef list_node* link_type;
  	
  protected:
  	link_type node;
  ...	
}

如果让指针node指向刻意置于尾端的一个空白结点,node便能符合STL对于“前闭后开”区间的要求,成为last迭代器,如图2:
在这里插入图片描述

图2

如果node指向尾端,则以下几个函数实现就很简单:

iterator begin() {return (link_type)((*node).next);}
iterator end() {return node;}
bool empty() const {return node->next == node;}
size_type size() 
const{
 size_type result = 0;
 distance(begin, end, result);
 return result;
}

//取头结点的内容(元素值)
reference front() {return *begin();}
//取尾结点的内容(元素值)
reference back() {return *(--end());}

【构造与内存管理】

list缺省使用alloc作为空间配置器,并据此另外定义了一个list_node_allocator,为的是更方便以结点大小为配置单位。

template <class T, class Alloc = alloc>
class list 
{
	protected:
		typedef __list_node<T> list_node;
		//专属之空间配置器,每次配置一个节点大小
		typedef simple_alloc<list_node, Alloc> list_node_allocator;
		...
	protected:
		//配置一个节点并传回
		link_type get_node() { return list_node_allocator::allocate(); }
		//释放一个节点
		void put_node(link_type p) { list_node_allocator::deallocate(p); }
		//产生(配置并构造)一个节点,带有元素值
		link_type create_node(const T& x) 
		{
			link_type p = get_node();
			__STL_TRY 
			{
				construct(&p->data, x);
			}
			__STL_UNWIND(put_node(p));
			return p;
		}
		//销毁(析构并释放)一个节点
		void destroy_node(link_type p) 
		{
			destroy(&p->data);
			put_node(p);
		}
}

list提供了许多constructors,其中一个是default constructor,允许我们不指定任何参数做出一个空的list出来:

public:
  list() { empty_initialize(); }
  
protected:
  void empty_initialize() { 
    node = get_node();
    node->next = node;
    node->prev = node;
  }

效果如图3:
在这里插入图片描述

图3
【元素操作】

简单的元素操作这里就不说了,操作都和循环链表操作方法一样,这里重点说下list内部提供的一个所谓的迁移操作(transfer):将某连续范围的元素迁移到某个特定位置之前。技术上很简单,节点间的指针移动而已。这个操作为其它的复杂操作如splice,sort,merge等提供了良好的基础。
Transfer源码:
[first,last)内的所有元素移动到position之前。

 void transfer(iterator position, iterator first, iterator last) {
    if (position != last) {
      (*(link_type((*last.node).prev))).next = position.node;	//(1)
      (*(link_type((*first.node).prev))).next = last.node;		//(2)
      (*(link_type((*position.node).prev))).next = first.node;  //(3)
      link_type tmp = link_type((*position.node).prev);			//(4)
      (*position.node).prev = (*last.node).prev;				//(5)
      (*last.node).prev = (*first.node).prev; 					//(6)
      (*first.node).prev = tmp;									//(7)
    }
  }

操作如图4:
在这里插入图片描述

图4

上述都是指针的移动操作,通过图中的序号可以很容易看懂。transfer所接受的[first,last)区间和position可以在同一个list,也可以在不同的list。

最后在说下list内部使用的sort算法。list不能使用STL的sort算法,因为STL算法sort()只接受RamdonAccessIterator。

template <class T, class Alloc>
void list<T, Alloc>::sort() {
  if (node->next == node || link_type(node->next)->next == node) return;
  list<T, Alloc> carry;
  list<T, Alloc> 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]);
}

书中说是quick sort,通过算法的实现方式来看其实更像是非递归归并排序。网上也有好多对此进行纠正说明的。对此算法的解读说明我这里也就不列了,大家可参考此篇链接:list::sort()深度解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值