向量基础知识测验

Choose a study mode

Play Quiz
Study Flashcards
Spaced Repetition
Chat to Lesson

Podcast

Play an AI-generated podcast conversation about this lesson
Download our mobile app to listen on the go
Get App

Questions and Answers

关于向量(Vector)的描述,以下哪一项是准确的?

  • 向量中各元素的类型必须是相同的基本类型。
  • 向量中的元素必须具有数值属性,以便可以比较大小。
  • 向量是线性数组的一种抽象和泛化,其中的元素通过秩相互区分。 (correct)
  • 向量只支持顺序访问,不支持通过秩直接访问元素。

若一个向量的起始地址为A,每个元素占用s个存储单位,那么第i个元素(A[i])的物理地址如何计算?

  • A + i
  • A + i * s (correct)
  • A * i * s
  • A + s

在向量的ADT接口中,search(e)操作的功能是什么?

  • 查找有序向量中小于等于e且秩最大的元素。 (correct)
  • 查找有序向量中大于等于e且秩最小的元素。
  • 查找向量中所有等于e的元素。
  • 查找向量中等于e且秩最小的元素。

以下关于ADT(抽象数据类型)接口的叙述,哪一项最准确地描述了其作用?

<p>符合统一ADT接口规范的不同实现可以相互调用和集成。 (C)</p> Signup and view all the answers

在给定的Vector模板类中,copyFrom(T const* A, Rank lo, Rank hi) 函数的作用是什么?

<p>复制数组区间 A[lo, hi) 到向量中。 (B)</p> Signup and view all the answers

以下哪种方式_不能_用于创建一个新的Vector对象?

<p>直接将一个数组赋值给<code>Vector</code>对象,不指定任何参数。 (A)</p> Signup and view all the answers

Vector类的动态空间管理中,为什么需要进行扩容操作?

<p>当向量的实际规模超过当前容量时,避免上溢。 (B)</p> Signup and view all the answers

如果一个Vector对象的容量(_capacity)为N,且装填因子接近1,那么在插入新元素时,以下哪种描述是_最准确的_?

<p>可能会触发扩容操作,容量变为原来的2倍。 (B)</p> Signup and view all the answers

假设有一个已经存在的Vector对象V,现在需要将其赋值给另一个Vector对象U。为了确保U拥有V的完整副本且内部数据区也得到复制,应该如何操作?

<p>重载赋值操作符,在重载的函数中先释放<code>U</code>的内存,然后使用<code>copyFrom</code>复制<code>V</code>的内容。 (D)</p> Signup and view all the answers

在扩容算法expand()中,以下哪个条件_不是_决定是否需要扩容的关键因素?

<p><code>_capacity &lt; DEFAULT_CAPACITY</code> (C)</p> Signup and view all the answers

Flashcards

数据结构

数据项的结构化集合,数据项之间具有相互联系和作用,也可理解为数据项之间存在某种逻辑次序。

向量(Vector)

一种线性结构,数据项的物理存放位置与其逻辑次序完全吻合,逻辑次序也称作秩。

秩(Rank)

元素在线性序列中的位置序号,表示该元素之前有多少个元素。

循秩访问(Call-by-Rank)

一种通过元素的秩来访问向量中元素的方式。

Signup and view all the flashcards

向量ADT接口

向量对象应支持报告向量当前规模(元素总数),获取秩为r的元素,用e替换秩为r元素的数值,e作为秩为r元素插入, 移除秩为r的元素

Signup and view all the flashcards

向量元素寻址

向量中秩为r的元素对应于内部数组中的_elem[r],其物理地址为_elem + r。

Signup and view all the flashcards

copyFrom()方法

copyFrom() 根据待复制区间的边界计算新向量的初始大小,并以双倍容量为内部数组 _elem[] 申请空间,然后复制区间A[lo, hi)中的元素。

Signup and view all the flashcards

重载赋值操作符

重载赋值操作符允许向量之间直接赋值,避免浅复制问题。它首先释放原有内容,然后使用 copyFrom() 复制。

Signup and view all the flashcards

装填因子

装填因子是向量实际规模与其内部数组容量的比值,衡量空间利用率。理想情况下,既不应超过1,也不应太接近0。

Signup and view all the flashcards

扩容算法 expand()

当向量空间不足时,expand() 函数会分配一个更大的数组,将原有元素复制到新数组中,然后释放旧数组。

Signup and view all the flashcards

Study Notes

好的,这是根据您提供的文本生成的学习笔记:

