本文是为了加深自己对线性表的印象。附有复杂度的分析,数组实现和指针实现的优缺点等。
一、线性表
template <typename E> class List{ private: void operator=(const List&){} //protect assignment why? List(const List&) {}//protect copy constructor public: List(){} virtual ~List(){} virtual void clear()=0;//clear contents from the lists,to make it empty virtual void insert(const E& item)=0; virtual void append(const E& item)=0;//add a element in the end of the list virtual E remove()=0;//remove the current element virtual void moveToStart()=0; virtual void moveToEnd()=0; virtual void prev()=0; virtual void next()=0; virtual int length() const=0; virtual int currPos() const=0; virtual void moveToPos(int pos)=0; virtual const E& getValue() const=0; }
二、数组实现线性表
#include "List.h" template <typename E> class AList:public List<E>{ private: int maxSize; int listSize; int curr; E* listArray; public: AList(){} AList(int size){ maxSize=size; listSize=0; curr=0; listArray=new E[maxSize]; } ~AList(){ delete [] listArray;} void clear(){ delete [] listArray; curr=listSize=0; listArray=new E[maxSize]; } bool check(){ if(listSize>maxSize||listSize<0) { cout<<"out of range"<<endl; return true; } } //time complexity:O(n) void insert(const E& item){ listSize++; if(check()) return; for(int cnt=listSize-1;cnt>curr;cnt--){ listArray[cnt]=listArray[cnt-1]; } listArray[curr]=item; return; } //time complexity:O(1) void append(const E&item){ listSize++; if(check()) return; listArray[listSize-1]=item; } //time complexity:O(n) E remove(){ int temp=listArray[cnt]; listSize--; if(check()) return NULL; for(int cnt=curr;cnt<listSize;cnt++){ listArray[cnt]=listArray[cnt+1]; } return temp; } //time complexity:O(1) void moveToStart(){ curr=0; } void moveToEnd(){ curr=listSize; } void next(){ if(curr>=listSize-1) cout<<"out of range"<<endl; curr++; } void prev(){ if(curr<=0) cout<<"out of range"<<endl; curr--; } int length(){ return listSize;} int currPos(){return curr;} void moveToPos(int pos){ if(pos<=0||pos>=listSize) cout<<"out of range"<<endl; curr=pos; } const E& getValue() const{ if(listSize<=0) cout<<"No current element"<<endl; return listArray[curr]; } void print() const{ for (int cnt = 0; cnt < listSize; cnt++) { cout << listArray[cnt] << " "; } cout << endl; } }
三、指针实现线性表
template <typename E> class Link{ public: E element; Link *next; Link(){ next=nullptr; } Link(const E&item){ element=item; next=nullptr; } Link(const E&item,Link* nextval){ element=item; next=nextval; } Link(Link* nextval){ next=nextval; } } template <typename E> class LList:public List<E>{ private: Link<E>* head; Link<E>* tail; Link<E>* curr; int size; void init(){ curr=tail=head=new Link<E>; cnt=0; } void removeall(){ while(head!=nullptr){ curr=head; head=head->next; delete curr; } } public: LList(){init();} LList(int length){ init(); cnt=length; int temp=cnt-1; while(temp--){ append(); } } void clear(){ removeall(); init(); } void insert(const E&item){ Link<E>* temp=new Link<E>(item,curr->next); curr->next=temp; if(tail==curr) tail=temp; size++; } void append(){ tail->next=new Link<E>(0,nullptr); tail=tail->next; cnt++; } void append(const E&item){ tail=tail->next=new Link<E>(item,nullptr); cnt++; } E remove(){//remove curr->next assert(curr!=tail,"No,element"); E temp=curr->next->element; if(curr->next==tail){ curr->next=nullptr; delete tail; tail=curr; cnt--; } else{ Link<E>* ltemp=curr->next; curr->next=curr->next->next; delete ltemp; cnt--; } return temp; } void moveToStart(){ curr=head; } void moveToEnd(){ curr=tail; } void prev(){ if(curr==head) return; Link<E>* temp=head; while(temp->next!=curr) temp=temp->next; curr=temp; } void next(){ if(curr==tail) return; curr=curr->next; } int length() const{ return size; } int currPos() const{ Link<E>* temp=head; int cnt=0; while(temp!=curr){ temp=temp->next; cnt++; return cnt; } void moveToPos(int pos){ Link<E>* temp=head; while(pos--){ temp=temp->next; } curr=temp; } const E& getValue() const{ return curr->element; } }
四、两种实现方式各自的优缺点
1.从存储空间上考虑
数组需要 size*n的大小。而链表需要 size*(n+4*cnt),指针本身需要空间存放。
2.从访问速度上考虑
数组访问任一成员的时间复杂度为O(1),而链表为O(n)。
3.从删除或添加成员带来的代价上考虑
数组添加或删除任一成员的时间复杂度为O(n),而链表为O(1)。
4.从可扩展性考虑
数组一旦建立,便不能更改大小,而链表可以随意扩大。
五、从链表衍生出来的其它数据结构
freelist
在链表创建和删除结点时,Link类的程序员能够提供简单而有效的内存管理例程,以代替系统级的存储分配和回收操作符。Link类能管理自己的可利用空间表(freelist),以取代反复调用的new和delete。当需要把一个新元素增加到链表时,先检查freelist是否为空,如果freelist不为空,则可从freelist中取走结点。在每次删除结点时,将其插入freelist中。
template <typename E>class Link{ private: static Link<E>* freelist; public: E element; Link* next; Link(Link* nextval=nullptr){ next=nextptr; } Link(const E&item,Link* nextval=nullptr){ element=item; next=nextptr; } void* operator new(size_t){ if(freelist=nullptr) return ::new Link; Link<E>* temp=freelist; freelist=freelist->next; return temp; } void operator delete(void* ptr){ ((Link<E>*)ptr)->next=freelist; freelist=(Link<E>*)ptr; } } template <typename E> Link<E>* Link<E>::freelist=nullptr;
在重载new 函数中,原来的new函数被现在的 ::new 函数代替了。这表明标准的C++new操作符被调用,而不是重载的new操作,避免死循环。
利用空间表,还能产生额外的功效:
调用100次系统new操作符获得一百个结点远比调用一次new操作(一次获得100个结点)要慢。因此如果程序员需要成千上万个链表结点,可以先创建很多个结点在freelist中,实现程序的加速。