一、基本概念
列表的核心是一个双向链表,双向开口,可在头、尾两个方向进行元素的插入和删除
- Vector是连续的容器,而List是非连续的容器,即Vector将元素存储在连续的容器中,而List存储在不连续的容器中
- Vector的插入和删除是比较麻烦的,需要大量的时间来移动元素,而链表克服了这个问题
- 在List中遍历速度很慢,因为List元素是按顺序访问的,而Vector支持随机访问。
二、接口与实现
1、接口
操作接口 | 功能 |
---|---|
pred() | 当前节点前驱节点的位置 |
succ() | 当前节点后继节点的位置 |
data() | 当前节点所存数据对象 |
insertAsPred(e) | 插入前驱结点,存入被引用对象e,返回新节点位置 |
insertAsSucc(e) | 插入后继节点,存入被引用对象e,返回新节点位置 |
2、实现
- 查找
template <typename T> //在无序列表内节点p(可能是trailer)的n个(真)前驱中,找到等于e的最后者
ListNodePosi(T) List<T>::find ( T const& e, int n, ListNodePosi(T) p ) const {
while ( 0 < n-- ) //(0 <= n <= rank(p) < _size)对于p的最近的n个前驱,从右向左
if ( e == ( p = p->pred )->data ) return p; //逐个比对,直至命中或范围越界
return NULL; //p越出左边界意味着区间内不含e,查找失败
} //失败时,返回NULL
- 插入
template <typename T> ListNodePosi(T) List<T>::insertAsFirst ( T const& e )
{ _size++; return header->insertAsSucc ( e ); } //e当做首节点插入
template <typename T> ListNodePosi(T) List<T>::insertAsLast ( T const& e )
{ _size++; return trailer->insertAsPred ( e ); } //e当做末节点插入
template <typename T> ListNodePosi(T) List<T>::insertA ( ListNodePosi(T) p, T const& e )
{ _size++; return p->insertAsSucc ( e ); } //e当做p的后继插入(After)
template <typename T> ListNodePosi(T) List<T>::insertB ( ListNodePosi(T) p, T const& e )
{ _size++; return p->insertAsPred ( e ); } //e当做p的前驱插入(Before)
- 删除
template <typename T> T List<T>::remove ( ListNodePosi(T) p ) { //删除合法节点p,返回其数值
T e = p->data; //备份待删除节点的数值(假定T类型可直接赋值)
p->pred->succ = p->succ; p->succ->pred = p->pred; //后继、前驱
delete p; _size--; //释放节点,更新规模
return e; //返回备份的数值
}
- 唯一化
template <typename T> int List<T>::deduplicate() { //剔除无序列表中癿重复节点
if ( _size < 2 ) return 0; //平凡列表自然无重复
int oldSize = _size; //记录原规模
ListNodePosi(T) p = header; Rank r = 0; //p从首节点开始
while ( trailer != ( p = p->succ ) ) { //依次直到末节点
ListNodePosi(T) q = find ( p->data, r, p ); //在p的r个(真)前驱中查找雷同者
q ? remove ( q ) : r++; //若的确存在,则删除;否则秩加一
} //assert: 循环过程中的任意时刻,p的所有前驱互不相同
return oldSize - _size; //列表规模变化量,即被删除元素总数
}
三、排序
1、插入排序
始终将整个序列视为并切分为两部分:有序的前缀,无序的后缀;通过迭代,反复地将后缀的首元素转移至前缀中。
template <typename T> //列表的插入排序算法:对起始于位置p的n个元素排序
void List<T>::insertionSort ( ListNodePosi(T) p, int n ) { //valid(p) && rank(p) + n <= size
for ( int r = 0; r < n; r++ ) { //逐一遍历各节点
insertA ( search ( p->data, r, p ), p->data ); //查找适当的位置并插入
p = p->succ; remove ( p->pred ); //转向下一节点
}
}
/*
为什么不用二分查找?
因为不是数组或向量,不能用二分查找。。。(为什么会有学生问这个问题)
*/
2、选择排序
与插入排序类似,该算法也将序列划分为无序前缀和有序后缀两部分:此外,还要求前缀不大于后缀。如此,每次只需从前缀中选出最大者,并作为最小元素转移至后缀中,即可使有序部分的范围不断扩张。
template <typename T> //列表的选择排序算法:对起始于位置p的n个元素排序
void List<T>::selectionSort ( ListNodePosi(T) p, int n ) { //valid(p) && rank(p) + n <= size
ListNodePosi(T) head = p->pred; ListNodePosi(T) tail = p;
for ( int i = 0; i < n; i++ ) tail = tail->succ; //待排序区间为(head, tail)
while ( 1 < n ) { //在至少还剩两个节点之前,在待排序区间内
ListNodePosi(T) max = selectMax ( head->succ, n ); //找出最大者(歧义时后者优先)
insertB ( tail, remove ( max ) ); //将其移至无序区间末尾(作为有序区间新的首元素)
tail = tail->pred; n--;
}
}
3、归并排序
和向量的归并排序几乎一致
4、游标实现
利用线性数组,以游标方式模拟列表
-elem[]: 对外可见的数据项
-link[]: 数据项之间的引用
link[]中存的是下一个数据在elem[]的下标(相当于succ、next)