第2章 向量

  • 数据结构是数据项的结构化集合,其结构性表现数据项之间的相互联系作用。
  • 逻辑次序的复杂程度大致将各种数据结构划分为线性、半线性与非线性结构三大类。
  • 线性结构中,各数据项按照一个线性次序构成一个整体,又被称为序列 (sequence)
  • 根据数据项的逻辑次序与其物理存储地址的对应关系,序列可进一步区分为向量 (vector)列表 (list)
  • 向量中所有数据项的物理存放位置与其逻辑次序完全吻合;逻辑次序也称作秩 (rank)。
  • 列表中逻辑相邻的数据项在物理上不一定相邻,而是采用间接定址相互引用。

数组

  • C、C++ 和 Java 等程序设计语言,都将数组作为一种内置的数据类型。
  • 集合 S 由 n 个元素组成,且具有线性次序,可存放于起始于地址 A、物理位置连续的一段存储空间,称为数组 (array)
  • 数组 A[] 中的每一元素唯一对应于某一下标编号,在多数高级程序设计语言中,一般从 0 开始编号,依次记作 0 号, 1 号, 2 号,...,n-1 号元素。
    • 记作: A = { a₀, a₁, ..., aₙ-₁ } 或者 A[0, n) = { A[0], A[1], ..., A[n - 1] }
  • 对于任何 0 ≤ i < j < n, A[i] 都是 A[j] 的前驱 (predecessor), A[j] 都是 A[i] 的后继 (successor)。
  • 对于任何i≥1,A[i-1]称作A[i]的直接前驱 (intermediate predecessor)
  • 对于任何i≤n-2,A[i+1]称作A[i]的直接后继 (intermediate successor)
  • 任一元素的所有前驱构成其前缀 (prefix),所有后继构成其后缀 (suffix)。
  • 采用唯一编号规范,可以使得每个元素都通过下标唯一指代,也可以直接访问到任一元素。
  • “访问”包含读取、修改等基本操作,都可以在常数时间内完成。
  • 数组的起始地址为A,且每个元素占用s个单位的空间,则元素A[i]对应的物理地址为: A+i×s
  • 元素的物理地址与其下标之间满足线性关系,故亦称作线性数组 (linear array)

向量

  • 向量(vector)是线性数组的一种抽象与泛化,也是由具有线性次序的一组元素构成的集合。
    • V = { v₀, v₁, ..., vₙ-₁ },其中的元素分别由秩相互区分。
  • 各元素的秩(rank)互异,且均为[0, n)内的整数
    • 若元素 e 的前驱元素共计 r 个,则其秩就是 r
  • "循秩访问(call-by-rank)": 通过秩亦可唯一确定元素。
  • 向量元素不限定统一基本类型,本身可以是来自于更具一般性的某类的对象。
  • 元素不见得同时具有某一数值属性,故不保证它们之间能够相互比较大小。

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() 遍历向量并统一处理所有元素,处理方法由函数对象指定 向量
  • 引入秩的概念、并将外部接口与内部实现分离之后,无论采用何种具体的方式,符合统一外部接口规范的任一实现均可直接相互调用和集成。

操作示例(整数向量)

操作 输出 向量组成(自左向右)
初始化 - -
insert(0, 9) - 9
insert(0, 4) - 4 9
insert(1, 5) - 4 5 9
put(1, 2) - 4 2 9
get(2) 9 4 2 9
insert(3, 6) - 4 2 9 6
insert(1, 7) - 4 7 2 9 6
remove(2) 2 4 7 9 6
insert(1, 3) - 4 3 7 9 6
insert(3, 4) - 4 3 7 4 9 6
size() 6 4 3 7 4 9 6
disordered() 3 4 3 7 4 9 6
find(9) 4 4 3 7 4 9 6
find(5) -1: 4 3 7 4 9 6
sort() 3 4 4 6 7 9
disordered() 0 3 4 4 6 7 9
search(1) -1: 3 4 4 6 7 9
search(4) 2 3 4 4 6 7 9
search(8) 4 3 4 4 6 7 9
search(9) 5 3 4 4 6 7 9
search(10) 5 3 4 4 6 7 9
uniquify() 3 4 6 7 9
search(9) 4 3 4 6 7 9

