接口和實現
從靜態到動態
根據是否修改數據結構,操作大致分爲兩類:
1)靜態:讀取,數據結構的內容和組成一般不變:get, search
2) 動態:寫入,數據結構的局部或者整體改變: insert,remove
與操作方式相對應,數據元素的存儲與組織方式也分爲兩種
1) 靜態:數據空間整體創建或銷燬
數據元素的物理儲存次序和邏輯次序嚴格一致,因此支持高效的靜態操作 (比如向量) getO(1),search O(logn);而寫入最少要O(n),爲了改變動態操作不足 採用動態的結構(列表)
2) 動態: 爲各數據元素動態分配和回收物理空間
邏輯上相鄰的元素記錄彼此的物理空間,在邏輯上形成一個整體,支持高效的動態操作
從向量到列表
L={a,b,c}
列表(List)是典型的動態儲存結構,其中各元素稱爲節點, 各節點通過指針或者引用彼此連接,在邏輯上構成線性序列,除了首末元素外均有前驅後繼
從秩到位置
向量支持循秩訪問 ,V[i]=V+i*s, 如此快的訪問 能否被列表沿用?
列表也是線性結構,的確可以通過秩來訪問,但是循序漸進的 訪問成本高
因此,改爲循位置訪問 即利用節點間的相互引用,找到特定節點
實現
節點作爲列表的基本元素,需要首先實現封裝
**列表節點:ListNode模板類**
#define Posi(T) ListNode<T>*//定義類型
template <typename>//簡潔起見,完全開放而不過度封裝
struct ListNode //列表節點模板類(雙向鏈表形式實現)
{
T date;//數值
Posi(T) pred;//前驅
Posi(T) succ;//後繼
ListNode () {}//針對header和trailer的構造
ListNode(T e,Posi(T) p= NULL,Posi(T) s=NULL)
:date(e),pred(p),succ(s){}//默認構造器
Posi(T) insertAsPred(T const& e);//前插入
Posi(T) insertAsSucc(T const& e);//前插入
};
**列表:List模板類**
include "listNode.h"//引入列表節點類
template <typename T>class List
{
private: int _size;//規模
Posi(T) header;Poai(T) trailer://頭,尾哨兵
protected: //內部函數
public: //構造函數 析構函數 只讀函數 可寫接口 便利節後
};
**構造**
temolate <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;
}
無序列表
秩到位置
可否模仿向量的循秩訪問方式?
**重載下標操作符**
template <typename>
T List<T>::operator[](Rank r )const//對列表重載操作符[],訪問第r個元素
{
Posi(T)p=first();//從首節點出發
while(0<r--) p=p->succ;//順數找到第r個節點
return p->date;//目標節點
}//節點的秩即前驅總數
**複雜度**:算術級數 每個節點訪問概率是1/n,所以複雜度爲O(n)
查找
在p節點的前驅中尋找e,注意對比,命中則停止
template <typename T>//從外部調用時,0<=n<=rank(p)<_size
Posi(T) List<T>::find(T const& e,int n,Posi(T) p) const
{
while(0<n--)
if(e==(p=p->pred)->date) return p;//直至命中或範圍越界
return NILL;
}
插入與複製
template <typemlate T> Posi(T) List<T>::insertBefore(Posi (T) p,T const & e)
{_size++;return p->insertAsPred(e);}//e作爲p的前驅插入
template <typename T> //前插入算法
Posi(T) ListNode<T>::insertAsPred(T const& e)
{
Posi(T)x=new ListNode(e,pred,this);//創建新節點
pred->succ =x;pred=x;return x;//建立鏈接返回新結點,並對前後節點做調整
}//哨兵的建立使p即使是首節點仍合理
基於複製的構造
template <typename T>//基本接口 O(n)
void List <T>::copyNodes(Posi<T> p,int n)
{
init();//創建哨兵並初始化
while(n--)//將自p節點的n項依次作爲末節點插入
{insertAsLast(p->data);p=p->succ;}//insertAsLast相當於insertBefore(trailer)!!!!!!!!!!!!!!
}
刪除與析構
template <typename T>//刪除合法位置p節點,返回其數值 O(1)
T List<T>::remove (Posi(T))
{
T e=p->data;
p->pred->succ=p->succ;
p->succ->pred=p->pred;
deleta p; _size--; return e;
}
析構把對外可見的節點刪除,再刪除哨兵
template <typename T>List <T>::~List()//列表析構
{clear ();deleta header;delete trailer;}//清空列表,釋放頭尾哨兵
template <typename T>int List <T>::clear()
{
int ols_size=_size;
while(o<_size)//反覆刪除節點
remove(header->succ);
return oldSize;
}//O(n),線性正比於列表規模
唯一化
大致分爲三部分:一部分不重複節點,待考察節點,待考察後綴
{
if(_size<2) return 0;//排除平凡列表
int olsSize =_size;//記錄原規模
Posi(T) p=first();Rank r=1;//p從首節點起
while(trailer !=(p=p->succ))
{
Posi(T) q=find(p->data,r,p);
q?remove(q):r++;//存在則刪除,否則秩遞增(因爲有p->succ操作所以非remove(p))
}
}
有序列表
唯一化 構思
有序向量唯一化比無序向量完成更快,那列表呢?
有序向量相同元素彼此相鄰,只保留一個元素,仿照此:p節點與後繼節點對比,相同則remove(q) ,繼續對比知道發現下一個不同節點 ,則將p指向並保留該不同節點
唯一化實現
temolate <typename T> int List <T>::uniquify()
{
if(_size<2) return 0;
int olsSize =_size;
ListNodePosi(T) p=first(); ListNodePosi(T) q;//p爲各區段起點,
while(trailer !=(q=p->succ))//q爲其後繼
if (p->data!=q->data) p=q;//前後節點互異則轉向下一個
else remove(q);//相同則刪除
return oldSize -_size;//返回規模變化量
}//只需遍歷一遍,O(n)
查找
template <typename T>//在有序列表節點p的n個前驅中,返回不大於e的最後者
Posi (T) List<T>::search(T const &e,int n,Posi(T) p) const
{
while(0<=n--)//對於p最近的n個節點,從左到右
if(((p=p->pred)->data)<=e) break;//逐個比較
return p;//直至命中或範圍越界
}//
Vector rank(秩): List posi(位置)
Ram模型對應循秩訪問方式
圖靈機模型對應循位置訪問