向量

  • ADT接口
  • Vector模板类
    • 初始化函数
    • 运算符重载
    • 动态空间管理
    • 逆序与置乱
    • 排序
    • 插入与查找
    • 有序向量查找
    • 删除
    • 去重
    • 遍历(批量)操作

参考书籍:《数据结构(C++语言版)》邓俊辉著

向量(vector)是最基本的线性数据结构——序列(sequence)中的一种。向量中。所有数据项的物理存放位置与其逻辑次序一致,其逻辑次序被称作秩(rank)。

C++中已经有实现向量的库<vector>,我们不使用该库,而是仿照它实现一种基于向量的抽象数据类型(Abstract Data Type,ADT)。

ADT接口

下面列出需要实现的接口:

操作接口 功能 适用对象
size ( ) 返回向量规模 向量
get ( r ) 获取秩为r的元素 向量
put ( r, e ) 用e替换秩为r的元素 向量
insert ( r, e ) 在秩为r的位置插入元素e 向量
remove ( r ) 删除秩为r的对象并返回 向量
disordered ( ) 判断是否逆序,返回逆序数 向量
sort ( ) 非降序重新排列 向量
find ( e ) 查找目标元素,返回最大的秩 向量
search ( e ) 查找目标元素,返回最大的秩 有序向量
deduplicate ( ) 删除重复元素 向量
uniquify ( ) 删除重复元素 有序向量
traverse ( ) 遍历向量并统一处理 向量

Vector模板类

下面定义Vector模板类:

typedef int Rank;
#define DEFAULT_CAPACITY 3template <typename T>
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);              //冒泡排序的基本操作,扫描交换
public://构造函数Vector(int c = DEFAULT_CAPACITY, int s = 0, T v = 0){_elem = new T[_capacity = c];for (_size = 0; _size < s; _elem[_size++] = v);}Vector(T const *A, Rank n) { copyFrom(A, 0, n); }                           //从数组复制Vector(T const *A, Rank lo, Rank hi) { copyFrom(A, lo, hi); }               //复制数组区间Vector(Vector<T> const &V) { copyFrom(V._elem, 0, V._size); }               //拷贝构造Vector(Vector<T> const &V, Rank lo, Rank hi) { copyFrom(V._elem, lo, hi); } //复制向量区间//析构函数~Vector() { delete[] _elem; } //删除数组//只读接口Rank size() const { return _size; }                                               //返回最大秩bool empty() const { return !_size; }                                             //判空int disordered() const;                                                           //返回逆序数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 (_size <= 0) ? -1 : search(e, 0, _size); } //有序向量整体查找Rank search(T const &e, Rank lo, Rank hi) const;                                  //有序向量区间查找//可写入接口T &operator[](Rank r) const;                         //重载[]操作符,使其能够像数组一样引用元素Vector<T> &operator=(Vector<T> const &);             //重载=操作符,使其能够向数组一样赋值T remove(Rank r);                                    //删除秩为r的元素Rank insert(Rank r, T const &e);                     //在秩为r的位置插入元素eRank insert(T const &e) { return insert(_size, e); } //在末尾插入元素evoid 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 <typename VST>void traverse(VST &); //使用函数对象操作
};

对于只读的接口,尽量将其声明为const类型。

初始化函数

由于向量的特性,我们选择数组这一结构作为向量类的基本元素单元,因为数组在内存中的物理地址与其逻辑次序一致。

构造函数在类声明中已经内联的实现了,下面实现内部的复制函数:

template <typename T>
void Vector<T>::copyFrom(T const *A, Rank lo, Rank hi)
{_elem = new T[_capacity = 2 * (hi - lo)]; //申请空间_size = 0;                                //规模置零while (lo < hi){_elem[_size++] = A[lo++]; //逐个复制}
}

根据模板参数T的不同,向量模板会自动请求合适的内存。

运算符重载

我们目前只需要重载=赋值操作符和[]取元素操作符:

template <typename T>
Vector<T> &Vector<T>::operator=(const Vector<T> &V)
{delete[] _elem;    //删除原有空间,因为下面会申请新的空间copyFrom(V._elem, 0, V._size);return *this; //返回值为引用便于链式赋值
}

