向量的基本原理-扩容,缩容,插入,删除,唯一化

在线性结构中,各数据项按照一个线性次序构成一个整体。最基本的的线性结构统称为序列(sequence),根据其中数据项的逻辑次序与其物理存储地址对应关系的不同,又可以进一步地将序列区分为向量(vector)和列表(list)。在向量中,所有数据项的物理存放在位置与逻辑次序完全吻合,此时的逻辑次序也称作秩(rank);而在列表中,逻辑上相邻的数据项在物理上未必是相邻,而是采用间接地址的方式通过封装后的位置(position)相互作用。
在这里,我的目的肯定不是为了讲述vector作为一个内置类型的ADT接口用法,而是对其数据结构做一定的分析和记录。

构造与析构
1.默认构造方法
与所有的对象一样,向量在使用之前也需要首先被系统创建–借助构造函数做初始化。其中默认的构造方法是,首先根据创建者指定的初始化容量,向系统申请空间,以创建内部私有数组_elem[]:若容量未明确指定,则使用默认值DEFAULT_CAPACITY。接下来,鉴于初生的向量尚不包含任何元素,故将指示规模的变量_size初始化为0。
这整个过程没有任何迭代,故若忽略用于分配数组空间的时间,共只需常数时间
2.析构方法
向量对象的析构对象:只需要释放用于存放元素的内部数组_elem[],将其占用的空间交还操作系统。_capacity和_size之类的内部变量无需做任何处理,它们作为向量对象自身的一部分被系统回收,此后既无需也无法被引用。

扩容
1.扩容原理
我在看北邮人论坛的时候,看到了一个帖子,面试官问了他vector扩容的机制是什么?所以想要对扩容有一个更深刻印象,是我写这篇文章的初衷。内部数组所占物理空间的容量,若在向量的生命期内不允许调整,则称静态空间策略。向量的实际规模与内部数组容量的比值(_size/_capacity),也称为装填因子(load factor),它是衡量空间利用率的重要指标。我们要做的便是保证向量的装填因子既不超过1,也不太接近于0,我们需要寻找一个balance。所以我们要改用动态空间的策略,使用可扩充向量是一个好方法。

T *oldElem=_elem; //_elem转换成指向数组首元素的指针
_elem=new T[_capacity<<=1];
for(int i=0; i<_size;++i)
         _elem[i]=oldElem[i];//复制原向量内容
delete []oldElem;         

实际上,在调用insert()接口插入之前新元素之前,都要先调用该算法,检查内部数组的可用容量,一旦当前数据区已满(_size==_capacity),则将原来数组替换为一个更大的数组。

这里值得注意的一点是,新数组的地址由操作系统分配,与原数据区没有直接关系。在这种情况下,若直接引用数组(就是直接改变数组的大小),比如说:

T &temp=_elem;
temp=new T[_capacity<<=1];

这种情况往往会导致共同指向原数组的其他指针失效,称为野指针。我也不知道理解是否准确,但这不是关键,关键是新数组的容量总是取作原数组的两倍。

2.时间复杂度
准确地,每一次由n到2n的扩容,都需要花费O(2n)=O(n)时间,这也是最坏情况下,单次插入操作所需要的时间。似乎,效率不高。但是实际上,随着向量规模的不断扩大,在执行插入操作之前需要进行扩容的概率,也迅速降低。

缩容
动态缩容shrik()算法:

if(_capacity<DEFAULT_CAPACITY<<1) return;//不至于收缩到DEFAULT_CAPACITY以下
if(_size<<2>_capacity) return;//以%25为界
T *oldElem=_elem; //_elem转换成指向数组首元素的指针
_elem=new T[_capacity>>=1];
for(int i=0; i<_size;++i)
         _elem[i]=oldElem[i];//复制原向量内容
delete []oldElem;

可见,每次删除操作之后,一旦空间利用率已降至某一阈值以下,该算法随即申请一个容量减半的新数组,将原数组中的元素逐一搬迁至其中,最后将原数组所占空间交还给操作系统。但在实际应用中,为避免出现频繁交替扩容和缩容的情况,可以选用更低的阈值,甚至取做0,相当于禁止缩容。

插入
根据向量ADT定义,插入操作insert(r,e)负责将任意给定的元素e插到任意指定的秩为r的单元:

