无序向量
template <typename T> class Vector {}; // template定义方式 |
模板和模板, 模板和类之间可以互相组合。意味着数据结构之间也可以互相组合
template <typename T> class Vector { |
无序向量: 没有顺序, 甚至不可能排成顺序。
元素访问(寻秩访问)
v.get(r), v.put(e)
A[r]
重载下标运算符”[]”// 寻秩访问
/* // my test
template <typename T>
T& Vector<T>::operator[](std::size_t n) { // 这个类 Vector<T>
assert(n < _size);
return _elem[n];
}
*/
template <typename T>
T& Vector<T>::operator[](Rank r) const { // 不改变数据成员, 定义成常量成员函数
// 在vector内部, 定义了秩的类型, 统一用Rank
assert(r < _size); // 对下标秩进行溢出检测
return _elem[r];
}
左值, 右值, 引用??
引用类型可作为左值。
寻秩访问
代码健壮性简化
- assert 断言,
assert(r < _size);
插入
// 插入 |
Template中泛型T的作用,
模板类中函数的互相搭配,
插入元素对vector操作的顺序
对_capacity和_size的影响。
删除算法
自前向后的迁移操作
缩容
// 删除操作 |
1, 规模仍旧不变? 删除一段区间, 这里可以不改变规模, 相当于后面留空?
改进成改变size的版本用于shrunk2 , _elem[hi++]能够被一直索引到?
超过_capacity时, 返回未定义的值3, _elem[hi++]为什么不清空?
把_capacity的剩余空间对应元素赋值给它的方法清空4, 看出移动操作过程中, 变量的同步性
5, 缩容不光光是改变_capacity的值, 仍旧要释放空间
查找
无序向量: T为可判等, 重载 “==”或者”!=”
有序向量: T为可比较,重载 “<” 或 “>”
// 查找 |
利用while本身的条件语句;后置递增的特性
返回hi?
将判断是否成功, 交给上层的调用者
;以及成功后被上层算法
进一步利用
最好情况 O(1), 最坏情况O(n)
输入敏感(input-sensitive): 最好情况和最坏情况相差悬殊的算法。
删除单元素
视为区间操作的特例
// 删除单个元素
/* my test code
template <typename T>
void Vector<T>::remove(Rank r) {
// 单元素的删除操作, 视为区间操作的特例 [r, r+1)
remove(r, r+1);
}
*/
template <typename T> // 删除向量中秩为r的元素, 0 <= r < size
T& Vector<T>::remove(Rank r) { // O(n-r)
T& old_t = _elem[r]; // 备份被删除的元素
remove(r, r+1); // 调用区间删除算法
return old_t; // 返回被删除元素
}
颠倒考虑
复杂度分析:
每次循环的耗时正比于删除区间的后缀长度 O(n-hi)
循环的次数等于区间宽度 O(hi-lo)
总体 O(n^2) 复杂度
right, but not fast!!
唯一化
实现
// 唯一化 |
记录规模?
考虑返回值怎么设计,这里我估计会在更高级的接口中用到从1开始?
因为要在当前i的前缀中查找至多一个?
删除至多一个, 实际不一定至多一个为什么不需要改变_size和_capacity大小?
说明remove为更底层的操作, 在底层操作中, 定义底层的数据成员的修改, 这样做的话, 在更高级的操作中, 可以不去管这些细节。也是封装调用的好处。
**正确性证明**
:
不变性: 对于当前元素V[i]的前缀V[0, i)中, 各元素彼此互异
初始时, i = 1, 两个元素成立,…
单调性:
1)前缀单调非降, 后缀减少, 且前缀迟早增至_size; // 与1)结合
2)后缀单调下降, _size迟早减至0; // 2)更容易把握
故算法必然终止, 至多迭代O(n)轮
复杂度
主要来自find() 和 remove();
find()从右向左针对前缀, remove()从左向右针对后缀, 因此每次while循环不超过O(n);
while循环会进行n次,
总体复杂度为 O(n^2)
练习三种优化方案(未完成)
1, 仿照uniquify()高效版本的思路.
元素移动的次数可以降至O(n), 但比较次数依然是O(n^2); 而且破坏稳定性
2, 先对需要删除的重复元素标记, 然后统一删除.
稳定性保持, 但因查找长度更长, 从而导致更多的对比操作
3, V.sort().uniquify(): 简明实现最优的O(nlogn)
遍历
visit
关于函数指针和函数对象的笔记
函数指针机制 ??
函数对象机制 ??
两种方法优劣
实例: 将向量中所有的元素统一加一
重载操作符 “++”
重载操作符 “()”
练习更为复杂的遍历
减1
// 遍历运用函数对象机制,对各个元素减1 |
template <typename T> |
为什么不是Vector::struct Decrease?
// Decrease对象, 不需要声明在vector类内, 这是借用函数对象作为traverse的参数, traverse声明在类内, 这个参数的类型是VST, 即函数对象。
为什么需要virtual
void operator()(T &e)?
// 第一个括号是代表重载运算符是(), 第二个是该重载函数的参数列表
三个函数之间的关系?
Decrease
// 在类的外部定义decrease函数, 它具有泛型T, 参数为Vector
// decrease()函数内部, 实例V调用traverse方法, 通过多次调用这个函数对象去遍历所有元素
为什么需要函数对象?
本质问题: 为什么需要函数对象or 函数指针, 为什么不直接调用函数呢?
将遍历traverse作为一种统一的接口, 具体的操作让其他的函数对象来完成, 这样子的接口相对清晰。
加倍
Double(T &e)
template <typename T> |
求和
函数中传入指针
Sum(T& e, T* sumPtr)
// 用于traverse的求和函数对象 |
Sum()函数对象中传入的参数, 在traverse的visit中定义好的, 因此需要重载traverse函数, 让它传入一个保存求和结果的指针。
前面都没什么问题,
实际调用的函数, 第一次我是这样写的, 返回的结果正确无误。template <typename T>
void sum_ptr(Vector<T>& V) {
T* sumPtr = &(V[0]); // 别定义空指针, 该指针指向向量首位
T sum = V.traverse(Sum<T>(), sumPtr); // 调用并赋值
std::cout << "sum = " << sum << std::endl;
}
但print_vector()之后-- --------print_vector--------- --
_size = 3
_capacity = 10
0:52 ==> _elem的首位被修改, 原本应该是10
1:24
2:18
3:0
4:0
5:0
6:0
7:0
8:0
9:0
因为Sum函数对象中, 会通过解引用指针, 不小心修改了V[0]的值!!!
于是我便做了如下修改:template <typename T>
void sum_ptr(Vector<T>& V) {
T sum_init = V[0]; // 创建一个副本, 避免通过指针修改V[0]
T* sumPtr = &(sum_init); // 别定义空指针, 该指针指向副本
T sum = V.traverse(Sum<T>(), sumPtr); // 调用并赋值
std::cout << "sum = " << sum << std::endl;
}
-- --------print_vector--------- -- |
重载函数什么时候用virtual?
类内static存放
static sumStc
静态数据成员在类内部声明, 外部定义, 作用相当于全局变量。// 通过类内static求和的函数对象
template <typename T>
struct Sum_static {
static T sum_t; // 内部声明存放求和结果的static变量
virtual void operator()(T& e) {
sum_t += e; std::cout << "revise static sum_t, sum_t = "
<< sum_t << std::endl; // 当心全局变量出错
}
};
template <typename T>
T Sum_static<T>::sum_t = 0; // 外部定义静态数据成员
template <typename T>
void sum_static(Vector<T> V) {
Sum_static<T> sumstc; // 实例化模板别忘记<type>
V.traverse(Sum_static<T>());
T sum = sumstc.sum_t;
std::cout << "in sum_static, sum = " << sum << std::endl;
}
关于static, 我的相关练习source code