标签


链表(list)

2014年08月27日

这篇文章记录了我学习列表找到的一些有用的资料以及edX上Data Structures and Algorithms第三周的笔记.

一些概念

1.list是采用动态存储的典型结构
(1)其中的元素称作节点(node)
(2)各节点通过指针或引用彼此连接,在逻辑上构成一个线性序列
(3)相邻node彼此称作前驱(predecessor)或后继(successor).(前驱或后继若存在,则必然唯一)
(4)没有前驱/后继的唯一节点称作首(first/front)/末(last/rear)节点

2.从秩到位置
(1)向量支持循秩访问(call-by-rank)
(2)根据数据元素的rank,可在O(1)时间内直接确定其物理位置
(3)对list call-by-rank 成本过高(从first/last出发,沿predecessor/successor引用)
(4)改用循位置访问(call-by-position)
(5)利用节点之间的相互引用, 找到特定的节点

3.循位置访问(call-by-position)
(1)列表节点(list node): ADT接口
(列表节点首先需要独立地"封装"实现),设置基本操作接口

pred();             //当前节点前驱节点的位置
succ();             //同上,后继
data();             //当前节点所存数据对象
insertAsPred(e);    //插入前驱节点,存入被引用对象e,返回新节点位置
insertAsSucc(e);    //同上,后继
/*列表节点:LIstNode模板类*/

#define Posi(T) ListNode<T>*            //列表节点位置(ISO C++.0x, template alias)
template<typename T>
struct ListNode{                        //不过度封装,列表节点模板类,以双向链表形式实现
    T data;                             //数值
    Posi(T) pred;                       //前驱
    Posi(T) succ;                       //后继
    ListNode(){}                        //针对header和trailer的构造
    ListNode(T e, Posi(T) p=NULL, Posi(T) s=NULL)
    : data(e),pred(p),succ(s){}         //默认constructor
    Posi(T) insertAsPred(T const & e);  
    Posi(T) insertAsSucc(T const & e);
}
#include "listNode.h"               //引入list节点类

template<typename T> class List {   //list模板类
  private: 
    int _size;
    Posi(T) header; Posi(T) trailer;
    /*
    头/尾哨兵,对外不可见
    firstNode/lastNode, 首元素/末元素对外可见
    头尾一定存在,firstNode和lastNode不一定存在
    头,首,末,尾
    -1, 0,n-1,n (rank)                          

  protected:
    /*内部函数*/

  public:
    /*constructor,destructor,只读接口,可写接口,遍历接口*/
}
/*构造*/

template<typename T> void List<T>::init(){      //初始化,创建列表对象时统一调用
    header  = new ListNode<T>;
    trailer = new ListNode<T>;
    header->succ = trailer; header->pred = NULL;//互联
    trailer->pred = header; trailer->succ= NULL;//互连
    _size = 0;                                  //记录规模
}

无序列表

1.循秩访问 (e.g.L[r])
(1)重载下标操作符

template <typename T>                   //assert 0<= rank <size
T List<T>::operator[](Rank r) const {   //O(r), 效率低下,可偶尔为之,不宜长用
    Posi(T) p = first();                //从首节点出发
    while(r-- > 0) p = p->succ;         //顺数第r个节点即是
    return p->data;                     //目标节点
}//任一节点的秩,亦即其前驱的总数

问题: Rank是什么类型? 是int,回头看vector那一章

2.查找
(1)在节点p(可能是trailer)的n个(真)前驱中,找到等于e的最后者

template <typename T>                                       //从外部调用时,0 <= n <= rank(p) < _size
Posi(T) List<T>::find(T const & e,int n,Posi(T) p) const{   //顺序查找, O(n)
    while(n-- > 0)  //从右向左,逐个将p的前驱与e比对
        if(e == ( p = p->pred )->data ) return p; //神奇
    return NULL;
}//header的存在使得处理更为简洁

find(e,n,p) //查找前驱pred
find(e,p,n) //查找后继succ

//自己更易懂的写法
template <typename T>
Posi(T) List<T>::find(T const & e,int n,Posi(T) p) const{   //顺序查找, O(n)
    while(n--){
    p = p->pred;
    if(e == p->data ) 
        return p; 
    }

    return NULL;
}//header的存在使得处理更为简洁

3.插入与复制

/*insert*/

template <typename T> Posi(T) List<T>::insertBefore(Posi(T) p,T const & e){
    _size++;
    return p->insertAsPred(e);
}

template <typename T>                           //前插入算法(后插入完全对称)
Posi(T) ListNode<T>::insertAsPred(T const & e){ //O(1)
    Posi(T) x = new ListNode(e,pred,this);      //创建(耗时100倍(?))
    pred->succ = x;                             //建立连接
    pred = x;
    return x;                                   //返回新节点的位置
}

//即便当前node是first node,它的pred依然存在, 是header
/*construction based on copy*/

template <typename T>                      //基本接口
void List<T>::copyNodes(Posi(T) p,int n){  //O(n)
    init();                                //创建header/trailer 
    while (n--){                           //将起自p的n项依次作为末节点插入
        insertAsLast(p->data);             //实际上就是insertBefore(trailer)
        p = succ;
    }
}

4.删除

/*remove node*/

template <typename T>       
T List<T>::remove(Posi(T) p){   //删除合法位置p处节点,返回其数值,O(1)
    T e = p->data;              //备份待删除节点数值(假设类型T可直接赋值)
    p->pred->succ = p->succ;
    p->succ->pred = p->pred;
    delete p;
    _size--;
    return e;                   //返回备份数值
}

5.析构 (destruct)

/*list dectructor*/