返回值为引用,这样就可以实现链式赋值(即连等)。

template <typename T>
T &Vector<T>::operator[](Rank r) const
{return _elem[r];
}

动态空间管理

在可用空间足够小时扩容,在剩余空间足够大时缩容,一般发生在向量修改前后:

template <typename T>
void Vector<T>::expand()
{while (_size == _capacity) //若实际规模等于容量{T *oldElem = _elem;_elem = new T[_capacity <<= 1]; //申请两倍的新的空间for (int i = 0; i < _size; i++){_elem[i] = oldElem[i]; //若T为非基本类型,则该类型需重载=操作符}delete[] oldElem; //释放原空间}
}

扩容两倍的空间,这是有理论依据的。当发生足够多次的插入操作时,每次将容量扩大一倍的平摊时间成本最低,为O(1)。

template <typename T>
void Vector<T>::shrink()
{while (_size << 2 < _capacity) //若实际规模不到容量的1/4,则缩容{T *oldElem = _elem;_elem = new T[_capacity >>= 1]; //申请原来一半的空间for (int i = 0; i < _size; i++){_elem[i] = oldElem[i]; //若T为非基本类型,则该类型需重载=操作符}delete[] oldElem; //释放原空间}
}

装填因子 = _size / _capacity ,我们选择25%作为装填因子的下限。

逆序与置乱

在必要时,可以将向量重新随机排序(需要先引入<random>库):

template <typename T>
void Vector<T>::unsort(Rank lo, Rank hi)
{T *V = _elem + lo; //调整指针for (Rank i = hi - lo; i > 0; i--)std::swap(V[i - 1], V[rand() % i]);
}

对应的,我们可以查询某个序列的逆序数:

template <typename T>
int Vector<T>::disordered() const
{int n = 0;for (int i = 1; i < _size; i++)if (_elem[i - 1] > _elem[i])n++;return n;
}

排序

排序算法是向量无序转有序的关键,该类算法多种多样,这里目前只实现比较简单的冒泡排序:

template <typename T>
void Vector<T>::sort(Rank lo, Rank hi)
{while (!bubble(lo, hi--)); //当返回sorted=true时,停止扫描
}

下面我们实现扫描交换的算法:

