向量与列表(一种线性的数据结构)

本文对比了向量与列表这两种序列数据结构,讲解了它们的实现方式、时间复杂度差异以及如何通过位置(Position)优化操作效率。向量基于数组实现,插入删除操作平均时间复杂度为O(n),而列表基于链表,查找元素时间复杂度为O(n),但插入删除操作在已知位置时为O(1)。同时介绍了Java中的ArrayList和Vector类,它们通过动态扩容策略提供高效的向量操作。
摘要由CSDN通过智能技术生成

1. 向量与列表

向量(Vector)与列表(List)是两种典型的序列(Sequence)。所谓序列,就是指一组依次排列的对象。此特点与数组十分相似,只是对于向量和列表而言,它们不再受到“容量”的制约,简单的来说,可以将它们看成一种“可变的数组”

向量和列表所支持的操作类似,其主要的区别在于实现的方式,以及由此引起的时间复杂度的区别

实现方式
向量(Vector)基于数组实现,因此也被称作数组列表(ArrayList)
列表(List)基于链表实现,是对链表结构的抽象与扩展

正如前面所说的,向量与列表拥有类似的方法接口,它们的 ADT(Abstract Data Type)如下所示:

方法功能描述
size()返回当前向量/列表中对象的数目
isEmpty()用于检测向量/列表是否为空
get(index)返回 index 对应的元素
add(index, element)将 element 插入到 index 处
remove(index)删除 index 对应的元素

序列的应用十分广泛,就其功能而言,队列都可以看作是带有限制条件的序列:插入、删除和访问操作只限于首元素的,就是栈;删除和访问操作只限于首元素、插入操作只限于末元素的,就是队列。

2. 时间复杂度的区别

由于实现方式不同,向量与列表在插入、删除和读取的时间复杂度上有所区别

  1. 对于向量而言,由于它是基于数组实现的,所以其静态获取某个 index 上数据时间复杂度只有 O(1)。但对于动态插入/删除操作而言,虽然找到对应的元素只需要 O(1) 的时间,但在插入/删除操作之后还需将该位置后边的元素依此向后/向前移动一个位置,此操作需要 O(n) 的时间,故整个操作的时间复杂度为 O(n)。

  2. 对于列表而言,由于它是基于链表实现的,所以静态获取某个数据需要遍历列表,最差的情况下需要 n 次操作,故其时间复杂度为 O(n)。而对于插入/删除操作而言,如果已知某元素的位置,插入/删除操作只需要修改其前后节点的引用即可,此操作可在常数时间内完成,即时间复杂度为 O(1)。但正如前面所说的,找到一个元素的位置需要花费 O(n) 的时间,即整体操作的时间复杂度依旧是 O(n)

    但是,如果能够利用某种数据结构(位置,Position)直接定位列表中的元素,就可以在常数时间内完成插入、删除和读取的操作。

3. 位置(Position)

为避免每次访问节点时都要对列表进行遍历,按照面向对象的规范对列表的节点对象进行了封装,称作位置(Position),其 ADT 如下所示:

方法功能描述
getElement()返回存放于当前位置的元素
setElement(element)将 element 放入当前位置

按线性次序排列的一组位置,就构成了一个列表。在列表中,各个位置都是相对而言的——相对于它的前、后邻居。利用位置,我们可以通过以下的方式实现列表的插入:

算法:insertBefore(p, e)
输入:位置p和元素e
输出:将e插入至p之前后,返回插入的位置
{
	创建一个新节点v;
	v.setElement(e);//用v来存放e
	v.setNext(p);//以p为v的直接后继
	v.setPrev(p.getPrev);//以p的直接前驱为v的直接前驱
	(p.getPrev()).setNext(v);//p的直接前驱应该以v为直接后继
	p.setPrev(v);//作为v的直接后继,p应该以v为直接前驱
	return((Position)v);//v作为一个位置被返回
}

删除操作与此类似,即改变其前后位置的引用即可,当然修改引用的操作并不能直接通过位置对象实现(此接口只提供了访问和修改位置内部元素的方法),需要在 remove 方法中隐式的实现。这也符合面向对象的设计思想,通过封装位置这一数据结构,保护了数据的安全性,避免了可以直接对前后的引用进行操作等风险。

利用位置,就可以直接访问其中的元素,在常数时间内完成插入、删除和读取的操作

4. 基于可扩充数组实现的向量

Java本身也提供了与向量ADT功能类似的两个类:java.util.ArrayList 和 java.util.Vector。对于 ArrayList 类,乍一看可能回将其误认为是列表,但正如我们前面所说的,对数组结构进行抽象与扩展之后,就可以得到向量结构,因此向量也称作数组列表,而将 ArrayList 翻译下不正好就是数组列表么!

正是由于向量是基于数组实现的,为了避免数组容量带来的限制,ArrayList 类和 Vector 类都采用了动态扩充数组容量的策略(当元素个数达到数组容量时,将所有的元素copy到一个新的数组中,ArrayList 和 Vector 类都默认新数组的容量是老数组的两倍,并用新数组代替老数组进行工作),这也是实现向量这一数据结构的一般策略。

当然,动态扩充数组容量涉及到两个数组之间的元素拷贝,势必会影响时间复杂度。但是,由于并不是每次插入操作都是需要扩容的,因此无法采用常规的方法来度量和分析其复杂度。为此可以引入分摊复杂度的概念,所谓分摊复杂度,就是指在连续执行的足够多次操作中,每次操作所需的平均运行时间

可以证明,基于可扩充数组实现的向量,如果扩容后的容量是之前的两倍,则每次数组扩容的分摊运行时间为O(1)。动态扩充数组容量的策略不仅可行,而且就分摊复杂度而言,其效率也足以令人满意。

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值