承接上文(Python数据结构之链表),在初步学习了链表的有关概念和方法之后,我们是时候了解一下链表的一系列标准/参考写法了。
下面,本文将从C++ STL list的源码出发,领略算法和数据结构大师们对于链表的理解和操作,并在最后尝试使用Python模仿其一二。阅读本文可能需要读者知道一些C++的基本语法,不过不懂也没关系,看中文或者看注释或者直接看最后的Python代码即可。
在STL中,list维护的是非连续空间,这意味着它每次插入或删除一个元素,就配置或释放一个元素的空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素移除,list永远是常数时间。
首先,由我们上一次写的代码可知,list本身和list的节点是不同的结构,需要分开设计。那我们就由简单的开始,先来看一下STL list 的节点(node)结构:
template <class T>
struct __list_node{
typedef void* void_pointer;
void_pointer prev;
void_pointer next;
T data;
}
显然,这是一个双向链表,其前驱和后继指针的型别都是void*
(其实也可以设为__list_node
)。这里和我们当初写的代码几乎一模一样。
STL中的迭代器有四种操作:递增、递减、取值、成员存取。这在连续空间中可以直接使用普通指针实现,但在list中不行,因此,list需要提供它特有的Bidirectional Iterators(因为要双向)。
以下是list迭代器的设计:
template <class T,class Ref,class Ptr>
struct __list_iterator{
typedef __list_iterator<T,T&,T*> 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_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;}
pointer operator->()const{return &(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 __list_node<T> list_node;
public:
typedef list_node* link_type;
protected:
link_type node;
...
}
如果我们人为地让node指向一个置于尾端的空白节点,node便能符合STL对于“前闭后开”区间的要求,成为last迭代器。这样一来,如下的几个函数都变的很自然:
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;
...
}
于是,list_node_allocator(n)表示配置n个节点空间。以下四个函数,分别用来配置、释放、构造、销毁一个节点:
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();
construct(&p->data);
return p;
}
//析构并释放一个节点
void destory_node(link_type p){
destory(&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;
}
对于插入操作,list提供了一个insert()方法,它首先配置并构造一个节点,然后在尾端进行适当的指针操作,将新节点插入进去:
//在迭代器position所指位置插入一个节点,内容为x
iterator insert(iterator position,const T& x){
link_type tmp=create_node(x);
tmp->next=position.node;
tmp->prev=position.node->prev;
(link_type(position.node->prev))->next=tmp;
position.node->prev=tmp;
return tmp;
}
终于到了本文最重要的一章了,在这一章里,我们将一窥STL list的核心操作。
首先是插入操作:
//插入一个节点作为头节点
void push_front(const T& x){insert(begin(),x);}
//插入一个节点作为尾节点
void push_back(const T& x){insert(end(),x);}
接着是删除操作:
//移除迭代器position所指节点
iterator erase(iterator position){
link_type next_node=link_type(position.node->next);
link_type prev_node=link_type(position.node->prev);
prev_node->next=next_node;
next_node->prev=prev_node;
destroy_node(position.node);
return iterator(next_node);
}
//移除头节点
void pop_front(){erase(begin());}
//移除尾节点
void pop_back(){
iterator tmp=end();
erase(--tmp);
}
//清除所有节点(整个链表)
template <class T,class Alloc>
void list<T,Alloc>::clear(){
link_type cur=(link_type) node->next;
while (cur!=node){
link_type tmp=cur;
cur=(link_type)cur->next;
destory_node(tmp);
}
node->next=node;
node->prev=node;
}
//将数值为value的所有元素移除
template <class T,class Alloc>
void list<T,Alloc>::remove(const T& value){
iterator first=begin();
iterator last=end();
while (first!=last){
iterator next=first;
++next;
if (*first==value) erase(first);
first=next;
}
}
到这边理解起来都不难,和我们之前写的代码也都相似,看代码就能懂。
不过下面介绍的迁移操作可能初次理解起来会有点小障碍:
//将[first,last)内的所有元素移动到position之前
void transfer(iterator position,iterator first,iterator last){
if (position!=last){
//将last的前驱指向position
(*(link_type((*last.node).prev))).next=position.node; //(1)
//first的前驱指向last
(*(link_type((*first.node).prev))).next=last.node; //(2)
//position的前驱指向first
(*(link_type((*position.node).prev))).next=first.node; //(3)
//保留position的前驱
link_type tmp=link_type((*position.node).prev); //(4)
//position以last的前驱为新的前驱
(*position.node).prev=(*last.node).prev; //(5)
//last以first的前驱为新的前驱
(*last.node).prev=(*first.node).prev; //(6)
//first以原先position的前驱为前驱
(*first.node).prev=tmp; //(7)
}
}
可以结合以下图片来理解:
其实,list公开的并非transfer()接口,而是以下的接合操作:
public:
//将x接合于position所指位置之前,要求x不同于*this
void splice(iterator position,list& x){
if (!x.empty())
transfer(position,x.begin(),x.end());
}
//将i所指元素接合于position所指位置之前,position和i可指向同一个list
void splice(iterator position,list&,iterator i){
iterator j=i;
++j;
if (position==i||position==j) return;
transfer(position,i,j);
}
//将[first,last)内的所有元素接合于position所指位置之前
//position和[first,last)可指向同一个list,
//但position不能位于[first,last)之内
void splice(iterator position,list&,iterator first,iterator last){
if (first!=last)
transfer(position,first,last);
}
有了transfer(),我们还可以对list做很多有趣的操作,比如:
//merge()操作,将x合并到*this身上。要求两个list的内容已经递增排好序
template <class T,class Alloc>
void list<T,Alloc>::merge(list<T,Alloc>& x){
iterator first1=begin();
iterator last1=end();
iterator first2=x.begin();
iterator last2=x.end();
while (first1!=last1&&first2!=last2){
if (*first2<*first1){
iterator next=first2;
transfer(first1,first2,++next);
first2=next;
}
else
++first1;
if (first2!=last2) transfer(last1,first2,last2);
}
}
//reverse()操作,将*this的内容逆向重置
template <class T,class Alloc>
void list<T,Alloc>::reverse(){
if (node->next==node||link_type(node->next)->next==node)
return;
iterator first=begin();
++first;
while (first!=end()){
iterator old=first;
++first;
transfer(begin(),old,first);
}
}
//因为STL算法sort()只接受RamdonAccessIterator,因此list只能设计自己的sort()
//本函数采用快排
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]);
}
这里先留空,等以后技术成熟了再补。