template <typename T>
List<T>::~List(){   
    clear();        //清空列表
    delete header;  //释放header
    delete trailer; //释放trailer
}

template <typename T> int List<T>::clear(){
    int oldSize = _size;
    while(_size>0){
        remove(header->succ);
    }
    return oldSize;
}//O(n)

6.唯一化(删除可能存在的重复元素)

/*deduplicate*/

template <typename T> int List<T>::deduplicate(){   //剔除无序列表中的重复节点
    if(_size<2) return 0;               //平凡列表自然无重复
    int oldSize = _size;
    Posi(T) p = first();   Rank r = 1;  //p从首节点起

    while( (p=p->succ) != trailer){     //依次直到末节点
        Posi(T) q = find(p->data,r,p);  //r是真前驱的数目
        q? remove(q): r++;              //若存在,删除;不可remove(p)
    }//assert(断言):循环过程中的任意时刻,p的所有pred互不相同

    return oldSize - _size;             //返回列表规模变化量
}//正确性及效率分析的方法与结论,与Vector::deduplicate()相同

有序列表(sorted list)

1.唯一化(uniquify)(和deduplicate差不多?)

/*uniquify*/

template <typename T>
int List<T>::uniquify(){        //成批删除重复元素
    int oldSize = _size;
    ListNodePosi(T) p = first();//各区段起点
    ListNodePosi(T) q;          //起点后继

    while( (q=p->succ) != trailer ){
        if(p->data != q->data) p = q;       //若互异,转向下一区段
        else                   remove(q);   //若相同,删除后者
    }

    return oldSize - _size;
}//O(n)

2.查找(search) (list可以二分查找?)

/*search*/
//在有序列表内节点p的n个真前驱中,找到不大于e的最后者(找到<=e的离e最近的那个)

template <typename T>
Posi(T) List<T>::search(T const & e,int n,Posi(T) p) const {
    while(n--){     //从右向左
        if( ( ( p=p->pred )->data ) <= e) break;
    }

    return p;
}//与无序列表的find没多大差异,最好O(1),最坏O(n),等概率平均时间O(n),正比于区间宽度

//问题在于list的循位置访问(call-by-position,就是得找pred/succ)
//与向量的循秩访问(call-by-rank,就是直接找[i]?(这里英文怎么说))有本质的差别
//RAM模型call-by-rank,找到对应的寄存器,读取数值,O(1)
//图灵机模型call-by-position,纸带长度无限,只能左右移动

//Vector rank
//List   posi

选择排序(selection sort)

/*selection sort*/
//对列表中起始于位置p的连续n个元素做选择排序,valid(p) && rank(p) + n <= size

tamplate <typename T> void List<T>::selectionSort(Posi(T) p,int n){
    //待排区间(head,tail)
    Posi(T) head = p->pred;
    Posi(T) tail = p;
    for(int i=0; i<n; i++){     //head/tail可能是header/trailer.
        tail = tail->succ;      //(tail到trailer的succ了怎么办? 岂不是null了?)    
        /*改进方法:
        if(tail->succ != NULL){
            tail = tail->succ;
        }
        */
    }

    while(n>1){
        insertBefore(tail,remove( selectMax(head->succ,n) ));//insert要new,remove要delete
        tail = tail->pred;                                   //是通常基本操作的100倍
        n--;                                                 //改进(1):直接修改reference
    }                                                        //改进(2):修改succ/pred
}
/*selectMax*/

template <typename T>
Posi(T) List<T>::selectMax(Posi(T) p, int n){
    Posi(T) max = p;

    for(Posi(T) cur=p;n>1;n--){
        if( !lt((cur = cur->succ)->data,max->data) ) //not less than
            max = cur;                               //painter's algorithm
    }                                                //这里有一个trick, 要用>=,不用>
                                                     //selectMax()中对于多个相等的最大元素,选取其中位置最靠后者
    return max;
}
  1. 分析: (1)复杂度O(n^2) (2)但是元素移动操作远少于起泡排序 (3)O(n^2)来自于元素比较 (4)第10章selectMax()可以在O(logn)而不是O(n)时间内完成

插入排序 (Insertion Sort)

1.将序列看成两部分
(1)sorted + unsorted

2.我的想法, 插入只需要对比O(logn)次,(binary compare?); 但是,在list里没办法call-by-rank

3.实现

/*Insertion Sort*/
//对列表中起始于位置p的连续n个元素做插入排序

template<typename T>
void List<T>::insertSort(Posi(T) p,int n){
    for(int r=0;r<n;r++){       //r是已排序的元素个数
        insertAfter( search(p->data,r,p),p->data );     
        p = p->succ;
        remove(p->pred);        //转向下一节点
    }//O(r+1)
}//仅用O(1)辅助空间,就地算法(in-place algorithm)

4.性能分析
(1)最好情况,几乎完全有序,O(n);最坏情况,O(n^2); 但平均也是O(n^2)
(2)selection所有情况都是O(n^2),没有最好最坏

5.后向分析法(backward analysis)
[0+1+...+(n-1)]/2+1 = O(n^2)

6.逆序对 (inversion)
(1)左大右小,构成inversion
(2)inversion记录在右(小)的那个元素
(3)逆序对数量可能n^2
(4)统计每一个元素所对应的inversion有多少个,累计能得到整个序列所含inversion的数目
(5)p所对应的inversion有多少个,就要经过多少次比较才能抵达最终插入位置
(6)最好情况,逆序对总数0,复杂度O(0+n)
(7)最坏情况,逆序对总数n^2,复杂度O(n^2+n)=O(n^2)

补充知识

1.ADT(abstract data type): 抽象数据类型
http://www.cnblogs.com/linux-sir/archive/2012/08/15/2640175.html