数据结构 | 第3章 列表(上)

162 阅读5分钟

第3章 列表

3.1 从向量到列表

向量:“循秩访问”(call-by-rank)

列表:“循位置访问” (call-by-position),亦称作“循链接访问”(call-by-link)

向量中的秩同时对应于逻辑和物理次序,而位置仅对应于逻辑次序。

二者之间的差异,表面上体现于对外的操作方式,但根源则在于其内部存储方式的不同。列表(list)结构尽管也要求各元素在逻辑上具有线性次序,但对其物理地址却未作任何限制——此即所谓 “动态存储”策略。具体地,在其生命期内,此类数据结构将随着内部数据的需要,相应地分配或回收局部的数据空间。采用动态存储策略,至少可以大大降低动态操作的成本(如链表),但相应的又不得不舍弃原静态存储策略中循秩访问的方式,从而造成静态操作性能的下降(如链表的访问O(n))。

与向量一样,列表也是由具有线性逻辑次序的一组元素构成的集合: L = { a0a_0, a1a_1, ..., an1a_{n-1}}

列表是链表结构的一般化推广,其中的元素称作节点(node),分别由特定的位置或链接指代。

3.2 接口

分列表节点类与列表类。

列表节点(ListNode)ADT接口:

image-20220623111152864.png

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接口:

image-20220623143026996.png

image-20220623143045411.png

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 天,点击查看活动详情