第二章 向量
向量(vector)是线性数组的一种抽象与泛化。
从向量最基本的接口出发,设计并实现与之对应的向量模板类。
a.向量ADT支持的操作接口
操作接口 | 功能 | 适用对象 |
---|---|---|
size() | 报告向量当前的规模(元素总数) | 向量 |
get(r) | 获取秩为r的元素 | 向量 |
put(r, e) | 用e替换秩为r的元素 | 向量 |
insert(r, e) | e作为秩为r元素插入,后继元素依次右移 | 向量 |
remove(r) | 删除秩为r的元素,返回该元素中原存放的对象 | 向量 |
disordered() | 判断所有元素是否已按非降序排列 | 向量 |
sort() | 调整各元素位置,使之按非降序排列 | 向量 |
find(e) | 查找等于e且秩最大的元素 | 向量 |
search(e) | 查找目标元素e,返回不大于e且秩最大的元素 | 有序向量 |
deduplicate() | 剔除重复元素 | 向量 |
uniquify() | 剔除重复元素 | 有序向量 |
traverse() | 遍历向量并统一处理所有元素,处理方法由函数对象指定 | 向量 |
b.Vector模板类
typedef int Rank; //秩
#define DEFAULT_CAPACITY 3 //默认的初始容量(实际应用中可设置为更大)
template class Vector { //向量模板类
protected:
Rank _size; int _capacity; T* _elem; //规模、容量、数据区
void copyFrom ( T const* A, Rank lo, Rank hi ); //复制数组区间A[lo, hi)
void expand(); //空间不足时扩容
void shrink(); //装填因子过小时压缩
bool bubble ( Rank lo, Rank hi ); //扫描交换
void bubbleSort ( Rank lo, Rank hi ); //起泡排序算法
Rank max ( Rank lo, Rank hi ); //选取最大元素
void selectionSort ( Rank lo, Rank hi ); //选择排序算法
void merge ( Rank lo, Rank mi, Rank hi ); //归并算法
void mergeSort ( Rank lo, Rank hi ); //归并排序算法
void heapSort ( Rank lo, Rank hi ); //堆排序(稍后结合完全堆讲解)
Rank partition ( Rank lo, Rank hi ); //轴点构造算法
void quickSort ( Rank lo, Rank hi ); //快速排序算法
void shellSort ( Rank lo, Rank hi ); //希尔排序算法
public:
// 构造函数
Vector ( int c = DEFAULT_CAPACITY, int s = 0, T v = 0 ) //容量为c、规模为s、所有元素初始为v
{ _elem = new T[_capacity = c]; for ( _size = 0; _size < s; _elem[_size++] = v ); } //s<=c
Vector ( T const* A, Rank n ) { copyFrom ( A, 0, n ); } //数组整体复制
Vector ( T const* A, Rank lo, Rank hi ) { copyFrom ( A, lo, hi ); } //区间
Vector ( Vector const& V ) { copyFrom ( V._elem, 0, V._size ); } //向量整体复制
Vector ( Vector const& V, Rank lo, Rank hi ) { copyFrom ( V._elem, lo, hi ); } //区间
// 析构函数
~Vector() { delete [] _elem; } //释放内部空间
// 只读访问接口
Rank size() const { return _size; } //规模
bool empty() const { return !_size; } //判空
Rank find ( T const& e ) const { return find ( e, 0, _size ); } //无序向量整体查找
Rank find ( T const& e, Rank lo, Rank hi ) const; //无序向量区间查找
Rank search ( T const& e ) const //有序向量整体查找
{ return ( 0 >= _size ) ? -1 : search ( e, 0, _size ); }
Rank search ( T const& e, Rank lo, Rank hi ) const; //有序向量区间查找
// 可写访问接口
T& operator[] ( Rank r ); //重载下标操作符,可以类似于数组形式引用各元素
const T& operator[] ( Rank r ) const; //仅限于做右值的重载版本
Vector & operator= ( Vector const& ); //重载赋值操作符,以便直接克隆向量
T remove ( Rank r ); //删除秩为r的元素
int remove ( Rank lo, Rank hi ); //删除秩在区间[lo, hi)之内的元素
Rank insert ( Rank r, T const& e ); //插入元素
Rank insert ( T const& e ) { return insert ( _size, e ); } //默认作为末元素插入
void sort ( Rank lo, Rank hi ); //对[lo, hi)排序
void sort() { sort ( 0, _size ); } //整体排序
void unsort ( Rank lo, Rank hi ); //对[lo, hi)置乱
void unsort() { unsort ( 0, _size ); } //整体置乱
int deduplicate(); //无序去重
int uniquify(); //有序去重
// 遍历
void traverse ( void (* ) ( T& ) ); //遍历(使用函数指针,只读或局部性修改)
template void traverse ( VST& ); //遍历(使用函数对象,可全局性修改)
}; //Vector
c.构造和析构
基于复制的构造方法
template //元素类型
void Vector::copyFrom ( T const* A, Rank lo, Rank hi ) { //以数组区间A[lo, hi)为蓝本复制向量
_elem = new T[_capacity = 2 * ( hi - lo ) ]; _size = 0; //分配空间,规模清零
while ( lo < hi ) //A[lo, hi)内的元素逐一
_elem[_size++] = A[lo++]; //复制至_elem[0, hi - lo)
}
需强调的是,由于向量内部含有动态分配的空间,默认的运算符“=”不足以支持向量之间的直接赋值。
故重载向量的赋值运算符。
template Vector::operator= (Vector const& V){ //重载
if (_elem) delete [] _elem; //释放原有内容
copyFrom ( V._elem, 0, V.size() ); //整体复制
return *this; //返回当前对象的引用,以便链式赋值
}
c.动态空间管理
1.静态空间管理
内部数组所占物理空间的容量,若在向量的生命期内不允许调整,则为静态管理策略。
缺点:因容量固定,总有可能在此后的某一时刻,无法加入更多的新元素--即导致所谓的上溢(overflow)。
也有可能拥有少量元素的数组长时间占据大量物理空间,造成空间浪费。
2.可扩充向量
template void Vector::expand() { //向量空间不足时扩容
if ( _size < _capacity ) return; //尚未满员时,不必扩容
if ( _capacity < DEFAULT_CAPACITY ) _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; //释放原空间
}
该扩容策略是容量加倍策略
分析:最坏情况:在初始容量为1的满向量中,连续插入n=2 ^ m>>2个元素,在第1、2、4、8、16次插入都需要扩容,每次扩容中复制原向量的时间成本分别为1,2,4,8,…,2^m = n,总体耗时O(n),每次扩容的分摊成本为O(1)
平均分析复杂度:根据数据结构各种操作出现概率的分布,将对应的成本加权平均,各种可能的操作作为独立事件分别考查,割裂了操作之间的相关性和连贯性,不能准确评判数据结构和算法的真实性能;
分摊复杂度:对数据结构连续实施足够多次操作,所需总成本分摊至单次操作,对一系列操作做整体的考量。
另外,追加固定数目的单元的策略,无论采用的固定常数多大,在最坏的情况下,此类数组单次操作的分摊时间复杂度都将高达Ω(n)。
3.缩小容量
template void Vector::shrink() { //装填因子过小时压缩向量所占空间
if ( _capacity < DEFAULT_CAPACITY << 1 ) return; //不致收缩到DEFAULT_CAPACITY以下
if ( _size << 2 > _capacity ) return; //以25%为界
T* oldElem = _elem; _elem = new T[_capacity >>= 1]; //容量减半
for ( int i = 0; i < _size; i++ ) _elem[i] = oldElem[i]; //复制原向量内容
delete [] oldElem; //释放原空间
}
d.常规向量
1.直接引用元素
template
T& Vector::operator[] ( Rank r ) const //重载下标操作符
{ return _elem[r]; } //assert: 0 <= r < _size
此后对外的V[r]对应内部V._elem[r]
右值:T x = V[r] + U[s] * W[t];
左值:V[r] = (T)(2*x + 3)
2.插入
template //e作为秩为r元素插入
Rank Vector::insert(Rank r, T const & e){
expand(); //如有必要,扩容
for (int i=_size; i>r; i--)
_elem[i] = _elem[i-1]; //后继元素顺次后移一个单元
_elem[r] = e; _size++;
return r; //返回秩
}
3.区间删除
template //e作为秩为r元素插入
Rank Vector::insert(Rank r, T const & e){
expand(); //如有必要,扩容
for (int i=_size; i>r; i--)
_elem[i] = _elem[i-1]; //后继元素顺次后移一个单元
_elem[r] = e; _size++;
return r; //返回秩
}
4.单元素删除
即区间删除的特例:[r] = [r, r+1)
template //删除向量中秩为r的元素
T Vector::remove(Rank r){
T e = _elem[r]; //备份被删除元素
remove(r,r+1);
return e;
}
5.查找
无序向量:T为可判等的基本类型,或已重载操作符"==“或”!="
有序向量:T为可比较的基本类型,或已重载操作符"<“或”>"
template
Rank Vector::find(T const & e, Rank lo, Rank hi) const{
while((lo < hi--) && (e!=_elem[hi]));
return hi;
}
Question:能否反复调用remove(r)实现remove(lo,hi)呢?
删除单元素每次循环耗时正比于删除区间的后缀长度 = n - hi = O(n),循环次数等于区间宽度=hi - lo = O(n),如此将导致O(n^2)的复杂度。