第3章 列表
3.1 从向量到列表
向量:“循秩访问”(call-by-rank)
列表:“循位置访问” (call-by-position),亦称作“循链接访问”(call-by-link)
向量中的秩同时对应于逻辑和物理次序,而位置仅对应于逻辑次序。
二者之间的差异,表面上体现于对外的操作方式,但根源则在于其内部存储方式的不同。列表(list)结构尽管也要求各元素在逻辑上具有线性次序,但对其物理地址却未作任何限制——此即所谓 “动态存储”策略。具体地,在其生命期内,此类数据结构将随着内部数据的需要,相应地分配或回收局部的数据空间。采用动态存储策略,至少可以大大降低动态操作的成本(如链表),但相应的又不得不舍弃原静态存储策略中循秩访问的方式,从而造成静态操作性能的下降(如链表的访问O(n))。
与向量一样,列表也是由具有线性逻辑次序的一组元素构成的集合: L = { , , ..., }
列表是链表结构的一般化推广,其中的元素称作节点(node),分别由特定的位置或链接指代。
3.2 接口
分列表节点类与列表类。
列表节点(ListNode)ADT接口:
ListNode模板类:
0001 typedef int Rank; //秩
0002 #define ListNodePosi(T) ListNode<T>*; //列表节点位置
0003 template <typename T> struct ListNode { //列表节点模板类(以双向链表形式实现)
0004 // 成员
0005 T data; ListNodePosi<T> pred; ListNodePosi<T> succ; //数值、前驱、后继
0006 // 构造函数
0007 ListNode() {} //针对header和trailer的构造
0008 ListNode ( T e, ListNodePosi<T> p = NULL, ListNodePosi<T> s = NULL )
0009 : data ( e ), pred ( p ), succ ( s ) {} //默认构造器
0010 // 操作接口
0011 ListNodePosi<T> insertAsPred ( T const& e ); //紧靠当前节点之前插入新节点
0012 ListNodePosi<T> insertAsSucc ( T const& e ); //紧随当前节点之后插入新节点
0013 };
列表(List)ADT接口:
List模板类:
0001 #include "listNode.h" //引入列表节点类(代码3.2)
0002
0003 template <typename T> class List { //列表模板类
0004
0005 private:
0006 int _size; ListNodePosi<T> header; ListNodePosi<T> trailer; //规模、头哨兵、尾哨兵
0007
0008 protected:
0009 void init(); //列表创建时的初始化
0010 int clear(); //清除所有节点
0011 void copyNodes ( ListNodePosi<T>, int ); //复制列表中自位置p起的n项
0012 ListNodePosi<T> merge ( ListNodePosi<T>, int, List<T> &, ListNodePosi<T>, int ); //归并
0013 void mergeSort ( ListNodePosi<T> &, int ); //对从p开始连续的n个节点归并排序
0014 void selectionSort ( ListNodePosi<T>, int ); //对从p开始连续的n个节点选择排序
0015 void insertionSort ( ListNodePosi<T>, int ); //对从p开始连续的n个节点插入排序
0016 void radixSort(ListNodePosi<T>, int); //对从p开始连续的n个节点基数排序
0017
0018 public:
0019 // 构造函数
0020 List() { init(); } //默认
0021 List ( List<T> const& L ); //整体复制列表L
0022 List ( List<T> const& L, Rank r, int n ); //复制列表L中自第r项起的n项
0023 List ( ListNodePosi<T> p, int n ); //复制列表中自位置p起的n项
0024 // 析构函数
0025 ~List(); //释放(包含头、尾哨兵在内的)所有节点
0026 // 只读访问接口
0027 Rank size() const { return _size; } //规模
0028 bool empty() const { return _size <= 0; } //判空
0029 T& operator[] ( Rank r ) const; //重载,支持循秩访问(效率低)
0030 ListNodePosi<T> first() const { return header->succ; } //首节点位置
0031 ListNodePosi<T> last() const { return trailer->pred; } //末节点位置
0032 bool valid ( ListNodePosi<T> p ) //判断位置p是否对外合法
0033 { return p && ( trailer != p ) && ( header != p ); } //将头、尾节点等同于NULL
0034 ListNodePosi<T> find ( T const& e ) const //无序列表查找
0035 { return find ( e, _size, trailer ); }
0036 ListNodePosi<T> find ( T const& e, int n, ListNodePosi<T> p ) const; //无序区间查找
0037 ListNodePosi<T> search ( T const& e ) const //有序列表查找
0038 { return search ( e, _size, trailer ); }
0039 ListNodePosi<T> search ( T const& e, int n, ListNodePosi<T> p ) const; //有序区间查找
0040 ListNodePosi<T> selectMax ( ListNodePosi<T> p, int n ); //在p及其n-1个后继中选出最大者
0041 ListNodePosi<T> selectMax() { return selectMax ( header->succ, _size ); } //整体最大者
0042 // 可写访问接口
0043 ListNodePosi<T> insertAsFirst ( T const& e ); //将e当作首节点插入
0044 ListNodePosi<T> insertAsLast ( T const& e ); //将e当作末节点插入
0045 ListNodePosi<T> insert ( ListNodePosi<T> p, T const& e ); //将e当作p的后继插入
0046 ListNodePosi<T> insert ( T const& e, ListNodePosi<T> p ); //将e当作p的前驱插入
0047 T remove ( ListNodePosi<T> p ); //删除合法位置p处的节点,返回被删除节点
0048 void merge ( List<T> & L ) { merge ( header->succ, _size, L, L.header->succ, L._size ); } //全列表归并
0049 void sort ( ListNodePosi<T> p, int n ); //列表区间排序
0050 void sort() { sort ( first(), _size ); } //列表整体排序
0051 int deduplicate(); //无序去重
0052 int uniquify(); //有序去重
0053 void reverse(); //前后倒置(习题)
0054 // 遍历
0055 void traverse ( void (* ) ( T& ) ); //遍历,依次实施visit操作(函数指针,只读或局部性修改)
0056 template <typename VST> //操作器
0057 void traverse ( VST& ); //遍历,依次实施visit操作(函数对象,可全局性修改)
0058 }; //List
“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情”