数据结构之 Array/Vector
写在前面
数据结构是数据项的结构化集合,其结构性表现为数据项之间的相互联系以及作用,可以理解为定义于数据项之间的
某种逻辑次序。
数据结构(按数据项间的逻辑次序划分)
- 线性结构
- 线性表\序列(Sequence)
- 栈(stack)
- 队列(queue)
- 半线性结构
- 树(Tree)
- 非线性结构
- 图(graph)
线性表/序列(按存储结构划分)
- 顺序表/向量
在这里把顺序表和向量放在一起了,其本质都是数组,但是在一般数据结构课中,更多地,我们称为顺序表。进一步地,在C++的STL中,数组被封装为成为适用更加广泛的Vector了,通过模板参数制定数据元素的类型,还有大量的ADT接口使用起来特别方便。当然二者还是有一些区别的,在下面讲解操作时会具体说。
但本质上,二者的逻辑操作与实现是一致的,因此放在一起写。 - 链表/列表
同上,一般数据结构课上就直接讲链表,但在STL中封装为List了。
顺序表/向量
- 通常,程序语言都会把数组作为一种内置的数据类型,支持其对一组相关元素的存储组织与访问操作。
- 具体来讲,一个集合S有n个元素,它们集中地存放于起始地址为A、物理地址连续地一段存储空间,并统称为数组(array)。通常以A作为该数组的标识。并且,用A[i]表示这一段连续存储空间中的第i+1个元素,值得注意的是,计算机中的i通常从0开始计数,而日常人们计数则从1开始。
即 A = { a0, a1, a2, ……, a(n-1) } - 由此可以看出,A中任意一个元素 ai 都有唯一的 i 可以对其进行直接访问,元素A[i]对应的物理地址为$ A + i * s $,其中单个元素占 s s s 个单位空间。
- 对于任何 0 ≤ i < j < n 0 \le i \lt j \lt n 0≤i<j<n, A[i]都是A[j]的前驱,特别地,当 i = j − 1 i=j-1 i=j−1时,A[i]是[j]的直接前驱,A[j]的所有前驱称为其前缀;相应地,A[j]都是A[i]的后继,特别地,当 i + 1 = j i+1=j i+1=j时,A[j]是A[i]的直接后继,A[i]的所有后继称为其后缀。
基本操作(ADT接口)
Vector()
- 构造函数,其实现方式最多样,具体可参见下面的代码。
- 这是一个重点。array的初始化一般采用固定长度的方式,也就是说,其大小是在赋值之前就确定的,而Vector模板类采用动态分配空间方式,因此稍显灵活。但当其规模频繁变化时,二者都需要动态调整,不仅费时间,而且还会遇到没有这么大规模的连续空间的问题。所以,在需要规模频繁变化时,一般不采用顺序表,而采用链表/列表。
size()
- 返回顺序表/向量的规模
- 值得注意的是,在insert或者remove这样的会引起元素数量变化的时候,要及时地对size进行更新,这样才会保证每次调用此接口不用遍历一遍计数,而是直接返回size变量的值时间复杂度为O(1)
get( r )
- 返回秩(rank,也就是通常所讲的下标)为r的元素
- 时间复杂度O(1),充分体现了顺序表可以随机访问的特征
- 这里需要注意的是,虽然C++ STL中重载了[ ],也就是说vector仍然可以使用vec[i]的方式访问某个元素,但实际上作为一个封装的抽象类,使用get( r )的方式更能体现其与底层无关的特点。
insert(r, e)
- 将元素e插到秩为r的位置
- 时间复杂度O(n),在插入之前,需将秩为n-1到r的所有元素依次向后移动一个位置,空出A[r]。r可为0, 1, 2……, n,则每次需要移动n, n-1, n-2……, 0次,平均移动 n / 2 n/2 n/2 次,即时间复杂度为O(n)
remove( r )
- 删除秩为r的元素
- 和插入一样,删除后需将后面的元素向前移动一个位置,时间复杂度为O(n)
find(e)
- 查找值等于e且秩最大的元素
- 当有多个值为e的元素时,返回秩最大的(至于原因,在实现排序时就能理解了),则可以从后往前依次查找,时间复杂度O(n)
traverse()
- 遍历所有元素
- 显然,当访问每个元素只进行常数复杂度的操作时,此接口的时间复杂度为O(n)
以下是根据邓公学习时自己仿写的,只给出头文件,部分简单的接口已经实现了,剩下的将会在结合具体算法讲的时候在贴出来。虽然顺序表很简单,但是越简单的东西可以变化的招式就越多,自己写完之后才能体会哪里是容易栽跟头的地方。
另外,同一种接口有多种实现方式,因此,可结合复杂度分析,判断优劣。
#ifndef VECTOR_H
#define VECTOR_H
// Vector template
typedef int Rank; //秩
#define DEFAULT_CAPACITY 3 //默认的初始容量(实际应用中可设置为更大)
template <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); //扫描交换
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); //堆排序
public:
// 构造函数
Vector(int c = DEFAULT_CAPACITY, int s = 0, T v = 0){ //容量为c、规模为s、所有元素初始为v
_elem = new T[_capacity = c];
for (_size = 0; _size < s; _elem[_size++] = v);
} // s<=c
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 (0 >= _size) ? -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的元素
int remove(Rank lo, Rank hi); //删除秩在区间[lo, hi)之内的元素
Rank insert(Rank r, T const& e); //插入元素
Rank insert(T const& e) { return insert(_size, e); } //默认作为末元素插入
void 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&); //遍历(使用函数对象,可全局性修改)
}; // Vector
#endif