Python数据结构之链表(进阶)

承接上文(Python数据结构之链表),在初步学习了链表的有关概念和方法之后,我们是时候了解一下链表的一系列标准/参考写法了。

下面,本文将从C++ STL list的源码出发,领略算法和数据结构大师们对于链表的理解和操作,并在最后尝试使用Python模仿其一二。阅读本文可能需要读者知道一些C++的基本语法,不过不懂也没关系,看中文或者看注释或者直接看最后的Python代码即可。

一、list概述

在STL中,list维护的是非连续空间,这意味着它每次插入或删除一个元素,就配置或释放一个元素的空间。因此,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<T>*)。这里和我们当初写的代码几乎一模一样。

三、list的迭代器

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;
    }
}

四、list的数据结构

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的构造与内存管理

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;
}

六、list的元素操作

终于到了本文最重要的一章了,在这一章里,我们将一窥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)
    }
}

可以结合以下图片来理解:

Alt

其实,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]);
}

七、Python语言模仿

这里先留空,等以后技术成熟了再补。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值