Vector 模板类

  • 向量的ADT接口的代码定义
  • typedef int Rank; //秩
  • #define DEFAULT_CAPACITY 3 //默认的初始容量
  • class Vector: 向量模板类
  • protected: 规模、容量、数据区
  • void copyFrom(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); //归并排序算法
  • Rank partition (Rank lo, Rank hi); //轴点构造算法
  • void quickSort(Rank lo, Rank hi); //快速排序算法
  • void heapSort(Rank lo, Rank hi); //堆排序(稍后结合完全堆讲解)

构造与析构

  • 向量结构在内部维护一个数组_elem[]
    • 元素类型为T
    • 容量由私有变量_capacity指示;
    • 有效元素的数量 (即向量当前的实际规模) 由_size指示。
  • 约定秩和物理地址具有如下关系:
    • 向量中秩为 r 的元素,对应于内部数组中的_elem[r],其物理地址_elem + r
  • 向量对象的构造与析构,将主要围绕初始化与销毁展开。

默认构造方法

  • 向量在使用之前需要被系统创建 — 借助构造函数 (constructor) 做初始化 (initialization) 。
  • 借助创建者指定的初始容量,向系统申请空间,以创建内部私有数组。
    • 容量未明确指定,则使用默认值DEFAULT_CAPACITY
  • 不包含任何元素,故将指示规模的变量_size初始化为 0。
  • 整个过程顺序进行,共需常数时间。

基于复制的构造方法

  • 以某个已有的向量 (vector) 或数组为蓝本,进行克隆。无论封装还是未封装,都可归于统一的 copyFrom() 方法。
  • 先根据待复制区间的边界,换算出新向量的初始规模,再以双倍的容量,为内部数组申请空间;再通过一趟迭代,完成各元素的顺次复制。
  • 基于复制的向量构造器的代码(以数组区间A[lo, hi)为蓝本复制向量): 1 template <typename T> //元素类型 2 void Vector<T>::copyFrom(T const* A, Rank lo, Rank hi) { //以数组区间A[lo,hi)为蓝本复制向量 3 _elem = new T[_capacity = 2 * (hi - lo)]; _size = 0; 4 while (lo < hi) { //A[lo, hi)内的元素逐 - 5 _elem[_size++] = A[lo++]; //复制至 _elem[0, hi - lo) 6 }
  • 运行时间应正比于区间宽度,即0(hi - lo) = 0(_size)。

析构方法

  • 向量不再需要会被析构函数 (destructor) 及时清理 (cleanup)
    • 以释放其占用的系统资源。
  • 向量对象的析构过程:只需释放内部数组_elem[],将其占用的空间交还操作系统,
  • 若不计系统用于空间回收的时间,整个析构过程只需 Θ(1) 时间。
  • 如果元素不是程序语言直接支持的基本类型,应该提前释放对应的空间。
  • “谁申请谁释放”的原则。究竞应释放掉向量各元素所指的对象,还是需要保留这些对象,应由上层确定。

静态空间管理

  • 内部数组所占物理空间的容量,若在向量的生命期内不允许调整,则称作静态空间管理策略。
    • 但空间效率难以保证。
    • 容量固定,总有可能在此后的某一时刻,无法加入更多的新元素 — 上溢 (overflow)。
    • 上溢是由于插入操作远多于删除操作造成的
    • 实际规模与其内部数组容量的比值(即_size / _capacity),亦称作装填因子 (load factor),它是衡量空间利用率的重要指标.

动态空间管理

  • 如何保证向量的装填因子既不致于超过1, 也不致于太接近于0?需要改用动态空间管理策略,改用所谓的可扩充向量。

可扩充向量

  • 当身体无法继续为其外壳所容纳,蝉就会蜕去外壳,同时换上一身更大的外壳。
  • 扩充向量 (extendable vector) 的原理,与之相仿。
    • 内部数组仍有空余,则照常执行。
    • 每经一次插入 (删除) ,可用空间都会减少(增加)一个单元。
    • 一旦可用空间耗尽,就动态地扩大内部数组的容量,以另行申请一个容量更大的数组。
    • 将原数组中的成员集体搬迁至新的空间,此后方可在新数组中顺利插入。
    • 原数组所占的空间,需要及时释放并归还操作系统。
  • 扩容算法expand()
    • 检查内部数组的可用容量。
    • 当前数据区已满 (_size == _capacity),则将原数组替换为一个更大的数组。
  • 不要直接引用数组,那样会导致野指针 (wild pointer);而经封装为向量之后,即可继续准确地引用各元素,从而有效地避免野指针的风险。
  • 新数组的容量总是取作原数组的两倍。