expand(); //若有必要,扩容
for(int i=_size;i>r;i--){
    _elem[i]=_elem[i-1];
    elem[i]=e;
    _size++;
    return r;
  }

我们可以看到,执行插入操作的时间主要消耗于后继元素的后移,线性正比于后缀元素。可见,新插入元素越靠后(前)所需的时间越短。

删除
我们在这里考虑区间删除:remove(lo,hi)

if(lo==hi) return 0;  //出于效率考虑,我们要考虑退化情况,比如remove(0,0)
while(hi<_size) _elem[lo++]=_elem[hi++];
_size=lo;
shrink();//如有必要,还应该要缩容

remove(lo,hi)的计算成本,只要消耗于后续元素的前移,线性正比于后缀的长度,总体不过O(m+1)=O(_size-hi+1)。这与我们的期望完全吻合:区间操作所需的时间,应该取决于后继元素的数目,而与被删除区间本身的宽度无关。一般来说,本删除元素在向量中的位置越靠后(前)所需时间越短(长),最好为O(1),最坏为O(n)=O(_size)。

唯一化
在很多应用中,在进一步处理之前都要求数据元素互异。下面看看针对无序向量的唯一化算法:

int oldsize=_size;
Rank i=1;
while(i<_size)
     find(_elem[i],0,i)<0 ?i++ :remove(i);

随着循环的不断进行,当前元素的后续持续地严格减少,因此,经过n-2步迭代之后该算法必然终止。
这里所需的时间,主要消耗于find()和remove()两个接口,因此每步迭代所需时间为O(n),总体复杂度应为O( n2 )。

下面来看看一道关于唯一化的面试题目,已知道数组中不重复且按升序排序,使用二分法查找法的递归或者非递归方式,查询数字m是否在数组a中,如果是则返回m在数组a中的位置。

递归解法:

void find(int b[],int target,int lo, int hi, int& res){
    if(lo==hi&&b[lo]==target) {res=lo;return;}
    if(lo==hi) return;
    int mi=(lo+hi)>>1;
    find(b,7,lo,mi,res);
    find(b,7,mi+1,hi,res);
}
int main() {
    int a[8]={0,1,2,3,4,5,6,7};
    int res=10;
    find(a,7,0,7,res);
    cout<<res<<endl;
}

复杂度分析:在这里,递归实例一共用2n-1个,故这个算法的运算时间为O(2n-1)=O(n),二分递归版本的时间复杂度和线性版本的时间复杂度是一样的。其实这个算法还可以做一些优化,比如说当找到这个target的时候,可以直接返回到main函数。所以算法还需要改进。

非递归解法:
关于非递归解法,我觉得应该先给数组排序,这无疑增加了空间复杂度,我觉得还是使用递归版本的算法比较好。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
鱼鹰算法优支持向量机(OOA-SVR)是一种基于鱼鹰算法的支持向量机优方法。支持向量机(Support Vector Machine,SVM)是一种常用的机器学习算法,用于分类和回归问题。 鱼鹰算法是一种基于自然界中鱼鹰捕食行为的优算法。它模拟了鱼鹰在捕食过程中的搜索和追踪策略,通过不断调整搜索空间中的候选解来寻找最优解。 OOA-SVR方法将鱼鹰算法应用于支持向量机的优过程中,以提高支持向量机的性能和精度。其原理如下: 1. 初始种群:根据问题的特点和要求,初始一定数量的鱼鹰个体作为初始种群。 2. 评估适应度:根据支持向量机的目标函数,计算每个个体的适应度值,评估其性能。 3. 更新位置:根据鱼鹰算法的搜索策略,通过调整个体的位置来更新种群。这里的位置表示支持向量机模型中的参数,如权重和偏置。 4. 更新速度:根据个体之间的位置差异和适应度值,更新个体的速度。速度的更新可以帮助个体更好地搜索最优解。 5. 选择操作:根据适应度值,选择一部分个体作为下一代的父代,用于产生新的个体。 6. 交叉和变异:通过交叉和变异操作,生成新的个体,并加入到下一代种群中。 7. 终止条件:根据预设的终止条件,判断是否满足停止迭代的条件。如果满足,则输出最优解;否则,返回第3步继续迭代。 通过以上步骤,OOA-SVR方法能够通过鱼鹰算法的优策略,不断调整支持向量机模型的参数,以找到更优的分类或回归模型。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值