邓俊辉<<数据结构>>-公开课-02-D1


觉得markdown格式的页面, 排版很不舒服。虽然有标题的概念, 但是标题字体大小不容易分清, 结构不是显而易见。尝试使用不带格式的记笔记,仍然保留markdown语法, 以便复制对比效果。
------------------------------ split line ---------------------------
实现源码[dsacpp - vector_ordered](https://github.com/timtingwei/dsacpp/blob/master/src/02/vector_ordered.cpp)

# 有序向量唯一化
无序: 比对
有序: 比较

## 有序性及其甄别

> * 有序/无序序列中, 任意/总有一对相邻元素顺序/逆序。

> * 相邻逆序对数目, 可以度量逆序程度。

> * 逆序程度实现

===================== source code =========================
// 逆序程度
/* my test code
template <typename T>
int Vector<T>::disordered() const {
int count = 0;
for (int i = 0; i < _size - 1; i++)
if (_elem[i] != _elem[i+1]) count++; // ERROR: 逆序:前个元素大于后一元素
return count;
}
*/

template <typename T>
int Vector<T>::disordered() const {
int count = 0; // 计数器
for (int i = i; i < _size; i++)
count += (_elem[i-1] > _elem[i]); // 逆序对则计数+1, bool和int的隐式转换
return count; // 向量有序当且仅当n = 0
} // 若只需要判断是否有序, 首次遇到逆序对之后, 就立即终止
============================================================

> * 若元素支持大小比较, 可将无序向量转换为有序向量。
> * 无序向量转换为有序向量后, 算法多可优化。

## 低效算法

> 1, 我自己的低效deduplicate()版本,
依赖一次性remove函数.
> * 尝试自己实现,

======================== sourece code =======================
// my deduplicate code
// 唯一化(低效)
int deduplicate_lower(int rm_arr[]);
// 唯一化所依赖的通过索引数组一次性remove函数
void remove(int rm_arr[], int n);

template <typename T>
int Vector<T>::deduplicate_lower(int rm_arr[]) {
// 删除有序向量重复的元素, rm_arr是保存删除对象索引数组, 返回被删除元素的数量
assert(!disordered()); // 当前为有序向量
Rank n = 0; // 数组当前插入位置
Rank r1 = 0, r2 = 1; // 创建两个索引值线性扫描
while (r2 < _size) {
if (_elem[r1] == _elem[r2]) { // r2指向的元素和r1对应元素重复
rm_arr[n++] = r2; // 索引数组中加入r2
// remove(r2);
} else { r1 = r2;}
r2++; // 递增r2
}
remove(rm_arr, n); // 一次性删除索引对应的元素
return n; // 返回删除元素数量
}


// 有序向量唯一化所依赖的通过索引数组一次性remove函数
template <typename T>
void Vector<T>::remove(int rm_arr[], int n) {
// 删除索引除外的索引对应元素保留, 从左向右扫描
int i_n = 0; // 指向rm_arr中的元素
int new_i = 0; // 保留索引
T* old_elem = _elem; // 备份一份当前元素
_elem = new T[_capacity = _capacity];
for (int i = 0; i < _size; i++) {
if (i != rm_arr[i_n]) { // 当前索引不在表中
_elem[new_i++] = old_elem[i]; // 当前索引对应元素赋值给当前元素的新位置
} else { // 当前索引在删除索引的表中
i_n++; // 指向下一个rm_arr中的元素
}
}
_size -= n;
delete [] old_elem;
}
=============================================================

> 2, 视频中的唯一化(低效版)
代码如下:
======================= source code ========================
// 有序向量唯一化
template <typename T>
int Vector<T>::uniquify() {
int old_size = _size; int i = 0; // 首元素开始
while (i < _size - 1) {
(_elem[i+1] == _elem[i]) ? remove(i+1) : i++;
} // _size的改变由remove隐式完成
return old_size - _size;
}
============================================================

## 低效算法: 复杂度
> 1, 低效复杂度
运行时间取决于while循环, 总计 _size - 1 = n - 1
最坏情况: 每次调用remove(), 耗时O(n)~ O(1), 累计O(n^2)
> 2, 比较
我实现了一次性remove的操作, 应该可以将O(n^2)的复杂度降到O(n)


## 高效算法:
> 1, 反思
低效来自于, 同一元素可作为删除元素的后继, 而多次参与前移。
> 2, 启示
将重复区间视为单位, 成批的删除
> 3, 算法
[i] [] [ . . . . duplicates] [j]
从i开始扫描, 直达找到不相等的元素j, 将j移动到i的后一位置。

高明之处在于: 实际上没有删除, 却等效于删除
================== source code ==============================
/* my test code
template <typename>
int Vector<T>::uniquify() {
int old_size = _size;
int i = 0, j = 1; // i指向首位置
int n = 1; // 实际更新后的_elem的索引
while (i < _size - 1)
if (_elem[i] != _elem[j])
{ _elem[n++] = _elem[j]; i = j; size--;
} else {j++;}
return old_size - n;
}
*/

template <typename T>
int Vector<T>::uniquify_faster() {
int i = 0, j = 0; // 两个计数都指向首元素
while (++j < _size) { // j扫描到末尾
if (_elem[i] != _elem[j]) {_elem[++i] = _elem[j];} // j指向元素移到i后
}
_size = ++i; shrunk(); // 改变_size数值后, 缩容
return j - i; // 注意j扫到尾端的特性
} // 依赖shrink();
============================================================

/* 实现问题分析
// 为什么不要另外的n作为索引计数?
当j移动至i后, 计数i也增加, 重新指向移动位置后的j, 不需要对原有那份的拷贝即可完成索引操作。记录位置只用i。算法设计时,也考虑一个操作是否必要。一份复制是否必要。
// 为什么不在uniquify中进行new 和 delete?
接口分离的体现, 在函数中改变_size, 通过_size的改变, 再由shrunk/expand对空间进行操作。
*/

## 高效算法: 实例与复杂度
> 1, *反思 (典型例子)
共计n-1次迭代, 每次常数时间, 累计O(n)时间。

优化之处在于, 没有亦步亦趋的移动, 而是直接移动到最终的位置;又只通过计算得到_size规模, 没有任何显式的删除操作。
体现了, 一个差的算法, 在经过反思之后, 拟定的改进策略, 最终使得效率有所改进。