分摊分析

  • 常规数组实现相比,可扩充向量更加灵活,只要系统尚有可用空间,其规模将不再受限于初始容量。但是,扩增是要花费额外时间的。
  • 对可扩充向量的足够多次连续操作,并将其间所消耗的时间,分摊至所有的操作
    • 单次操作的时间成本,称作分摊运行时间(amortized running time)。
  • 分摊复杂度可以针对计算成本和效率,做出更为客观而准确的估计。

静态空间管理

  • 内部数组所占物理空间的容量,若在向量的生命期内不允许调整,则称作静态空间管理策略。
    • 但空间效率难以保证
    • 既然容量固定,总有可能在此后的某一时刻, 无法加入更多的新元素————上溢 (overflow)。
    • 上溢是由于插入操作远多于删除操作造成的
    • 实际规模与其内部数组容量的比值(即_size/_capacity)
      • 亦称作装填因子 (load factor) + 是衡量空间利用率的重要指标
  • 如何才能保证向量的装填因子既不致于超过1, 也不致于太接近于0?——需要改用动态空间管理策略

动态空间管理

  • 导致低效率的另一情况是,向量的实际规模可能远远小于内部数组的容量。装填因子低于某一阈值时,称数组发生了下溢 (underflow)。 尽管下溢不属于必须解决的问题,但在格外关注空间利用率的场合,发生下溢时也有必要适当缩减内部数组容量。代码 2.5 给出了一个动态缩容 shrink()算法。

常规向量

  • 既可以通过下标访问元素,比如数组形式 (形如“A[i]“),也可沿用数组的访问方式

直接引用元素

1 template <typename T> T& Vector<T>::operator[](Rank r) const //重载下标操作符 2 { return _elem[r]; } // assert: 0 <= r < _size

置乱器

  • 借助重载后的操作符“[]”返回的是数组元素的引用, 这就意味着它既可以取代get()操作 ,也可以取代操作
  • 利用置乱算法可以在各元素等概率出现于每一位置

判等器和比较器

  • 用来区别对待数值类型
  • 本书将主要采用后一方式,为节省篇幅,这里只给出涉及到的比较和判等操作符,读者可以根据实际需要,参照给出的代码加以扩充。 在一些复杂的数据结构中,内部元素本身的类型可能就是指向其它对象的指针;而从外部更多关注的,则往往是其所指对象的大小。若不加处理而直接根据指针的数值(即被指对象的物理地址)进行比较,则所得结果将毫无意义。

无序查找

  • 在无序向量中查找任意指定元素e时,因为没有更多的信息可以借助,故在最坏情况下 一 只有在访遍所有元素之后,才能得出查找结论;称为顺序查找
  • **查找接口find():**返回最后一个元素 e 的位置
  • 复杂度:最好情况仅需要 0(1) 时间,最坏情况下 O(n)

插入

  • 整体实现的代码:

1 template <typename T> //将 作为秩为 元素插入 2 Rank Vector<T>::insert(Rank r,T const& e) { //assert: 0 <= r <= _size 3 expand(); //若有必要,扩容 4 for (inti = size; i > r; i--)_elem[i] = _elem[i - 1];//后继元素顺次后移个单元 5 _elem[r] = e;_size++; //置入新元素并更新容量 6 return r;//返同 7 }

  • 时间主要消耗于后继元素的后移,线程正比于后缀的长度。故总体0(_size - r - 1)。

删除

  • 两个接口:
    • (remove(lo, hi)用以删除区间[lo, hi)内的元素)
    • (remove(r)用以删除秩为r的单个元素)
  • 代码: 同样, 向量中的元素可能不是程序语言直接支持的基本类型。 比如,可能是指向动态分配对象的指针,或引用。
  • 需要根据具体的要求来定制策略。

无序去重

  • 借助 remove 只能删除单个元素
  • 算法自前向后逐一考查各元素 _elem[i], 并通过调用 find()接口, 在其前缀中寻找与之雷问者 算法的正确性: 在 while 循环中,在当前元素 的前缀 _elem[0, i) 内,所有元素彼此互异

Studying That Suits You

Use AI to generate personalized quizzes and flashcards to suit your learning preferences.

Quiz Team

More Like This

STL Components and Vectors
32 questions
Buidheann ann an C++
5 questions

Buidheann ann an C++

DextrousGreatWallOfChina avatar
DextrousGreatWallOfChina
Vectores y Arreglos en Programación
87 questions
R Data Structures: Vectors and Data Frames
43 questions
Use Quizgecko on...
Browser
Browser