从静态到动态
根据是否修改数据结构,操作大致分为两类:
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*//定义类型
template //简洁起见,完全开放而不过度封装
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 class List
{
private: int _size;//规模
Posi(T) header;Poai(T) trailer://头,尾哨兵
protected: //内部函数
public: //构造函数 析构函数 只读函数 可写接口 便利节后
};
**构造**
temolate void List::init()//初始化,创建列表时统一调用
{
header=new ListNode;//创建头哨兵节点
trailer =new ListNode;//创建尾哨兵节点
header->succ =trailer; header->pred=NULL;//互联
trailer->pred =header;trailer->succ=NULL;
_size=0;
}
秩到位置
可否模仿向量的循秩访问方式?
**重载下标操作符**
template
T List::operator[](Rank r )const//对列表重载操作符[],访问第r个元素
{
Posi(T)p=first();//从首节点出发
while(0succ;//顺数找到第r个节点
return p->date;//目标节点
}//节点的秩即前驱总数
**复杂度**:算术级数 每个节点访问概率是1/n,所以复杂度为O(n)
查找
在p节点的前驱中寻找e,注意对比,命中则停止
template //从外部调用时,0<=n<=rank(p)<_size
Posi(T) List::find(T const& e,int n,Posi(T) p) const
{
while(0pred)->date) return p;//直至命中或范围越界
return NILL;
}
插入与复制
template Posi(T) List::insertBefore(Posi (T) p,T const & e)
{_size++;return p->insertAsPred(e);}//e作为p的前驱插入
template //前插入算法
Posi(T) ListNode::insertAsPred(T const& e)
{
Posi(T)x=new ListNode(e,pred,this);//创建新节点
pred->succ =x;pred=x;return x;//建立链接返回新结点,并对前后节点做调整
}//哨兵的建立使p即使是首节点仍合理
基于复制的构造
template //基本接口 O(n)
void List ::copyNodes(Posi p,int n)
{
init();//创建哨兵并初始化
while(n--)//将自p节点的n项依次作为末节点插入
{insertAsLast(p->data);p=p->succ;}//insertAsLast相当于insertBefore(trailer)!!!!!!!!!!!!!!
}
删除与析构
template //删除合法位置p节点,返回其数值 O(1)
T List::remove (Posi(T))
{
T e=p->data;
p->pred->succ=p->succ;
p->succ->pred=p->pred;
deleta p; _size--; return e;
}
析构把对外可见的节点删除,再删除哨兵
template List ::~List()//列表析构
{clear ();deleta header;delete trailer;}//清空列表,释放头尾哨兵
template int List ::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 int List ::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 //在有序列表节点p的n个前驱中,返回不大于e的最后者
Posi (T) List::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模型对应循秩访问方式
图灵机模型对应循位置访问