文章目录
数组到向量
C/C++中,数组A[]中的元素[0,n)内的编号一一对应,有A[0],A[1],A[2],…,A[n-1];并且每一个元素均可由(非负)编号唯一指代,并且可以直接访问,A[i]的物理地址是A+i*s(s是单个元素暂用的空间量),也称作线性数组。
向量由数组抽象来,由一组元素按照线性次序封装,各个元素[0,n)内的秩(int ,c++对应iterator),元素类型不限于基本类型,操作、管理和维护更为简化、统一、安全,可以更为便捷的参与复杂数据结构的定制与实现。
优点在于支持内存连续,支持随机访问。但是为了保证内存连续性,插入和删除操作需要对后续所有元素全部执行一次搬移。
c++ std::vector
定义
template<class _Ty, class _Alloc = allocator<_Ty> >
class vector
: public _Vector_alloc<!is_empty<_Alloc>::value,_Vec_base_types<_Ty, _Alloc> >
其中_Vector_alloc又继承了_Vector_val类,_Vector_val有三个指针
pointer _Myfirst; // pointer to beginning of array
pointer _Mylast; // pointer to current end of sequence
pointer _Myend; // pointer to end of array
而_Vector_val类继承了_Container_base类,他有一个指针
_Container_proxy *_Myproxy;
所以构造一个空vector需要16个字节空间。即sizeof(vector())==16
常用接口
-
构造函数:
默认构造一个空向量,size =0=capacity,push_back操作几次后size开始小于等于capacity
支持传入一对迭代器,用其界定的区间元素初始化
支持传入一个对应类型的数组,用以初始化
vector(n,val) 用n个val初始化 -
push_back(T &a) 把元素a接入vector尾部
pop_back(); 把vector尾部元素从向量中剔除。
_Pop_back_n(n) 从尾部弹出n个元素;
这一对操作均无返回值,故而要当作栈使用时需要先调用back()成员获取到末尾的元素再执行弹出操作。由于时尾部插入删除,操作时无额外的操作开销。 -
insert(iterator,val) 从指定位置之前插入val
insert(iterator,count , val) 从指定位置之前插入count个val
insert(iterator,iterator_strat,iterator_end);在指定位置之前插入后面一对迭代器之间的所有元素
erase(iterator) 删除指定位置的元素
erase(iterator_strat,iterator_end) 删除指定区间的所有元素
这一对操作会改变后面所有迭代器的值,用了之后不要轻易调用end()成员。 -
swap(vector<value_type> a) 与同元素类型的vector交换所有元素
clear()清楚容器中的所有元素。
[i ]操作符 返回下表i 的引用,可以访问和修改元素值(但是c++建议使用iterator访问元素值)。 -
assign(count,val) 把count个元素val赋值到容器中
assign(iterator_start,iterator_end) 把迭代器区间的所有元素赋值到容器中。
assign操作会清除原容器中的所有内容 -
resize(n) 将capacity调整为n,如果capacity大于n则删除多余元素,如果capacity小于n则扩容,新增的元素值为val(默认使用类型构造函数,也可以传进去)
reserve(n) 扩容,如果如果capacity大于n则不做操作,如果capacity小于n则开辟一块可以存放n个元素的空间,将数据拷贝过去。
内存自动增长机制:
设置一个数据成员capacity记录当前最多允许存放的元素个数,而size记录当前已经存放的元素个数。当size==capacity的时候立即在找一块足够大的内存空间让capacity翻倍(也有别的扩容策略,不一定是翻倍,但是此策略在扩容时消耗的时间成本总体而言较小,但空间利用率>=50%),把所有元素搬移过去,释放原空间内存。虽然扩容之后物理地址有所改变,却不会出现野指针
STL的做法是设置三个指针记录数据的起始位置,已经存放数据的最后位置,申请的连续内存空间的最后位置。
几个小要点:
- vector不提供内置find、sort 成员,可调用标准库find、sort接口内存允许时可以调优先级队列实现选择排序。
- 插入删除操作会使之后的迭代器失去原本的指向或者直接失效,当有插入删除操作时要确保迭代器指向不变。
- vector是一个c++数据类型,有内置的数据成员,故而村放一个vector所需的内存空间要比存放等数量同类型元素的数组要大。
- vector 的迭代器可以与int值相加减,代表将迭代器右移或左移相应位
有序向量的查找算法
二分查找
将向量分为lo mi hi三个部分,mi取作中点,小于当前值则在左侧递归查找,大于则在右侧递归查找。查找失败返回-1(在B-tree等一些数据结构中,查找失败则返回大于不大于它的最后一个元素所在位置<也即如果查找成功它应该在的位置的前一个位置>)
代码实现:
// 二分查找算法:在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size
//有多个命中元素时,不能保证返回秩最大者;查找失败时,不能指示失败的位置
template <typename T> int binSearch ( T* S, T const& e, int lo, int hi ) {
while ( lo < hi ) {
int mi = lo + ( hi - lo ) / 2;
if( e < S[mi] ) hi = mi;
else if ( S[mi] < e ) lo = mi + 1;
else return mi;
} //成功查找提前终止
return -1; //查找失败
}
// 二分查找算法:在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size
//有多个命中元素时,总能保证返回秩最大者;查找失败时,能够返回失败的位置,若需要插入该元素应插入返回值之后的位置(失败时返回值位置可能不可访问,如-1表示应在最前面插入)
template <typename T> int binSearch ( T* S, T const& e, int lo, int hi,bool fail/*指示是否失败,任意值均可*/ )
{
while ( lo < hi ) {
//每步迭代仅需做一次比较判断,有两个分支
int mi = lo +( hi - lo ) / 2;
( e < S[mi] ) ? hi = mi : lo = mi + 1;
} //成功查找不能提前终止
fail = S[ lo - 1 ] != e;
return lo - 1;
}
平均时间复杂度是T(n) = T(n/2) + O(1) = O(lgn) <其实是1.5lgn,对每一次比较,成功只比较1次,失败会比较2次。转向的代价不一导致的比较次数不等而递归深度相同>
改进每次以黄金分割比比较而不是中点,可继续优化至平均复杂度O(1.44lgn):
//Fibonacci数列类
class Fib {
private:
int f, g; //f = fib(k - 1), g = fib(k)。
public:
Fib ( int n ) //初始化为不小于n的最小Fibonacci项
{
f = 1; g = 0; while ( g < n ) next(); } //fib(-1), fib(0),O(log_phi(n))时间
int get() {
return g; } //获取当前Fibonacci项,O(1)时间
int next() {
g += f; f = g - f; return g; } //转至下一Fibonacci项,O(1)时间
int prev() {
f = g - f; g -= f; return g; } //转至上一Fibonacci项,O(1)时间
};
//有多个命中元素时,不能保证返回秩最大者&#x