本文最后更新于 976 天前,其中的信息可能已经有所发展或是发生改变。
不同数据结构内部的存储与组织方式各异,其操作接口的使用方式及时空性能也不尽相同。引入列表结构的目的在于弥补向量结构在解决某些应用问题时,在功能及性能方面的不足。二者之间的差异,表面上体现于对外的操作方式,但根源则在于其内部存储方式的不同。
从静态到动态:
数据结构支持的操作,通常无非静态和动态两类:前者仅从中获取信息,后者则会修改数据结构的局部甚至整体。以基于数组实现的向量结构为例,其 size()
和 get()
等静态操作均可在常数时间内完成,而 insert()
和 remove()
等动态操作却都可能需要线性时间。究其原因,在于"各元素物理地址连续"的约定,此即所谓的"静态存储"策略。
基于上述策略,可在 O(1) 的时间内由秩确定向量元素的物理地址,但反过来,在添加(删除)元素之前(之后),又不得不移动 O(n) 个后继元素。可见,尽管如此可使静态操作的效率达到极致,但就动态操作而言,局部的修改可能引起大范围甚至整个数据结构的调整。
列表(list)结构尽管也要求各元素在逻辑上具有线性次序,但对其物理地址却未作任何限制。此即所谓"动态存储"策略。链表(linked list)就是一种典型的动态存储结构。其中的数据,分散为一系列称作节点(node)的单位,节点之间通过指针相互索引和访问。为了引入新节点或删除原有节点, 只需在局部调整少量相关节点之间的指针。这就意味着,采用动态存储策略,至少可以大大降低动态操作的成本。
从秩到位置:
改用以上动态存储策略之后,在提高动态操作效率的同时,却又不得不舍弃原静态存储策略中循秩访问的方式,从而造成静态操作性能的下降。
以采用动态存储策略的链表为例。尽管按照逻辑次序,每个数据元素依然具有秩这一指标,但为了访问秩为 r
的元素,我们只能顺着相邻元素之间的指针,从某一端出发。 逐个扫描各元素,经过 r
步选代后才能确定该元素的物理存储位置。这意味着,原先只需 O(1) 时间的静态操作,此时的复杂度也将线性正比于被访问元素的秩。在最坏情况下等于元素总数 n, 即便在各元素被访问概率相等的情况下,平均而言也需要 O(n) 时间。
对数据结构的访问方式,应与其存储策略相一致。此时,既然继续延用循秩访问的方式已非上策,就应更多地习惯于通过位置,来指代并访问动态存储结构中的数据元素。
与向量中秩的地位与功能类似,列表中的位置也是指代各数据元素的一个标识性指标,借助它可以得到元素的物理存储地址。各元素的位置,通常可表示和实现为联接于元素之间的指针或引用。因此,基于此类结构设计算法时,应更多地借助逻辑上相邻元素之间的位置索引,以实现对目标元素的快速定位和访问,并进而提高算法的整体效率。
列表:
与向量一样,列表也是由具有线性逻辑次序的一组元素构成的集合。列表是链表结构的一般化推广,其中的元素称作节点(node),分别由特定的位置或链接指代。与向量一样,在元素之间也可定义前驱、直接前驱,以及后继、直接后继等关系。相对于任意元素,也有定义对应的前缀、后缀等子集。
ADT 接口
ListNode 模板类
| #include <iostream> |
| #include <cstdlib> |
| using namespace std; |
| |
| typedef int Rank; |
| #define ListNodePosi(T) ListNode<T>* |
| |
| template<typename T> |
| struct ListNode{ |
| |
| T data; |
| ListNodePosi(T) pred; |
| ListNodePosi(T) succ; |
| |
| ListNode(){} |
| ListNode(T e,ListNodePosi(T) p = NULL,ListNodePosi(T) s = NULL) |
| :data(e),pred(p),succ(s){} |
| |
| ListNodePosi(T) insertAsPred(T const &e); |
| ListNodePosi(T) insertAsSucc(T const &e); |
| }; |
每个节点都存有数据对象 data
。为保证叙述简洁,在不致歧义的前担下,将不再区分节点及其对应的 data
对象。此外,每个节点还设有指针 pred
和 succ
,分别指向其前驱和后继。 为了创建一个列表节点对象,只需根据所提供的参数,分别设置节点内部的各个变量。其中前驱、后继节点的位置指针若未子指定,则默认取作 NULL
。
List 模板类
| template <typename T> class List{ |
| private: |
| |
| int _size; |
| ListNodePosi(T) header; |
| ListNodePosi(T) trailer; |
| |
| |
| protected: |
| |
| void init(); |
| void copyNodes ( ListNodePosi(T) p, int n ); |
| void merge (ListNodePosi(T) p, int mid, int n); |
| |
| public: |
| |
| List() { init(); } |
| List ( List<T> const& L); |
| List ( List<T> const& L, Rank r, int n); |
| List ( ListNodePosi(T) p,int n); |
| |
| |
| ~List(); |
| |
| |
| |
| |
| Rank size() const { return _size; } |
| bool empty() const { return !_size; } |
| T &operator[](Rank r) const; |
| ListNodePosi(T) first() const { return header -> succ; } |
| ListNodePosi(T) last() const {return trailer -> pred; } |
| bool valid(ListNodePosi(T) p) |
| { return p && (trailer != p) && (header != p); } |
| ListNodePosi(T) find ( T const& e) const |
| { return find( e, _size, trailer); } |
| ListNodePosi(T) find( T const& e, int n, ListNodePosi(T) p) const; |
| ListNodePosi(T) search( T const& e) const |
| { return search( e, _size, trailer); } |
| ListNodePosi(T) search( T const& e, int n, ListNodePosi(T) p) const; |
| ListNodePosi(T) selectMax (ListNodePosi(T) p, int n); |
| ListNodePosi(T) selectMax () {return selectMax( header -> succ, _size); } |
| |
| |
| ListNodePosi(T) insertAsFirst (T const& e); |
| ListNodePosi(T) insertAsLast (T const& e); |
| ListNodePosi(T) insertA (ListNodePosi(T) p, T const& e); |
| ListNodePosi(T) insertB (ListNodePosi(T) p, T const& e); |
| void clear(); |
| T remove ( ListNodePosi(T) p ); |
| int deduplicate(); |
| int uniquify(); |
| void reverse(); |
| void insertionSort (ListNodePosi(T) p, int n); |
| void selectionSort ( ListNodePosi(T) p, int n ); |
| void mergeSort ( ListNodePosi(T) p, int n ); |
| void mergeLists(ListNodePosi(T) p, int n, List<T>& L, ListNodePosi(T) q, int m); |
| void mergeLists( List<T> & L ) { mergeLists ( header -> succ, _size, L, L.header -> succ, L._size ); } |
| |
| |
| void traverse(void (*)(T &)); |
| template <typename VST> |
| void traverse(VST &); |
| template <typename VST> |
| void traverse( VST &visit , ListNodePosi(T) r, ListNodePosi(T) l); |
| |
| }; |
头、尾节点
List
对象的内部组成及逻辑结构如图所示:
其中私有的头节点(header)和尾节点(trailer)始终存在,但对外并不可见。对外部可见的数据节点如果存在,则其中的第一个和最后一个节点分别称作首节点(first node)和末节点(last node)。
就内部结构而言,头节点紧邻于首节点之前,尾节点紧邻于末节点之后。这类经封装之后从外部不可见的节点,称作哨兵节点(sentinel node)。List
模板类中的 List::valid()
关于合法节点位置的判别准则可见,此处的两个哨兵节点从外部被等效地视作 NULL
。设置哨兵节点之后,对于从外部可见的任一节点而言,其前驱和后继在列表内部都必然存在,故可简化算法的描述与实现。又如,为实现 first()
和 last()
操作,只需直接返回 header -> succ
或 trailer -> pred
。此外更重要地,哨兵节点的引入,也使得相关算法不必再对各种边界退化情况做专门的处理,从而避免出错的可能。
默认构造
创建 List
对象时,默认构造方法将调用统一初始化过程 init()
,在列表内部创建一对头、尾哨兵节点,并适当地设置其前驱、后继指针构成一个双向链表。
| |
| template<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; |
| } |
解释:
- 如图,该链表对外的有效部分初始为空,哨兵节点对外不可见,此后引入新的节点,都将陆续插入到这一对哨兵节点中。
在列表的其它构造方法中,内部变量的初始化过程与此相同,因此都可统一调用 init()
过程。该过程仅涉及常数次基本操作,共需运行常数时间。
由秩到位置的转换
鉴于偶尔可能需要通过秩来指定列表节点,可通过重载操作符 []
提供一个转换接口:
| |
| template <typename T> |
| T& List<T>::operator[] ( Rank r ) const{ |
| ListNodePosi(T) p = first(); |
| while ( 0 < r-- ) p = p -> succ; |
| return p -> data; |
| } |
解释:
- 为将任意指定的秩
r
转换为列表中对应的元素,可从首节点出发,顺着后继指针前进 r
步。
只要秩 r
合法, 该算法的正确性即一目了然。其中每步选代仅需常数时间,故该算法的总体运行时间应为 O(r+1),线性正比于目标节点的秩。相对于向量同类接口的 O(1) 复杂度,列表的这一效率十分低下。
无序列表的顺序查找
列表 ADT
针对整体和区间查找,重载了操作接口 find(e)
和 find(e,p,n)
。其中,前者作为特例,可以直接调用后者。因此,只需实现后一接口:
| |
| |
| template <typename T> |
| ListNodePosi(T) List<T>::find ( T const &e, int n, ListNodePosi(T) p ) const { |
| while (0 < n--) |
| if (e == (p = p -> pred) -> data) return p; |
| return NULL; |
| } |
以上算法的思路及过程,与无序向量的顺序查找算法 Vector : :find()
相仿,故时间复杂度也应是 O(n),线性正比于查找区间的宽度。
有序列表的顺序查找
与有序向量可以借助二分查找不同,尽管有序列表中的节点已经在逻辑上单调。但本质上,其动态的存储策略,使得节点的物理地址与其逻辑次序无关,故无法进行有效的查询。
| |
| |
| template <typename T> |
| ListNodePosi(T) List<T>::search ( T const &e, int n, ListNodePosi(T) p ) const { |
| while (0 < n--) |
| if (((p = p -> pred) -> data) <= e) break; |
| return p; |
| } |
前插入
将新元素 e
作为当前节点的前驱插至列表的过程:
| |
| template <typename T> |
| ListNodePosi(T) ListNode<T>::insertAsPred ( T const &e ){ |
| ListNodePosi(T) x = new ListNode ( e, pred, this ); |
| pred -> succ = x; pred = x; |
| return x; |
| } |
解释:
- 插入新节点之前, 列表局部的当前节点及其前驱如图(a)所示。
- 该算法首先如图(b) 所示创建新节点
new
,构造函数同时将其数据项置为 e
,并令其后继链接 succ
指向当前节点,令其前驱链接 pred
指向当前节点的前驱节点。
- 随后如图(c)所示,使
new
成为当前节点前驱节点的后继,使 new
成为当前节点的前驱(次序不能颠倒)。
- 最终如图(d)所示,经过如此调整,新节点即被顺利地插至列表的这一局部。
注意:
- 列表规模记录的更新由上层调用者负责。
- 得益于头哨兵节点的存在,即便当前节点为列表的首节点,其前驱也如图(a)所示必然存在,故不必另做特殊处理。
后插入
将新元素 e
作为当前节点的后继插至列表的过程:
| |
| template <typename T> |
| ListNodePosi(T) ListNode<T>::insertAsSucc ( T const &e ){ |
| ListNodePosi(T) x = new ListNode ( e, this, succ ); |
| succ -> pred = x; succ = x; |
| return x; |
| } |
插入接口
为将节点插至列表,除了上述两种插入方法,我们可视具体要求的不同,额外提供以下大量接口:
| |
| template <typename T> ListNodePosi(T) List<T>::insertAsFirst ( T const &e ) |
| { _size ++; return header -> insertAsSucc ( e ); } |
| |
| |
| template <typename T> ListNodePosi(T) List<T>::insertAsLast ( T const &e ) |
| { _size ++; return trailer -> insertAsPred ( e ); } |
| |
| |
| template <typename T> ListNodePosi(T) List<T>::insertA ( ListNodePosi(T) p, T const &e ) |
| { _size ++; return p -> insertAsSucc ( e ); } |
| |
| |
| template <typename T> ListNodePosi(T) List<T>::insertB ( ListNodePosi(T) p, T const &e ) |
| { _size ++; return p -> insertAsPred ( e ); } |
删除
在列表中删除指定节点 p
的算法:
| |
| template <typename T> |
| T List<T>::remove ( ListNodePosi(T) p ){ |
| T e = p -> data; |
| p -> pred -> succ = p -> succ; p-> succ -> pred = p -> pred; |
| delete p; |
| _size--; |
| return e; |
| } |
解释:
- 删除节点之前,列表在位置p附近的局部如图(a)所示。
- 为了删除位置
p
处的节点,首先如图(b)所示,令其前驱节点与后继节点相互链接。
- 然后如图(c)所示, 释放掉已经孤立出来的节点
p
,同时相应地更新列表规模计数器 _size
。
- 最终如图(d)所示,经过如此调整之后,原节点
p
即被顺利地从列表中摘除。
时间复杂度
由于对链表的插入和删除是直接元素插入位置的地址进行处理,故不需要像向量一样在插入或删除之后移动元素。故所有的插入和删除操作所需的时间复杂度均为 O(1)。在进行频繁的插入删除操作时,列表的效率远远高于向量,可见动态存储策略的优越性。
copyNodes()
与向量一样,列表的内部结构也是动态创建的,故利用默认的构造方法并不能真正地完成新列表的复制创建。为此,需要专门编写相应的构造方法,通过复制某一已有列表来构造新列表。
尽管这里提供了多种形式,以允许对原列表的整体或局部复制,但其实质过程均大同小异,都可概括和转化为底层内部方法 copyNodes()
。在输入参数合法的前提下,copyNodes()
首先调用 init()
方法,创建头、尾哨兵节点并做相应的初始化处理,然后自 p
所指节点起,从原列表中取出 n
个相邻的节点,并逐一作为末节点插至新列表中。
| |
| template <typename T> |
| void List<T>::copyNodes ( ListNodePosi(T) p, int n ){ |
| init(); |
| while ( n-- ) { insertAsLast ( p -> data ); p = p -> succ; } |
| } |
根据此前的分析,init()
操作以及各步适代中的插入操作均只需常数时间,故 copyNodes()
过程总体的运行时间应为 O(n+1),线性正比于待复制列表区间的长度 n
。
基于复制的构造
基于 copyNodes()
我们可以实现多种接口:
| |
| template <typename T> |
| List<T>::List ( ListNodePosi(T) p, int n ) { copyNodes ( p, n ); } |
| |
| |
| template <typename T> |
| List<T>::List ( List<T> const& L ) { copyNodes ( L.first(), L._size ); } |
| |
| |
| template <typename T> |
| List<T>::List ( List<T> const& L, int r, int n ) { copyNodes ( L[r], n ); } |
列表的清空
| |
| template <typename T> |
| void List<T>::clear(){ |
| int oldSize = _size; |
| while ( 0 < _size ) remove ( header -> succ ); |
| } |
列表的析构
与所有对象一样,列表对象析构时,将其所占用的资源归还操作系统。
| |
| template <typename T> List<T>::~List() |
| { clear(); delete header; delete trailer; } |
解释:
- 列表的析构需首先调用
clear()
接口删除并释放所有对外部有效的节点。
- 然后释放内部的头、尾哨兵节点。
列表的析构与清空时间复杂度一样,均为 O(n),线性正比于列表规模。
无序列表的唯一化
旨在剔除无序列表中重复元素的接口 deduplicate()
,与算法 Vector: :deduplicate()
类似,这里也是自前向后依次处理各节点 p
,一旦通过 find()
接口在 p
的前驱中查到雷同者,则随即调用 remove()
接口将其删除。
| |
| template <typename T> |
| int List<T>::deduplicate() { |
| if ( _size < 2 ) return 0; |
| int oldSize = _size; |
| ListNodePosi(T) p = header; Rank r = 0; |
| while ( trailer != ( p = p -> succ ) ){ |
| ListNodePosi(T) q = find ( p -> data, r, p ); |
| q ? remove ( q ) : r++; |
| } |
| return oldSize - _size; |
| } |
解释:
- 向量与列表中元素的逻辑次序一致,故二者的
deduplicate()
算法亦具有类似的不变性和单调性 ,故正确性均可保证。
与无序向量的去重算法一样,该算法总共需做 O(n) 步迭代。 每一步迭代中 find()
操作所需的时间线性正比于查找区间宽度。列表节点每次 remove()
操作仅需常数时间,但迭代需要的时间为 O(n2),故时间复杂度为 O(n2)。
有序列表的唯一化
与有序向量同理,有序列表中的雷同节点也必然在逻辑上彼此紧邻。利用这一特性,可实现重复节点删除算法。位置指针 p
和q
分别指向每一对相邻的节点,若二者雷同则删除 q
,否则转向下一对相令节点。如此反复迭代,直至检查过所有节点。
| |
| template <typename T> |
| int List<T>::uniquify() { |
| if ( _size < 2 ) return 0; |
| int oldSize = _size; |
| ListNodePosi(T) p = first(); ListNodePosi(T) q; |
| while ( trailer != ( q = p -> succ ) ) |
| if ( p -> data != q -> data ) p = q; |
| else remove ( q ); |
| return oldSize - _size; |
| } |
整个过程运行时间为 O(n),线性正比于列表原先的规模。
列表也提供支持节点批量式访问的遍历接口,与向量一样,我们在此也提供两种方式:
| |
| template <typename T> |
| void List<T>::traverse ( void ( *visit ) ( T & ) ){ |
| for ( ListNodePosi(T) p = header -> succ; p != trailer; p = p -> succ ){ |
| visit ( p -> data ); |
| } |
| } |
| |
| |
| template <typename T> template <typename VST> |
| void List<T>::traverse ( VST &visit ){ |
| for ( ListNodePosi(T) p = header -> succ; p != trailer; p = p -> succ ){ |
| visit ( p -> data ); |
| } |
| } |
实例:
实现遍历输出
| |
| template <typename T> template <typename VST> |
| void List<T>::traverse ( VST &visit , ListNodePosi(T) r, ListNodePosi(T) l){ |
| for ( ListNodePosi(T) p = r -> pred -> succ; p != l -> succ; p = p -> succ ){ |
| visit ( p -> data ); |
| } |
| } |
| |
| void show(int e){ |
| cout << e << " "; |
| } |
列表逆置
交换节点的前驱和后继的指针,并交换头尾节点:
| |
| template <typename T> |
| void List<T>::reverse(){ |
| ListNodePosi(T) p = header; |
| for(; p; p = p -> pred){ |
| std::swap(p -> pred, p -> succ); |
| } |
| std::swap(header, trailer); |
| } |
插入排序
插入排序(insertionsort)算法适用于包括向量与列表在内的任何序列结构。
思想:
- 始终将整个序列视作并切分为两部分: 有序的前缀,无序的后缀。通过迭代,反复地将后缀的首元素转移至前缀中。
- 由此可知:在任何时刻,相对于当前节点
e = S[r]
,前缀 S[0, r)
总是业已有序。
| |
| template <typename T> |
| void List<T>::insertionSort ( ListNodePosi(T) p, int n ){ |
| for (int r = 0; r <= n; r ++){ |
| insertA ( search( p -> data, r, p ), p -> data ); |
| p = p -> succ; remove ( p -> pred ); |
| } |
| } |
解释:
- 算法开始时该前缀为空,不变性自然满足。
- 现假设如图所示前缀
S[0,r)
已经有序,接下来, 借助有序序列的查找算法,可在该前缀中定位到不大于 e
的最大元素。
- 于是只需将
e
从无序后组中取出,并紧邻于查找返回的位置之后插入,即可使得有序前缀的范围扩大至 S[0,r]
。
- 如此,该前缀的范围可不断拓展。当其最终费盖整个序列时,亦即整体有序。
插入排序算法共由 n
步迁代组成,故其运行时间应取决于各步迁代中所执行的查找、删除及插入操作的效率。根据此前的结论,插入操作和删除操作只需 O(1) 时间,查找操作 search()
所需时间在 O(1) 至 O(n) 之间浮动。
若输出序列完全逆序,,则各次 search()
操作所需时间将线性递增。在等概率条件下,平均仍需要 O(n2) 时间。
选择排序
选择排序(selectionsort)也适用于向量与列表之类的序列结构。
思想:
- 与插入排序类似,该算法也将序列划分为无序前缀和有序后缀两部分,且要求要求前缀不大于后缀。
- 如此,每次只需从前缀中选出最大者,并作为最小元素转移至后缀中,即可使有序部分的范围不断扩张,直至整个列表有序。
- 由此可知:在任何时刻,后缀
S(r, n)
已经有序,且不小于前缀 S[0, r)
。
首先要实现定位无序列表中最大的节点的函数 selectMax()
:
| |
| template <typename T> |
| ListNodePosi(T) List<T>::selectMax (ListNodePosi(T) p, int n){ |
| ListNodePosi(T) max = p; |
| for ( ListNodePosi(T) cur = p; 1 < n; n-- ){ |
| if ( !lt ( ( cur = cur->succ )->data, max->data ) ) |
| max = cur; |
| } |
| return max; |
| } |
选择排序 selectionSort()
:
| |
| template <typename T> |
| void List<T>::selectionSort ( ListNodePosi(T) p, int n ){ |
| ListNodePosi(T) head = p -> pred; ListNodePosi(T) tail = p; |
| for ( int i = 0; i < n; i ++ ) tail = tail -> succ; |
| while ( 1 < n ){ |
| ListNodePosi(T) max = selectMax ( head -> succ, n ); |
| insertB ( tail, remove ( max ) ); |
| tail = tail -> pred; n --; |
| } |
| } |
解释:
- 在算法的初始时刻,后缀为空,不变性自然满足。
- 如图所示,假设不变性己满足。于是,可调用无序序列的查找算法,从前绥中找出最大者
M
。
- 接下来,只需将
M
从前绥中取出并作为首元素插入后缀,即可使得后缀的范围扩大,并继续保持有序。
- 如此,该后缀的范围可不断拓展,当其最终覆盖整个序列时,亦即整体有序。
与插入排序类似地,选择排序亦由 n
步选代组成,故其运行时间取决于各步送代中查找及插入操作的效率 。根据结论可知 insertB()
和 remove()
均只需 O(1) 时间 。 selectMax()
每次必须遍历整个无序前缀,耗时应线性正比于前缀长度,全程累计耗时 O(n2)。
归并排序
基于二路归并的向量排序算法,其构思也同样适用于列表结构。实际上,有序列表的二路归并不仅可以实现,而且能够达到与有序向量二路归并同样高的效率。
仿照向量的归并排序算法 mergesort()
,采用分治策略并基于以上有序列表的二路归并算法,实现列表的归并排序算法:
| |
| template <typename T> |
| void List<T>::merge(ListNodePosi(T) p, int mid, int n){ |
| int lenB = mid; |
| int lenC = n - mid; |
| ListNodePosi(T) A = p; |
| T * B = new T[mid]; |
| for(int i = 0; i<mid; ++i){ |
| B[i]= p->data; p = p->succ; |
| } |
| ListNodePosi(T) C = p; |
| for(int j = 0, k = 0; j < lenB; ){ |
| if( lenC <= k || B[j] <= C->data ){ |
| A -> data = B[j ++]; |
| A = A -> succ; |
| } |
| if( k < lenC && C -> data < B[j]){ |
| A -> data = C -> data; |
| A = A -> succ; C = C -> succ; |
| ++ k; |
| } |
| } |
| delete [] B; |
| } |
| |
| |
| template <typename T> |
| void List<T>::mergeSort(ListNodePosi(T) p, int n){ |
| if( n < 2 ) return ; |
| ListNodePosi(T) q = p; |
| int mid = n >> 1; |
| int x = mid; while(x --) q = q -> succ; |
| mergeSort(p, mid); mergeSort(q, n - mid); |
| if(q -> pred -> data > q -> data) merge(p, mid, n); |
| } |
针对两个有序的链表合并,我们提供对外接口:
| |
| template <typename T> |
| void List<T>::mergeLists(ListNodePosi(T) p, int n, List<T> &L, ListNodePosi(T) q, int m){ |
| List<T> t; |
| while(m > 0 && n > 0){ |
| if(p -> data <= q -> data){ |
| t.insertAsLast(p -> data); |
| p = p -> succ;n --; |
| } |
| else{ |
| t.insertAsLast(q -> data); |
| q = q -> succ;m --; |
| } |
| } |
| while(n > 0) t.insertAsLast(p -> data), p = p -> succ, n --; |
| while(m > 0) t.insertAsLast(q -> data), q = q -> succ, m --; |
| this -> clear(), L.clear(); |
| for(ListNodePosi(T) i = t.first(); i != t.trailer; i = i -> succ){ |
| this -> insertAsLast(i -> data); |
| } |
| p = this -> first(); |
| } |
| #include <iostream> |
| #include <cstdlib> |
| using namespace std; |
| |
| typedef int Rank; |
| #define ListNodePosi(T) ListNode<T>* |
| |
| template<typename T> |
| struct ListNode{ |
| |
| T data; |
| ListNodePosi(T) pred; |
| ListNodePosi(T) succ; |
| |
| ListNode(){} |
| ListNode(T e,ListNodePosi(T) p = NULL,ListNodePosi(T) s = NULL) |
| :data(e),pred(p),succ(s){} |
| |
| ListNodePosi(T) insertAsPred(T const &e); |
| ListNodePosi(T) insertAsSucc(T const &e); |
| }; |
| |
| template <typename T> class List{ |
| private: |
| |
| int _size; |
| ListNodePosi(T) header; |
| ListNodePosi(T) trailer; |
| |
| |
| protected: |
| |
| void init(); |
| void copyNodes ( ListNodePosi(T) p, int n ); |
| void merge (ListNodePosi(T) p, int mid, int n); |
| |
| public: |
| |
| List() { init(); } |
| List ( List<T> const& L); |
| List ( List<T> const& L, Rank r, int n); |
| List ( ListNodePosi(T) p,int n); |
| |
| |
| ~List(); |
| |
| |
| |
| |
| Rank size() const { return _size; } |
| bool empty() const { return !_size; } |
| T &operator[](Rank r) const; |
| ListNodePosi(T) first() const { return header -> succ; } |
| ListNodePosi(T) last() const {return trailer -> pred; } |
| bool valid(ListNodePosi(T) p) |
| { return p && (trailer != p) && (header != p); } |
| ListNodePosi(T) find ( T const& e) const |
| { return find( e, _size, trailer); } |
| ListNodePosi(T) find( T const& e, int n, ListNodePosi(T) p) const; |
| ListNodePosi(T) search( T const& e) const |
| { return search( e, _size, trailer); } |
| ListNodePosi(T) search( T const& e, int n, ListNodePosi(T) p) const; |
| ListNodePosi(T) selectMax (ListNodePosi(T) p, int n); |
| ListNodePosi(T) selectMax () {return selectMax( header -> succ, _size); } |
| |
| |
| ListNodePosi(T) insertAsFirst (T const& e); |
| ListNodePosi(T) insertAsLast (T const& e); |
| ListNodePosi(T) insertA (ListNodePosi(T) p, T const& e); |
| ListNodePosi(T) insertB (ListNodePosi(T) p, T const& e); |
| void clear(); |
| T remove ( ListNodePosi(T) p ); |
| int deduplicate(); |
| int uniquify(); |
| void reverse(); |
| void insertionSort (ListNodePosi(T) p, int n); |
| void selectionSort ( ListNodePosi(T) p, int n ); |
| void mergeSort ( ListNodePosi(T) p, int n ); |
| void mergeLists(ListNodePosi(T) p, int n, List<T>& L, ListNodePosi(T) q, int m); |
| void mergeLists( List<T> & L ) { mergeLists ( header -> succ, _size, L, L.header -> succ, L._size ); } |
| |
| |
| void traverse(void (*)(T &)); |
| template <typename VST> |
| void traverse(VST &); |
| template <typename VST> |
| void traverse( VST &visit , ListNodePosi(T) r, ListNodePosi(T) l); |
| |
| }; |
| |
| |
| template<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> |
| ListNodePosi(T) List<T>::find ( T const &e, int n, ListNodePosi(T) p ) const { |
| while (0 < n--) |
| if (e == (p = p -> pred) -> data) return p; |
| return NULL; |
| } |
| |
| |
| |
| template <typename T> |
| ListNodePosi(T) List<T>::search ( T const &e, int n, ListNodePosi(T) p ) const { |
| while (0 < n--) |
| if (((p = p -> pred) -> data) <= e) break; |
| return p; |
| } |
| |
| |
| template <typename T> |
| ListNodePosi(T) ListNode<T>::insertAsPred ( T const &e ){ |
| ListNodePosi(T) x = new ListNode ( e, pred, this ); |
| pred -> succ = x; pred = x; |
| return x; |
| } |
| |
| |
| template <typename T> |
| ListNodePosi(T) ListNode<T>::insertAsSucc ( T const &e ){ |
| ListNodePosi(T) x = new ListNode ( e, this, succ ); |
| succ -> pred = x; succ = x; |
| return x; |
| } |
| |
| |
| template <typename T> ListNodePosi(T) List<T>::insertAsFirst ( T const &e ) |
| { _size ++; return header -> insertAsSucc ( e ); } |
| |
| |
| template <typename T> ListNodePosi(T) List<T>::insertAsLast ( T const &e ) |
| { _size ++; return trailer -> insertAsPred ( e ); } |
| |
| |
| template <typename T> ListNodePosi(T) List<T>::insertA ( ListNodePosi(T) p, T const &e ) |
| { _size ++; return p -> insertAsSucc ( e ); } |
| |
| |
| template <typename T> ListNodePosi(T) List<T>::insertB ( ListNodePosi(T) p, T const &e ) |
| { _size ++; return p -> insertAsPred ( e ); } |
| |
| |
| template <typename T> |
| T List<T>::remove ( ListNodePosi(T) p ){ |
| T e = p -> data; |
| p -> pred -> succ = p -> succ; p-> succ -> pred = p -> pred; |
| delete p; |
| _size--; |
| return e; |
| } |
| |
| |
| template <typename T> |
| void List<T>::copyNodes ( ListNodePosi(T) p, int n ){ |
| init(); |
| while ( n-- ) { insertAsLast ( p -> data ); p = p -> succ; } |
| } |
| |
| |
| template <typename T> |
| List<T>::List ( ListNodePosi(T) p, int n ) { copyNodes ( p, n ); } |
| |
| |
| template <typename T> |
| List<T>::List ( List<T> const& L ) { copyNodes ( L.first(), L._size ); } |
| |
| |
| template <typename T> |
| List<T>::List ( List<T> const& L, int r, int n ) { copyNodes ( L[r], n ); } |
| |
| |
| template <typename T> |
| void List<T>::clear(){ |
| int oldSize = _size; |
| while ( 0 < _size ) remove ( header -> succ ); |
| } |
| |
| |
| template <typename T> List<T>::~List() |
| { clear(); delete header; delete trailer; } |
| |
| |
| template <typename T> |
| int List<T>::deduplicate() { |
| if ( _size < 2 ) return 0; |
| int oldSize = _size; |
| ListNodePosi(T) p = header; Rank r = 0; |
| while ( trailer != ( p = p -> succ ) ){ |
| ListNodePosi(T) q = find ( p -> data, r, p ); |
| q ? remove ( q ) : r++; |
| } |
| return oldSize - _size; |
| } |
| |
| |
| template <typename T> |
| int List<T>::uniquify() { |
| if ( _size < 2 ) return 0; |
| int oldSize = _size; |
| ListNodePosi(T) p = first(); ListNodePosi(T) q; |
| while ( trailer != ( q = p -> succ ) ) |
| if ( p -> data != q -> data ) p = q; |
| else remove ( q ); |
| return oldSize - _size; |
| } |
| |
| |
| template <typename T> |
| void List<T>::traverse ( void ( *visit ) ( T & ) ){ |
| for ( ListNodePosi(T) p = header -> succ; p != trailer; p = p -> succ ){ |
| visit ( p -> data ); |
| } |
| } |
| |
| |
| template <typename T> template <typename VST> |
| void List<T>::traverse ( VST &visit ){ |
| for ( ListNodePosi(T) p = header -> succ; p != trailer; p = p -> succ ){ |
| visit ( p -> data ); |
| } |
| } |
| |
| |
| template <typename T> template <typename VST> |
| void List<T>::traverse ( VST &visit , ListNodePosi(T) r, ListNodePosi(T) l){ |
| for ( ListNodePosi(T) p = r -> pred -> succ; p != l -> succ; p = p -> succ ){ |
| visit ( p -> data ); |
| } |
| } |
| |
| void show(int e){ |
| cout << e << " "; |
| } |
| |
| |
| template <typename T> |
| void List<T>::reverse(){ |
| ListNodePosi(T) p = header; |
| for(; p; p = p -> pred){ |
| std::swap(p -> pred, p -> succ); |
| } |
| std::swap(header, trailer); |
| } |
| |
| |
| template <typename T> |
| void List<T>::insertionSort ( ListNodePosi(T) p, int n ){ |
| for (int r = 0; r <= n; r ++){ |
| insertA ( search( p -> data, r, p ), p -> data ); |
| p = p -> succ; remove ( p -> pred ); |
| } |
| } |
| |
| |
| template <typename T> |
| ListNodePosi(T) List<T>::selectMax (ListNodePosi(T) p, int n){ |
| ListNodePosi(T) max = p; |
| for ( ListNodePosi(T) cur = p; 1 < n; n-- ){ |
| if ( !lt ( ( cur = cur->succ )->data, max->data ) ) |
| max = cur; |
| } |
| return max; |
| } |
| |
| |
| template <typename T> |
| void List<T>::selectionSort ( ListNodePosi(T) p, int n ){ |
| ListNodePosi(T) head = p -> pred; ListNodePosi(T) tail = p; |
| for ( int i = 0; i < n; i ++ ) tail = tail -> succ; |
| while ( 1 < n ){ |
| ListNodePosi(T) max = selectMax ( head -> succ, n ); |
| insertB ( tail, remove ( max ) ); |
| tail = tail -> pred; n --; |
| } |
| } |
| |
| |
| template <typename T> |
| void List<T>::merge(ListNodePosi(T) p, int mid, int n){ |
| int lenB = mid; |
| int lenC = n - mid; |
| ListNodePosi(T) A = p; |
| T * B = new T[mid]; |
| for(int i = 0; i<mid; ++i){ |
| B[i]= p->data; p = p->succ; |
| } |
| ListNodePosi(T) C = p; |
| for(int j = 0, k = 0; j < lenB; ){ |
| if( lenC <= k || B[j] <= C->data ){ |
| A -> data = B[j ++]; |
| A = A -> succ; |
| } |
| if( k < lenC && C -> data < B[j]){ |
| A -> data = C -> data; |
| A = A -> succ; C = C -> succ; |
| ++ k; |
| } |
| } |
| delete [] B; |
| } |
| |
| |
| template <typename T> |
| void List<T>::mergeSort(ListNodePosi(T) p, int n){ |
| if( n < 2 ) return ; |
| ListNodePosi(T) q = p; |
| int mid = n >> 1; |
| int x = mid; while(x --) q = q -> succ; |
| mergeSort(p, mid); mergeSort(q, n - mid); |
| if(q -> pred -> data > q -> data) merge(p, mid, n); |
| } |
| |
| |
| template <typename T> |
| void List<T>::mergeLists(ListNodePosi(T) p, int n, List<T> &L, ListNodePosi(T) q, int m){ |
| List<T> t; |
| while(m > 0 && n > 0){ |
| if(p -> data <= q -> data){ |
| t.insertAsLast(p -> data); |
| p = p -> succ;n --; |
| } |
| else{ |
| t.insertAsLast(q -> data); |
| q = q -> succ;m --; |
| } |
| } |
| while(n > 0) t.insertAsLast(p -> data), p = p -> succ, n --; |
| while(m > 0) t.insertAsLast(q -> data), q = q -> succ, m --; |
| this -> clear(), L.clear(); |
| for(ListNodePosi(T) i = t.first(); i != t.trailer; i = i -> succ){ |
| this -> insertAsLast(i -> data); |
| } |
| p = this -> first(); |
| } |
| |
| int num[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; |
| |
| void test_01(){ |
| |
| cout << "##### test_01() begin #####" << endl << endl; |
| |
| cout << "Test List Init :" << endl; |
| List<int> a; |
| List<double> b; |
| List<List<int>> c; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "Test List copyNodes():" << endl; |
| |
| List<int> e; |
| |
| for(int i = 0; i < 10; i ++) e.insertAsLast(num[i]); |
| |
| cout << "e = "; |
| e.traverse(show, e.first(), e.last()); |
| cout << endl; |
| |
| List<int> f(e); |
| cout << "f = "; |
| f.traverse(show, f.first(), f.last()); |
| cout << endl; |
| |
| List<int> g(e.first(), 4); |
| cout << "g = "; |
| g.traverse(show, g.first(), g.last()); |
| cout << endl; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "##### test_01() over #####" << endl; |
| |
| } |
| |
| void test_02(){ |
| |
| cout << "##### test_02() begin #####" << endl << endl; |
| |
| cout << "Test find() :" << endl; |
| |
| List<int> a; |
| |
| for(int i = 0; i < 10; i ++){ |
| int x = rand() % 20; |
| a.insertAsLast(x); |
| } |
| |
| cout << "a = "; |
| a.traverse(show, a.first(), a.last()); |
| cout << endl; |
| |
| bool vis_1[101] = {0}; |
| |
| for(int i = 0; i < 10; i ++){ |
| int x = rand() % 20; |
| if(!vis_1[x]) cout << "find(" << x << ") = " << a.find(x) << endl; |
| vis_1[x] = 1; |
| } |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "Test search() :" << endl; |
| |
| List<int> b; |
| |
| for(int i = 0; i < 10; i ++) b.insertAsLast(num[i]); |
| |
| cout << "b = "; |
| b.traverse(show, b.first(), b.last()); |
| cout << endl; |
| |
| int vis_2[101]={0}; |
| |
| for(int i = 0; i < 10; i ++){ |
| int x = rand() % 20; |
| if(!vis_2[x]){ |
| cout << "search(" << x << ") = " << b.search(x); |
| if(b.search(x) == b.last() && b.last() -> data != x) cout << " Not Found!"; |
| cout << endl; |
| } |
| vis_2[x] = 1; |
| } |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "##### test_02() over #####" << endl; |
| |
| } |
| |
| void test_03(){ |
| |
| cout << "##### test_03() begin #####" << endl << endl; |
| |
| cout << "Test remove() :" << endl; |
| |
| List<int> a; |
| |
| for(int i = 0; i < 10; i ++) a.insertAsLast(num[i]); |
| |
| cout << "a = "; |
| a.traverse(show, a.first(), a.last()); |
| cout << endl; |
| |
| cout << "remove(4)" << endl; |
| cout << "remove(7)" << endl; |
| |
| a.remove(a.first() -> succ -> succ -> succ); |
| a.remove(a.last() -> pred -> pred -> pred); |
| |
| cout << "removed a = "; |
| a.traverse(show, a.first(), a.last()); |
| cout << endl; |
| cout << "size(a) = " << a.size() << endl; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "Test clear() : " << endl; |
| |
| List<int> b(a); |
| |
| cout << "b = "; |
| b.traverse(show, b.first(), b.last()); |
| cout << endl; |
| |
| b.clear(); |
| |
| cout << "b.clear(), size(b) = " << b.size() << endl; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "##### test_03() over #####" << endl; |
| |
| } |
| |
| void test_04(){ |
| |
| cout << "##### test_04() begin #####" << endl << endl; |
| |
| cout << "Test deduplicate() :" << endl; |
| |
| List<int> a; |
| |
| for(int i = 0; i < 20; i ++){ |
| int x = rand() % 20; |
| a.insertAsLast(x); |
| } |
| |
| cout << "a = "; |
| a.traverse(show, a.first(), a.last()); |
| cout << endl; |
| |
| a.deduplicate(); |
| |
| cout << "deduplicated a = "; |
| a.traverse(show, a.first(), a.last()); |
| cout << endl; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "Test uniquify() :" << endl; |
| |
| List<int> b; |
| |
| for(int i = 1; i <= 5; i ++){ |
| for(int j = 0; j < i; j ++){ |
| b.insertAsLast(i); |
| } |
| } |
| |
| cout << "b = "; |
| b.traverse(show, b.first(), b.last()); |
| cout << endl; |
| |
| b.uniquify(); |
| |
| cout << "uniquifyed b = "; |
| b.traverse(show, b.first(), b.last()); |
| cout << endl; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "##### test_04() over #####" << endl; |
| |
| } |
| |
| void test_05(){ |
| |
| cout << "##### test_05() begin #####" << endl << endl; |
| |
| cout << "Test reverse() :" << endl; |
| |
| List<int> a; |
| |
| for(int i = 1; i <= 10; i ++) a.insertAsLast(i); |
| |
| cout << "a = "; |
| a.traverse(show, a.first(), a.last()); |
| cout << endl; |
| |
| a.reverse(); |
| |
| cout << "reversed a = "; |
| a.traverse(show, a.first(), a.last()); |
| cout << endl; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "Test sort() :" << endl; |
| |
| List<int> b; |
| |
| for(int i = 0; i < 20; i ++){ |
| int x = rand() % 100; |
| b.insertAsLast(x); |
| } |
| |
| cout << "b = "; |
| b.traverse(show, b.first(), b.last()); |
| cout << endl; |
| |
| |
| |
| b.mergeSort(b.first(), b.size()); |
| |
| cout << "sorted b = "; |
| b.traverse(show, b.first(), b.last()); |
| cout << endl; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "Test mergeLists() :" << endl; |
| |
| List<int> e,f; |
| |
| for(int i = 1; i < 20; i += 2) e.insertAsLast(i); |
| for(int i = 2; i <= 20; i += 2) f.insertAsLast(i); |
| |
| cout << "e = "; |
| e.traverse(show, e.first(), e.last()); |
| cout << endl; |
| |
| cout << "f = "; |
| f.traverse(show, f.first(), f.last()); |
| cout << endl; |
| |
| e.mergeLists(f); |
| |
| cout << "e = "; |
| e.traverse(show, e.first(), e.last()); |
| cout << endl; |
| |
| cout << "RIGHT!" << endl << endl; |
| |
| cout << "##### test_05() over #####" << endl; |
| |
| } |
| |
| int main(){ |
| |
| cout << "###########################" << endl; |
| test_01(); |
| cout << "###########################" << endl; |
| cout << endl; |
| |
| cout << "###########################" << endl; |
| test_02(); |
| cout << "###########################" << endl; |
| cout << endl; |
| |
| cout << "###########################" << endl; |
| test_03(); |
| cout << "###########################" << endl; |
| cout << endl; |
| |
| cout << "###########################" << endl; |
| test_04(); |
| cout << "###########################" << endl; |
| cout << endl; |
| |
| cout << "###########################" << endl; |
| test_05(); |
| cout << "###########################" << endl; |
| cout << endl; |
| |
| return 0; |
| |
| } |