THU_数据结构_学习笔记(6)

数据结构,邓俊辉老师
平台——学堂在线 https://www.xuetangx.com/course/THU08091000384/5883586?channel=learn_title

向量,基本的数据结构

讨论两个问题

  1. 如何定制和实现数据结构
  2. 如何通过更有效的算法,使对外接口更加高效率的工作

+辨析

抽象数据类型和数据结构 Abstract Data Type vs. Data Structure

将数据结构比喻为某种产品,比如汽车
两类人,应用(客户)和
这两类人所关心的和职责不同,
用户只关心外在特性和如何实现,而实现者则需要对这些功能如何显示和落实负责
这两者之间,有某种形式的协议—— 使用说明书,手册

从数组到向量

向量的理解,由一组元素按线性次序封装而成
所有元素的物理地址,可按照线性方式确定
各元素与[0,n)内的秩(rank)一一对应,寻秩访问

元素类型
不限于基本类型
可参与更加复杂的数据结构的实现
存在向量ADT接口,功能非常丰富

  • 比如报告向量当前规模、获取秩为r的元素,修改特定元素,插入,删除;也可以查看是否有序排列,也可以在尚未有序时按一定顺序排列,逐一进行枚举甚至访问一遍
  • 在这里插入图片描述

在逻辑上,甚至物理上,紧密相连

各类接口,不关心如何实现,只关心它们的语义,不关心实现细节,只关心功能

Vector模板类的主体框架

内部封装的变量 int _size, int _capacity, T* _elem //规模、容量、数据区
用户完全可以按照说明书,通过接口规范直接使用
+课堂截图,通过“玻璃”后,应用实现之间很好分工并很好协作
在这里插入图片描述

+了解,构造与析构

+形象理解左闭右开,把hi理解为一个哨兵

  • 在这里插入图片描述
    +代码
template <typename T> //T为基本类型,或已重载赋值操作符‘=’
void Vector<T>::copyFrom(T* const A, Rank lo, Rank hi){
	_elem = new T[_capacity = 2*(hi - lo)]; //分配空间
	_size = 0 // 规模清零
	while (lo < hi) //A[lo, hi)内的元素逐一
		_elem[_size++] = A[lo++]; //复制至_elem[0, hi=lo)
}
  • 实际规模_size ; 物理容量_capacity
  • 分配空间时,*2的原因,不必要因为扩容而打断计算过程
  • 之后顺次存入到_elem区间中去

可扩充向量

+目标

  • 需要能自适应地扩充空间
  • 需要采取一些策略,采取聪明的策略

+回忆内部私有的数组

+问题,

  • 上溢:_elem[] 不足以存放所有元素
  • 下溢,_elem[]中的元素寥寥无几,空间效率地下
    更糟糕的是,一般应用环境难以准确预测空间的需求量

+问题,可否使得向量可随实际需求动态调整容量呢?可以

动态空间管理

模仿蝉的哲学:
身体每经过一段时间的生长,以致无法为外壳容纳,即蜕去原先的外壳,代之以一个新的外壳

+理解目标,在即将发生上溢时,向量适当地扩大内部数组的容量
+理解物理过程,动态申请另一个“外壳”,原来有效元素逐一复制,新多出的空间足以存放,原来的空间释放并且归还给系统
+理解数据结构的好处

template <typename T>
void Vector<T>::expand(){
	if (_size < _capacity) return; //尚未满员时,不必扩容
	_capacity = max(_capacity, DEFAULT_CAPACITY); //不低于最小容量
	T* oldElem = _elem; _elem = new T[_capacity <<= 1]; //容量加倍
	for(int i  = 0; i < _size; i++)// 复制原向量内容
		_elem[i] = oldElem[i]; // T为基本类型,或已重载赋值操作符'='
	delete [] oldElem; //释放原空间
}
  • 得益于向量的封装,尽管扩容之后数据区的物理地址有所改变,无论此前此后访问具体元素时,都是通过_elem这个统一的指示器标识数组的起点,因此不会出现野指针的情况。

+问题,为什么必须采用容量加倍的策略?
情况并不是那么简单,
考虑容量递增策略,只在原来容量基础上,追加一个固定的数额
“_capacity += INCREMENT”,
每次扩容过程中复制原向量时间成本依次为0, I, 2I, …, (m-1)I
每次扩容的分摊成本为O(n),成本高

容量加倍策略

在第1、2、4、8、16、…,2^m = n次插入时需要扩容
几何级数,与末项同阶,总体耗时O(n),每次扩容的分摊成本为O(1)

+课堂截图,直观对比
在这里插入图片描述

+小总结
倍增策略,通过在空间效率上做了一个牺牲,换取时间方面巨大的收益

平均分析 vs. 分摊分析

  • 平均分析
    对可能的操作,作为独立事件分别考查,割裂了操作之间的相关性和连贯性

  • 分摊复杂度 ✔ amortized complexity
    对数据结构连续地实施足够多次操作,分摊至单次操作,可以更为精准地评判数据结构和算法的真实性能

无序向量

通用方法Template
从 a vector of integers, floats…
到 a vector of Binary Trees——> forest

简单结构互相融合组合
更为复杂的数据结构

*自己笔记:类似于烧菜,简单炒蛋到番茄培根炒蛋(口水)2333

操作实现

1)元素访问(循秩访问)

C++语言中,重载下标操作符‘[]’
在这里插入图片描述
补充,在入口处增加“断言”,保证入口参数r可以在合理范围内;真正实际应用中,做更加严格的处理

2)元素插入

  • +关键,后继元素右移时,次序后者优先
    疑问颠倒如何?出现元素被覆盖的危险
  • 在后移前,若有必要,扩容

3)区间删除

  • 次序同样敏感,自前向后的前移操作,思考特殊情况:区间重复
  • 在这里插入图片描述
    4)单元素删除
  • 备份删除元素,调用区间删除算法,返回
  • 若在区间删除里多次运用但元素删除,方法可行,效率不行

5)查找

  • 判等和比较,不是所有类型都支持
    在这里插入图片描述
  • 退出条件—— 对应C1&&C2有两种情况
    1)确实在某个位置发现了某个元素
    2)一直持续到最后(已越过lo的有效范围),返回hi < lo 意味着失败
  • *自己笔记:注意while C1&&C2的前后关系,先判定C1,成立下再审查C2;顺序与效率也有关系,具体操作应注意
  • 一种输入敏感的算法,时间复杂度与输入配置紧密相关

6)唯一化:算法
实例,网格化去重:
自前向后逐一考查各元素_elem[i],查找前缀序列存在同元素则移除
证明方法—— 不变性和单调性

  • 不变性,归纳法:初始化i = 1自然成立;其余一般情况下分析
  • 单调性:前缀序列单调非增,后缀长度单调下降,必然严格终止

优化方案:V.sort().uniquify() 一种combined的算法,简明实现O(nlogn)

7)遍历

  • 指针操作 在这里插入图片描述

  • 对象操作 ✔通用性更强
    在这里插入图片描述

  • 对象操作实例,统一将所有元素加一

template <typename T>
struct Increase{
    virtual void operator()(T & e){e++;} //加一
};

template<typename T> void increase(Vector<T> &v){
    v.traverse(Increase<T>()); //即可以之为基本操作遍历向量
}

总结
*连续型线性存储的结构,对操作实现的先后次序敏感,具体编程中应予以注意
+The same abstract data type may be implemented using multiple data structures. 同一个抽象数据类型可能用多种数据结构实现。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值