template <typename T>
bool Vector<T>::bubble(Rank lo, Rank hi)
{bool sorted = true;while (++lo < hi)if (_elem[lo - 1] > _elem[lo]){sorted = false; //判断说明下次扫描可能还会出现逆序std::swap(_elem[lo - 1], _elem[lo]);}return sorted; //只有本次扫描没有出现逆序,则下次一定不会出现逆序,可终止扫描
}

插入与查找

插入单个元素,届时检查容量是否支持该次插入操作,不支持时需要扩容:

template <typename T>
Rank Vector<T>::insert(Rank r, T const &e)
{expand(); //若需要,先扩容for (int i = _size; i > r;)_elem[i] = _elem[i - 1]; //整体后移一位,从后向前_elem[r] = e;_size++;return r;
}

这是对于无序向量查找的实现,顺次查询:

template <typename T>
Rank Vector<T>::find(T const &e, Rank lo, Rank hi) const
{while ((lo < hi--) && (e != _elem[hi])); //当匹配到对应的e后停止,并返回秩return hi;
}

有序向量查找

二分查找是查找有序序列常见的思路,对于分切点的选取,我们给出两种策略:

  1. 区间中点作为切割点(三分支,比较两次):
template <typename T>
static Rank binSearch(T *A, T const &e, Rank lo, Rank hi)
{while (lo < hi){Rank mi = (lo + hi) >> 1; //取区间中点if (A[mi] > e)hi = mi; //若中点值大于目标值,转移至前驱区间[lo, mi)else if (A[mi] < e)lo = mi + 1; //若中点值小于目标值,转移至后继区间(mi, hi)elsereturn mi; //若中点值等于目标值,返回中点的秩}return -1;
}

对该算法可行性的检验可以通过列举一些退化的例子,如只有三个元素:<、<、=或<、<、>,以及=、>、>和<、>、>。

需要注意的是,最后一个元素实际不参与比较或根本不存在,只是一个哨兵元素。

  1. 斐波拉契(黄金分割)分段点:

二分查找本质上是一种“减而治之”的查找策略,它并不约束查找中点(mi)的位置。我们可以按照黄金分割比来确定mi的位置:

设向量长度为 n = fib(k) - 1

算法的复杂度基本仅来源于中值的比较,这两种查找方式都是O(logn)级的时间复杂度。但准确来说,以黄金分割率确立的查找策略比前者优于一个常数级。

由于是三分支的查找,至少需要设置两次比较,若目标项在向量首段,则每次迭代需要进行一次比较;若在末段,则需比较两次(因为转向后继区间的代码位于else if,想要转向后继区间,必须先执行if段的代码,这是无法避免的)。

相比于等分策略,黄金分割策略加长了前驱区间(只需一次比较)的长度,即减少了需比较两次的情况,因此理论上性能更优。当然我们也有其他提升性能的方法,比如改进成两分支。

删除

区间批量删除:

template <typename T>
int Vector<T>::remove(Rank lo, Rank hi)
{if (lo == hi)return 0;while (hi < _size)_elem[lo++] = _elem[hi++]; //整体前移,若删除区间大于其后缀区间,未覆盖部分不做处理,下次缩容时自动消除_size = lo;                    //确定新界限shrink();                      //若装填因子过小,缩容return hi - lo;                //返回删除元素的个数
}

单个删除操作:

template <typename T>
T Vector<T>::remove(Rank r)
{T re_elem = _elem[r]; //备份将被删除的元素remove(r, r + 1);return re_elem;
}

我们将区间批量删除作为单个删除的子操作,因为批量删除操作相比执行多次单个删除操作所需的时间成本更低。

去重

对于无序向量,只能逐次检查,每次删除一个重复元素:

template <typename T>
int Vector<T>::deduplicate() //无序向量去重
{int oldSize = _size; //记录原规模Rank i = 1;while (i < _size)   //从前向后依次检查,保证每次最多检查到一个重复元素(find(_elem[i], 0, i) < 0) ? i++ : remove(i); //若查找到,删除该元素并检查其后继元素return oldSize - _size;
}

从前向后逐渐扩大查重范围。

对于有序向量,我们有更方便的去重方式,因为元素都是依次排好的:

template <typename T>
int Vector<T>::uniquify() //有序向量去重
{Rank i = 0, j = 1;while (j < _size)(_elem[i] == _elem[j]) ? j++ : _elem[++i] = _elem[j++]; //查询到首个不重复元素并将其赋值到i的后继位置_size = ++i; //重新对规模赋值,未被覆盖的元素不做处理,或等到内存动态规划时删除shrink();  //装填因子过小时缩容return j - i;
}

从前向后依次检查,可批量的删除重复的元素:

这里的删除并不是实际执行的操作,仅通过将不重复的元素赋值到一起,忽略重复的元素来实现。

遍历(批量)操作

我们为两种批量操作都提供了接口:

  1. 遍历向量,对每个元素执行函数指针提供的操作:
template <typename T>
void Vector<T>::traverse(void (*visit)(T &))
{for (int i = 0; i < _size; i++)visit(_elem[i]); //使用传入的函数指针处理
}
  1. 遍历向量,对每个元素执行函数对象(一种重载()操作符的特殊类,其实例对象可以像函数一样调用)提供的操作:
template <typename T>
template <typename VST>
void Vector<T>::traverse(VST &visit)
{for (int i = 0; i < _size; i++)visit(_elem[i]); //使用传入的函数对象处理
}

我们可以自定义函数指针和函数对象来执行批量的任务。

【C++】数据结构——向量相关推荐

  1. R语言的四种数据结构---向量

    R中常用的数据结构有四种:向量.矩阵.数据库和列表 (1)基本操作 1.向量的创建 c()函数创建向量:seq()函数创建等差数列的向量:sample()从一列数字中随机抽取几个数:paste0()把 ...

  2. r 字符串转化为数值_【R语言】数据结构Ⅰ—向量,矩阵,数组

    数据结构是为了便于存储不同类型的数据而设计的. R中常用的数据结构包括: 同质数据类型(homogeneous data types),即所存储的一定是相同类型的元素,包括向量.矩阵.数组: 异质数据 ...

  3. r语言逻辑向量相加_R语言基础教程——第3章:数据结构——向量

    如果学过像JAVA或者C这样的高级语言,都知道,数据类型的概念,包括,整数型.浮点型.字符串.布尔类型.这些语言中,定义变量需要定义数据类型,而在R中不需要.只需要直接赋值即可.在给变量赋值时,R中可 ...

  4. R语言数据结构之向量

    R总共是6中数据结构:向量.因子.矩阵.数组.数据框.列表.向量用于存储数值型.字符型或逻辑型数据的一维数组,是R中的最小单元.向量构成的基本元素为:数值(numeric).字符(character) ...

  5. 数据结构与算法之-----向量(Vector)

    [ 写在前面的话:本专栏的主要内容:数据结构与算法. 1.对于​​​​​​​初识数据结构的小伙伴们,鉴于后面的数据结构的构建会使用到专栏前面的内容,包括具体数据结构的应用,所使用到的数据结构,也是自己 ...

  6. R语言与数据分析(11)R语言的数据结构

    数据结构 数据结构是计算机存储.组织数据的方式,数据结构是指相互之间存在一种或多种特定关系的数据元素的集合 R中的数据类型 1.数值型 数值可以用于直接结算,加减乘除 2.字符串型 可以进行连接.转换 ...

  7. r语言x c(-1 -2),【软件】R语言入门之向量

    "R语言入门开篇,向量(vector)相关知识的介绍" R语言是一款优秀统计学编程语言,本文介绍R语言的几个重要命令,以及R语言中非常重要的一种数据结构-向量(Vector)的相关 ...

  8. 机器学习中的Numpy库基础——向量、矩阵和数组

    1.Numpy简介 Numpy是Python机器学习技术栈的基础. Numpy可以对机器学习中常用的数据结构--向量(vector).矩阵(matrice).张量(tensor)--进行高效的操作. ...

  9. R数据分析——安装,数据类型转换,向量

    R数据分析 R中安装与使用包 卸载与移除包 基本数据类型(六种) 数据类型转换 运算符 R中的数据结构 向量 R中安装与使用包 R中的包存储在计算机上名为library的目录下 ✓ 使用函数libPa ...

最新文章

  1. Visual Studio 2005 Team System: Demo Videos
  2. java php mysql_系统学习javaweb13----MYSQL学习(使用PHP、SQL)1
  3. (十)装饰器模式详解(与IO不解的情缘)
  4. IDEA中实用的快捷方式
  5. 认识VLAN,并学会VLAN的划分和网络配置实例
  6. 【干货下载】2020新基建展望:新战略、新动力、新格局.pdf(附下载链接)
  7. 机器学习实战——决策树:matplotlib绘图
  8. 全网首发:doubango提示text relocations错误的解决办法
  9. Notepad2添加到右键菜单栏
  10. 关于成型滤波器实现方式的简单比较
  11. 微信支付商户平台扫码登录说明
  12. Fake it till you make it: face analysis in the wild using synthetic data alone
  13. 【阿里云】云解析DNS
  14. jieba分词关键词抽取
  15. NIVIDIA 硬解码学习1
  16. 金色传说:SAP-BC-导出数据时不能选择导出文件的格式问题
  17. C#判断线段是否相交
  18. 腾讯云tca认证题库
  19. 停车场管理系统车牌识别中的无感支付有什么bug
  20. java实现手机验证码功能

热门文章

  1. C语言课程设计-实验室设备管理系统
  2. Like What You Like: Knowledge Distill via Neuron Selectivity Transfer 论文翻译
  3. 2018阿里巴巴基础平台研发工程师实习生笔试题:一.十进制数转换成的二进制数中有几个1. 二.输出IP所在的网段
  4. 【java并发探赜索隐】判断标志、死锁、守护进程
  5. uni-app知识点整理
  6. 远程登陆没有固定ip的服务器
  7. CSS 列表样式 (ul)
  8. html中如何把一张图片分块,神奇图片分割软件有哪些分割模式 图片分割器如何检验能否无缝拼图...
  9. Practice II 字符串
  10. 判断TTS语音朗读是否结束