数据结构(c++)学习笔记--列表
文章目录
- 一、循位置访问
- 1.从静态到动态
- 2.从向量到列表
- 3.从秩到位置
- 二、接口与实现
- 1.列表节点:ADT接口
- 2.列表:ADT接口
- 3.循秩访问
- 三、无序列表
- 1.插入与删除
- 2.构造与析构
- 3.查找与去重
- 4.遍历
- 四、有序列表
- 1.唯一化
- 2.查找
- 五、选择排序
- 1.代码
- 2.稳定性
- 3.性能分析
- 六、循环节
- 1.循环节
- 2.单调性
- 3.无效的交换
- 七、插入排序
- 1.减而治之
- 2.代码
- 3.平均性能:后向分析
- 八、归并排序
- 九、逆序对
- 1.逆序对(Inversion)
- 2.起泡排序
- 3.插入排序
- 4.计数
- 十、游标实现
- 1.动机与构思
- 2.实例
一、循位置访问
1.从静态到动态
根据是否修改数据结构,所有操作大致分为两类方式
- 静态: 仅读取,数据结构的内容及组成一般不变:get、search
- 动态: 需写入,数据结构的局部或整体将改变:put、insert、remove
与操作方式相对应地,数据元素的存储与组织方式也分为两种
- 静态
- 数据空间整体创建或销毁
- 数据元素的物理存储次序与其逻辑次序严格一致;可支持高效的静态操作
- 比如向量,元素的物理地址与其逻辑次序线性对应
- 动态
- 为各数据元素动态地分配和回收的物理空间
- 相邻元素记录彼此的物理地址,在逻辑上形成一个整体;可支持高效的动态操作
- 静态
2.从向量到列表
列表(list)是采用动态储存策略的典型结构
- 其中的元素称作节点(node),通过指针或引用彼此连接
- 在逻辑上构成一个线性序列:L = { a0, a1, …, an-1 }
相邻节点彼此互称前驱(predecessor)或后继(successor)
- 没有前驱/后继的节点称作首(first/front)/末(last/rear)节点
3.从秩到位置
- 向量支持循秩访问(call-by-rank):根据元素的秩,可在O(1)时间内直接确定其物理地址
- 然而,此时的循秩访问成本过高,已不合时宜;因此,应改用循位置访问(call-by-position)的方式,亦即,转而利用节点之间的相互引用,找到特定的节点
二、接口与实现
1.列表节点:ADT接口
作为列表的基本元素 列表节点首先需要 独立地“封装”实现
基本操作接口
|操作接口 |功能 |
|–|–|
|pred() |当前节点前驱节点的位置 |
|succ() |当前节点后继节点的位置|
|data() |当前节点所存数据对象 |
|insertAsPred(e) |插入前驱节点,存入被引用对象e,返回新节点位置|
|insertAsSucc(e) |插入后继节点,存入被引用对象e,返回新节点位置|ListNode模板
template<typename T> using ListNodePosi = ListNode*; //列表节点位置(C++.0x)
template<typename T> struct ListNode { //简洁起见,完全开放而不再严格封装 T data; ListNodePosi pred; ListNodePosi succ; ListNode() {} //针对header和trailer的构造 ListNode(T e, ListNodePosi p = NULL, ListNodePosi s = NULL): data(e), pred(p), succ(s) {} //默认构造器 ListNodePosi insertAsPred( T const & e ); //前插入 ListNodePosi insertAsSucc( T const & e ); //后插入
};
2.列表:ADT接口
ADT接口
|操作接口 |功能 |适用对象 |
|–|–|–|
|size() |报告列表当前的规模(节点总数) |列表 |
|first(), last() |返回首、末节点的位置| 列表 |
|insertAsFirst(e), insertAsLast(e) |将e当作首、末节点插入 |列表|
|insert(p, e), insert(e, p) |将e当作节点p的直接后继、前驱插入 |列表|
|remove§ |删除位置p处的节点,返回其中数据项 |列表 |
|disordered() |判断所有节点是否已按非降序排列 |列表|
|sort() |调整各节点的位置,使之按非降序排列 |列表|
|find(e) |查找目标元素e,失败时返回NULL |列表|
|search(e)| 查找e,返回不大于e且秩最大的节点| 有序列|
|deduplicate(), uniquify() |剔除重复节点| 列表/有序列表|
|traverse() |遍历列表 |列表|List模板类
#include "listNode.h" //引入列表节点类
template<typename T> class List { //列表模板类
private: int _size; ListNodePosi header, trailer; //哨兵 //头、首、末、尾节点的秩,可分别理解为-1、0、n-1、n
protected: /* ... 内部函数 */
public: /* ... 构造函数、析构函数、只读接口、可写接口、遍历接口 */
};template void List::init() { //初始化,创建列表对象时统一调用 header = new ListNode; trailer = new ListNode; header->succ = trailer; header->pred = NULL; trailer->pred = header; trailer->succ = NULL; _size = 0;
}
3.循秩访问
- 重载下标操作符,可模仿向量的循秩访问方式
template<typename T> //assert: 0 <= r < size
T List::operator[]( Rank r ) const {ListNodePosi p = first(); //从首节点出发
while ( 0 < r-- ) p = p->succ; //顺数第r个节点即是
return p->data; //目标节点
} //秩 == 前驱的总数
- 时间复杂度为O®;均匀分布时,期望复杂度为(1+2+3+…+n)/n=O(n)
三、无序列表
1.插入与删除
- 插入
template<typename T> //前插入算法(后插入算法完全对称)
ListNodePosi ListNode::insertAsPred( T const & e ) { //O(1) ListNodePosi x = new ListNode( e, pred, this ); //创建 pred->succ = x; pred = x; //次序不可颠倒 return x; //建立链接,返回新节点的位置
} //得益于哨兵,即便this为首节点亦不必特殊处理——此时等效于insertAsFirst(e)
- 删除
template<typename T> //删除合法位置p处节点,返回其数值 T
List::remove( ListNodePosi p ) { //O(1) T e = p->data; //备份待删除节点数值(设类型T可直接赋值) p->pred->succ = p->succ; p->succ->pred = p->pred; //短路 delete p; _size--; return e; //返回备份数值
}
2.构造与析构
- copyNodes() + 构造
template<typename T>
void List::copyNodes( ListNodePosi p, int n ) { //O(n) init(); //创建头、尾哨兵节点并做初始化 while ( n-- ) { //将起自p的n项依次作为末节点 insertAsLast( p->data ); p = p->succ; }
}
- clear() + 析构
template<typename T>
List::~List() //列表析构 { clear(); delete header; delete trailer;
} //清空列表,释放头、尾哨兵节点 template<typename T>
int List::clear() { //清空列表 int oldSize = _size; while ( 0 < _size ) //反复删除首节点,O(n) remove( header->succ ); return oldSize;
}
3.查找与去重
- 查找
template<typename T>
ListNodePosi List::find( T const & e, int n, ListNodePosi p ) const { while ( 0 < n-- ) //自后向前 if ( e == ( p = p->pred ) ->data ) //逐个比对(假定类型T已重载“==”) return p; //在p的n个前驱中,等于e的最靠后者 return NULL;
} //O(n)
- 去重
template<typename T> int List::deduplicate() { int oldSize = _size;ListNodePosi p = first(); for ( Rank r = 0; p != trailer; p = p->succ ) //O(n) if ( ListNodePosi q = find( p->data, r, p ) ) //O(n) remove ( q ); else r++; //无重前缀的长度 return oldSize - _size; //删除元素总数
} //正确性及效率分析的方法与结论,与Vector::deduplicate()相同
4.遍历
//函数指针
template<typename T> void List::traverse( void ( * visit )( T & ) ) { for(NodePosi p = header->succ; p != trailer; p = p->succ) visit( p->data );
}//函数对象
template<typename T> template<typename VST> void List::traverse( VST & visit ) { for( NodePosi p = header->succ; p != trailer; p = p->succ ) visit( p->data );
}
四、有序列表
1.唯一化
template<typename T>
int List::uniquify() { if ( _size < 2 ) return 0; //平凡列表自然无重复 int oldSize = _size; //记录原规模 ListNodePosi p = first(); ListNodePosi q; //各区段起点及其直接后继 while ( trailer != ( q = p->succ ) ) //反复考查紧邻的节点对(p,q) if ( p->data != q->data ) p = q; //若互异,则转向下一对 else remove(q); //否则(雷同)直接删除后者,不必如向量那样间接地完成删除 return oldSize - _size; //规模变化量,即被删除元素总数
} //只需遍历整个列表一趟,O(n)
2.查找
template<typename T>
ListNodePosi List::search( T const & e, int n, ListNodePosi p ) const { do { p = p->pred; n--; } //从右向左 while ( ( -1 < n ) && ( e < p->data ) ); //逐个比较,直至命中或越界 return p; //失败时,返回区间左边界的前驱(可能是header)
}
- 性能 + 拓展
最好O(1),最坏O(n);等概率时平均O(n),正比于区间宽度
语义与向量相似,便于插入排序等后续操作:insert( search( e, r, p ), e )
按照循位置访问的方式,物理存储地址与其逻辑次序无关;依据秩的随机访问无法高效实现,而只能依据元素间的引用顺序访问
五、选择排序
1.代码
template<typename T> void List::selectionSort( ListNodePosi p, int n ) { ListNodePosi head = p->pred, tail = p; for ( int i = 0; i < n; i++ ) tail = tail->succ; //待排序区间为(head, tail) while ( 1 < n ) { //反复从(非平凡)待排序区间内找出最大者,并移至有序区间前端 insert(remove( selectMax( head->succ, n ) ), tail ); //可能就在原地... tail = tail->pred; n--; //待排序区间、有序区间的范围,均同步更新 }
}template<typename T>
ListNodePosi List::selectMax( ListNodePosi p, int n ) { //Θ(n) ListNodePosi max = p; //最大者暂定为p for ( ListNodePosi cur = p; 1 < n; n-- ) //后续节点逐一与max比较 if ( ! lt( (cur = cur->succ)->data, max->data ) ) //data≥max max = cur; //则更新最大元素位置记录 return max; //返回最大节点位置
}
2.稳定性
稳定性:有多个元素同时命中时,约定返回其中特定的某一个(比如最靠后者)
在这里,需要采用比较器
!lt
()或ge()
,从而等效于后者优先;若采用平移法,如此即可保证,重复元素在列表中的相对次序,与其插入次序一致
3.性能分析
- 共迭代n次,在第k次迭代中
- selectMax() 为Θ(n - k)
- swap()/remove() + insert() 为 O(1)
故总体复杂度应为Θ(n2)
尽管如此,元素的移动操作远远少于起泡排序;也就是说,Θ(n2)主要来自于元素的比较操作(实际更为费时,成本相对更低)
利用高级数据结构,selectMax()可改进至O(logn)
六、循环节
1.循环节
任何一个序列A[0,n),都可以分解为若干个循环节
任何一个序列A[0,n),都对应于一个有序序列S[0,n)
元素A[k]在S中对应的秩,记作r(A[k])=r(k)∈[0,n)
元素A[k]所属的循环节是:A[k],A[r(k)],A[r(r(k))],…,A[r(…(r(r(k))))]=A[k]
每个循环节,长度均不超过n
循环节之间,互不相交
2.单调性
- 采用交换法,每迭代一步,M都会脱离原属的循环节,自成一个循环节
- M原所属循环节,长度恰好减少一个单位;其余循环节,保持不变
3.无效的交换
M已经就位,无需交换
最初有c个循环节,就出现c次 —— 最大值为n,期望Θ(logn)
七、插入排序
1.减而治之
不变性
- 序列总能视作两部分: S[0, r) + U[r, n)
初始化:|S| = r = 0
反复地,针对e = A[r] 在S中查找适当位置,以插入e
2.代码
template<typename T> void List::insertionSort( ListNodePosi p, int n ) { for ( int r = 0; r < n; r++ ) { //逐一引入各节点,由S 得到 r Sr+1 insert( search( p->data, r, p ), p->data ); p = p->succ; remove( p->pred ); //转向下一节点 } //n次迭代,每次O(r + 1)
} //仅使用O(1)辅助空间,属于就地算法
- 紧邻于search()接口返回的位置之后插入当前节点,总是保持有序
3.平均性能:后向分析
e=[r]刚插入完成的那一时刻,此时的有序前缀[0,r]中其中的r+1个元素均有可能是e,且概率均为1/(r+1)
因此,在刚完成的这次迭代中为引入S[r]所花费时间的数学期望为1+∑k=0rk/(r+1)=1+r/21+\sum_{k=0}^{r}k/(r+1) = 1+r/21+∑k=0rk/(r+1)=1+r/2
于是,总体时间的数学期望为∑r=0n−1(r/2+1)=O(n2)\sum_{r=0}^{n-1}(r/2+1) = O(n^2)∑r=0n−1(r/2+1)=O(n2)
八、归并排序
template<typename T> void List::mergeSort( ListNodePosi & p, int n ) { if ( n < 2 ) return; //待排序范围足够小时直接返回,否则... ListNodePosi q = p; int m = n >> 1; //以中点为界 for ( int i = 0; i < m; i++ ) q = q->succ; //均分列表:O(m) = O(n) mergeSort( p, m ); mergeSort( q, n – m ); //子序列分别排序 p = merge( p, m, *this, q, n – m ); //归并
} //若归并可在线性时间内完成,则总体运行时间亦为O(nlogn)template<typename T> ListNodePosi<T>
List::merge( ListNodePosi p, int n, List & L, ListNodePosi q, int m ) { ListNodePosi pp = p->pred; //归并之后p或不再指向首节点,故需先记忆,以便返回前更新 while ( ( 0 < m ) && ( q != p ) ) //小者优先归入 if ( ( 0 < n ) && ( p->data <= q->data ) ) { p = p->succ; n--; } //p直接后移 else { insert( L.remove( ( q = q->succ )->pred ) , p ) ); m--; } //q转至p之前 return pp->succ; //更新的首节点
} //运行时间O(n + m),线性正比于节点总数
九、逆序对
1.逆序对(Inversion)
考查序列A[0, n),设元素之间可比较大小
- <i,j> is called an inversion if 0≤i<j<n and A[i]>A[j]
为便于统计,可将逆序对统一记到后者的账上
- I(j)=||{0≤i<j | A[i]>A[j] and hence <i,j> is an inversion}||
实例
- A[] = { 5, 3, 1, 4, 2 } 中,共有 0 + 1 + 2 + 1 + 3 = 7 个逆序对
- A[] = { 1, 2, 3, 4, 5 } 中,共有 0 + 0 + 0 + 0 + 0 = 0 个逆序对
- A[] = { 5, 4, 3, 2, 1 } 中,共有 0 + 1 + 2 + 3 + 4 = 10 个逆序对
显然,逆序对总数 I=∑jI(j)≤(n2)=O(n2)I=\sum_{j}{}I(j)≤{n \choose 2}=O(n^2)I=∑jI(j)≤(2n)=O(n2)
2.起泡排序
- 在序列中交换一对逆序元素,逆序对总数必然减少
在序列中交换一对紧邻的逆序元素,逆序对总数恰好减一
因此对于Bubblesort算法而言,交换操作的次数恰等于若共含 个逆序对,则输入序列所含逆序对的总数
3.插入排序
针对e=A[r]的那一步迭代恰好需要做I®次比较
若共含I个逆序对,则
关键码比较次数为O(I)
运行时间为O(n+I)
4.计数
- 任意给定一个序列,如何统计其中逆序对的总数?
- 蛮力算法需要O(n2n^2n2)时间;而借助归并排序,仅需O(nlognn\log nnlogn)时间
十、游标实现
1.动机与构思
- 某些语言不支持指针类型,即便支持 频繁的动态空间分配也影响总体效率
- 利用线性数组,以游标方式模拟列表
- elem[]:对外可见的数据项
- link[]:数据项之间的引用
- 维护逻辑上互补的列表data和free
2.实例
数据结构(c++)学习笔记--列表相关推荐
- ES6基础4(数据结构)-学习笔记
文章目录 ES6基础4(数据结构)-学习笔记 set map symbol ES6基础4(数据结构)-学习笔记 set //set 数据结构 类似数组 成员信息唯一性var s = new Set() ...
- Python 学习笔记 列表 range() xxx XXX
Python 学习笔记 列表 range() xxx XXX print("-" * 30) for value in range(1, 5):print(value)number ...
- Python 学习笔记 列表 xxx XXX
Python 学习笔记 列表 xxx XXX bicycles = ['trek', 'cannondale', 'redline', 'specialized'] print(bicycles) p ...
- Python 学习笔记 列表 排序 xxx XXX
Python 学习笔记 列表 排序 xxx XXX print("-" * 30) cars = ['bmw', 'audi', 'toyota', 'subaru'] cars. ...
- 数据结构专题-学习笔记:李超线段树
数据结构专题 - 学习笔记:李超线段树 1. 前言 2. 详解 3. 应用 4. 总结 5. 参考资料 1. 前言 本篇博文是博主学习李超线段树的学习笔记. 2020/12/21 的时候我在 线段树算 ...
- python的基本数据结构_Python学习笔记——基本数据结构
列表list List是python的一个内置动态数组对象,它的基本使用方式如下: shoplist = ['apple', 'mango', 'carrot', 'banana'] print 'I ...
- python的基本数据结构_python学习笔记-基本数据结构
Python 学习笔记-1 写在最前面,因为组内小伙伴要走,生信团队由原来的7个人,慢慢的变的只有我一个人了,需要紧急突击下python,因为有python的流程要交接维护 python 基本情况 代 ...
- Python学习笔记 | 列表和字典
基于Python3版本的学习. 列表 用中括号[ ]把里面的各种数据框起来,里面的每一个数据叫作"元素". 每个元素之间都要用英文逗号隔开. list1=['小明',18,1.70 ...
- 数据结构课程学习笔记
整理一下上数据结构课记录的笔记. 第一章 绪论 1.1 数据结构的基本概念 1.2 算法的基本概念 1.2.1 时间复杂度 事前预估算法时间开销T(n)与问题规模n的关系.分析算法操作的执行次数x和问 ...
最新文章
- Sql存储过程加密和解密
- angular 兼容ie7 bootstrap2兼容ie6
- iOS之深入解析“锁”的底层原理
- 其实我就是个技术迷-自身定位及展望
- linux Postfix + dovecot + extmail + extman + mysql
- LeetCode 605 种花问题
- phoenix的元数据一般存在哪里_ElasticSearch文档元数据(Metadata)
- Qt部件学习之-烧鹅
- 2022 老A卡密验证系统
- 【D3 API 中文手册】
- 【蓝桥杯真题】走迷宫算法
- matlab中与或非、等逻辑符号
- 真实如刀的洞见:和扶墙老师聊技术、组织和商业
- JZOJ 6297. 2019.08.10【NOIP提高组A】世界第一的猛汉王
- 研读 项目名-LinkMap-normal-x86_64.txt
- Alist保姆级搭建教程
- 微软官方的精简版Windows 7——Windows Thin PC
- 判断Checkbox选中两种方法
- 201803-4 棋局评估
- SRS(简单实时视频服务) 笔记(5)- 视频录制
热门文章
- 嵌入式工程师面试题集汇总
- Linux----网络传输层UDP/TCP
- 华为智慧屏x65鸿蒙,华为智慧屏 65英寸登录京东:用上鸿蒙OS
- 数据库学习——第三天
- 2022-2028年中国USB键盘行业市场竞争状况及发展趋向分析报告
- css 图片紧贴页面底部,CSS StickyFooter——当内容不足一屏时footer紧贴底部
- 云盘共享产品简单比较
- java矩形派生正方形_从矩形派生正方形是否违反了Liskov的替代原理?
- Python实现视频自动打码功能,避免看到羞羞的画面
- Barsetto百胜图TripressoCA美式便携咖啡机测评——随心玩乐,自由掌握