数据结构实现(一):动态数组(C++版)
1. 概念及基本框架
数组 是一种 线性结构 ,而且存储上属于 顺序存储(即内存的物理空间是连续的),也就是线性表中的 顺序表 。数组结构如下图所示:
下面以一个我实现的一个简单的数组类来进一步理解数组。
const int initialLen = 10;
template <class T>
class Array{
public:
Array(int len = initialLen){
T *p = new T[len];
m_data = p;
m_capacity = len;
m_size = 0;
}
...
private:
T *m_data; //数组数据
int m_capacity; //数组容量
int m_size; //数组大小
};
这里为了避免重复设计就可以兼容更多数据类型,引入了 泛型 ,即 模板 的概念。(模板的关键字是 class 或 typename)
这里的 数组容量 表示数组最多可存放空间的大小,而 数组大小 指的是数组实际占用空间的大小。为了保护数据,这两个变量以及 数组数据 都设置为 private 。
构造数组时,可以初始化数组的数组容量。(默认是10)
那么就会出现一个问题,如果这块内存被用完了怎么办?因为数组的物理空间必须连续,所以我们必须另外申请一块更大的内存,先把当前数组复制到新的内存上,然后释放掉旧的内存空间。同理,如果很多的内存都没有被利用,我们也可以适当缩小数组容量。所以,我们需要一个这样的 扩容(缩容)函数 去动态的实现数组。代码如下:
template <class T>
class Array{
public:
...
void resize(int len){
T *p = new T[len];
for (int i = 0; i < m_size; ++i){
p[i] = m_data[i];
}
delete[] m_data;
m_data = p;
m_capacity = len;
}
...
};
实现了前面的程序之后,接下来就是一个数组的增、删、改、查以及一些其他基本操作,接下来利用代码去实现。
2. 基本操作程序实现
2.1 增加操作
template <class T>
class Array{
public:
...
//增加操作
void add(int index, T num);
void addFirst(T num);
void addLast(T num);
...
};
首先,在类体内进行增加操作函数的原型说明。这里包括三个函数:
add(添加到任意位置)
addFirst(添加到头部)
addLast(添加到尾部)
然后分别去实现它们。
template <typename T>
void Array<T>::add(int index, T num){
if (index < 0 || index > m_size){
cout << "添加位置非法!" << endl;
return;
//throw 0; //这里可以使用抛异常,下面也可以
}
if (m_size >= m_capacity){
resize(2 * m_capacity);
}
for (int i = m_size; i > index; --i){
m_data[i] = m_data[i - 1];
}
m_data[index] = num;
m_size++;
}
template <class T>
void Array<T>::addFirst(T num){
add(0, num);
}
template <class T>
void Array<T>::addLast(T num){
if (m_size >= m_capacity){
resize(2 * m_capacity);
}
m_data[m_size] = num;
m_size++;
}
由于这些函数在类体外,所以每个函数头部必须添加一行代码:
template <class T>
表示该函数使用模板,下面同理。
增加元素时可能会用到了扩容函数,当原空间已经使用完的时候进行扩容操作。这里我每次将容量扩展到原来的 2 倍,其实也可以扩展到原来的 1.5 倍。
2.2 删除操作
template <class T>
class Array{
public:
...
//删除操作
T remove(int index);
T removeFirst();
T removeLast();
void removeElement(T num);
...
};
同理,在类体内进行删除函数的原型说明。这里包括四个函数:
remove(删除任意位置元素):返回删除元素的值。
removeFirst(删除头部元素):返回删除元素的值。
removeLast(删除尾部元素):返回删除元素的值。
removeElement(删除特定元素):这里我利用下面的 find 函数来实现的,所以删除的是第一个这样的元素,如果想把这样的元素都删掉,可以写一个新的函数来实现。
然后分别去实现它们。
template <class T>
T Array<T>::remove(int index){
if (index < 0 || index >= m_size){
cout << "删除位置非法!" << endl;
return NULL;
}
T res = m_data[index];
m_size--;
for (int i = index; i < m_size; ++i){
m_data[i] = m_data[i + 1];
}
if (m_size < m_capacity / 4){
resize(m_capacity / 2);
}
return res;
}
template <class T>
T Array<T>::removeFirst(){
T res = m_data[0];
remove(0);
return res;
}
template <class T>
T Array<T>::removeLast(){
if (m_size == 0){
cout << "删除位置非法!" << endl;
return NULL;
}
m_size--;
if (m_size < m_capacity / 4){
resize(m_capacity / 2);
}
return m_data[m_size];
}
template <class T>
void Array<T>::removeElement(T num){
int flag = find(num);
if (flag != -1){
remove(flag);
}
}
删除时用到了缩容函数,当原空间被利用太少的话,就使用一块新的更小的空间。这里当空间利用率不足 1/4 时,我将内存缩至原空间的 1/2 ,即还有一些空间可以去添加。
这里需要注意的是,不要使用当空间利用率不足 1/2 时,就将内存缩至原空间的 1/2 ,这样可能会导致震荡。
例如当空间恰好被利用完了以后,增加了一个元素,这时空间变为 2 倍,然后又删除了一个元素,空间又降至原大小,然后又增加了一个元素,空间又变为 2 倍,再删除一个元素,空间又降至原大小……循环往复,造成震荡。
这里删除操作的“删除位置非法”后面返回的 NULL 也可以用 throw 抛异常来实现,这里只是为了方便。
2.3 修改操作
template <class T>
class Array{
public:
...
//修改操作
void set(int index, T num);
...
};
修改操作只有一个函数
set(修改指定位置的值)
同理,在类体内进行删除函数的原型说明,然后在类体外实现。
template <class T>
void Array<T>::set(int index, T num){
if (index < 0 || index >= m_size){
cout << "修改位置非法!" << endl;
return;
}
m_data[index] = num;
}
2.4 查找操作
template <class T>
class Array{
public:
...
//查找操作
T get(int index);
int find(T num);
bool contains(T num);
...
};
查找函数有三个:
get(返回特定位置元素)
find(返回第一个特定元素位置)
contains(返回是否包含特定元素)
分别对它们进行实现。
template <class T>
T Array<T>::get(int index){
if (index < 0 || index >= m_size){
cout << "访问位置非法!" << endl;
return NULL;
}
return m_data[index];
}
template <class T>
int Array<T>::find(T num){
for (int i = 0; i < m_size; ++i){
if (m_data[i] == num){
return i;
}
}
return -1;
}
template <class T>
bool Array<T>::contains(T num){
for (int i = 0; i < m_size; ++i){
if (m_data[i] == num){
return true;
}
}
return false;
}
同理,这里 get 函数的“访问位置非法”后面返回的 NULL 也可以用 throw 抛异常来实现,这里只是为了方便。
2.5 其他操作
数组还有一些其他的操作,这些函数我在类体内进行了实现。
包括 数组容量 、数组大小 的查询,还有 数组的打印 等操作。
template <class T>
class Array{
public:
...
int capacity(){
return m_capacity;
}
int size(){
return m_size;
}
...
bool isEmpty(){
return m_size == 0;
}
void print(){
cout << "Array: ";
cout << "Capacity = " << m_capacity << ", " << "Size = " << m_size << endl;
cout << '[';
for (int i = 0; i < m_size; ++i){
cout << m_data[i];
if (i != m_size - 1){
cout << ',';
}
}
cout << ']' << endl;
}
...
};
3. 算法复杂度分析
3.1 增加操作
函数 | 最坏复杂度 | 平均复杂度 |
---|---|---|
add | O(n+n) = O(n) | O(n/2+1) = O(n) |
addFirst | O(n+n) = O(n) | O(n+1) = O(n) |
addLast | O(1+n) = O(n) | O(1+1) = O(1) |
add 的最坏复杂度 O(n+n) 中第一个 n 是指元素移动操作,第二个 n 是指 resize 函数,以下同理。
增加可能会引发扩容操作,平均而言,每增加 n 个元素,会扩展一次,会发生 n 个元素的移动,所以平均下来是 O(1) 。
3.2 删除操作
函数 | 最坏复杂度 | 平均复杂度 |
---|---|---|
remove | O(n+n) = O(n) | O(n/2+1) = O(n) |
removeFirst | O(n+n) = O(n) | O(n+1) = O(n) |
removeLast | O(1+n) = O(n) | O(1+1) = O(1) |
同理,删除操作与增加操作类似。
3.3 修改操作
函数 | 最坏复杂度 | 平均复杂度 |
---|---|---|
set | O(1) | O(1) |
3.4 查找操作
函数 | 最坏复杂度 | 平均复杂度 |
---|---|---|
get | O(1) | O(1) |
find | O(n) | O(n/2) = O(n) |
contains | O(n) | O(n/2) = O(n) |
总体情况:
操作 | 时间复杂度 |
---|---|
增 | O(n) |
删 | O(n) |
改 | 已知索引O(1);未知索引O(n) |
查 | 已知索引O(1);未知索引O(n) |
由此可以看出,数组比较适用于已知索引情况下的数据存放,也就是说,适用于索引有意义的情况。
4. 完整代码
程序完整代码(这里使用了头文件的形式来实现类)如下:
#ifndef __ARRAY_H__
#define __ARRAY_H__
using namespace std;
const int initialLen = 10;
template <class T>
class Array{
public:
Array(int len = initialLen){
T *p = new T[len];
m_data = p;
m_capacity = len;
m_size = 0;
}
int capacity(){
return m_capacity;
}
int size(){
return m_size;
}
void resize(int len){
T *p = new T[len];
for (int i = 0; i < m_size; ++i){
p[i] = m_data[i];
}
delete[] m_data;
m_data = p;
m_capacity = len;
}
bool isEmpty(){
return m_size == 0;
}
void print(){
cout << "Array: ";
cout << "Capacity = " << m_capacity << ", " << "Size = " << m_size << endl;
cout << '[';
for (int i = 0; i < m_size; ++i){
cout << m_data[i];
if (i != m_size - 1){
cout << ',';
}
}
cout << ']' << endl;
}
//增加操作
void add(int index, T num);
void addFirst(T num);
void addLast(T num);
//删除操作
T remove(int index);
T removeFirst();
T removeLast();
void removeElement(T num);
//修改操作
void set(int index, T num);
//查找操作
T get(int index);
int find(T num);
bool contains(T num);
private:
T *m_data; //数组数据
int m_capacity; //数组容量
int m_size; //数组大小
};
template <typename T>
void Array<T>::add(int index, T num){
if (index < 0 || index > m_size){
cout << "添加位置非法!" << endl;
return;
//throw 0; //这里可以使用抛异常,下面也可以
}
if (m_size >= m_capacity){
resize(2 * m_capacity);
}
for (int i = m_size; i > index; --i){
m_data[i] = m_data[i - 1];
}
m_data[index] = num;
m_size++;
}
template <class T>
void Array<T>::addFirst(T num){
add(0, num);
}
template <class T>
void Array<T>::addLast(T num){
if (m_size >= m_capacity){
resize(2 * m_capacity);
}
m_data[m_size] = num;
m_size++;
}
template <class T>
T Array<T>::remove(int index){
if (index < 0 || index >= m_size){
cout << "删除位置非法!" << endl;
return NULL;
}
T res = m_data[index];
m_size--;
for (int i = index; i < m_size; ++i){
m_data[i] = m_data[i + 1];
}
if (m_size < m_capacity / 4){
resize(m_capacity / 2);
}
return res;
}
template <class T>
T Array<T>::removeFirst(){
T res = m_data[0];
remove(0);
return res;
}
template <class T>
T Array<T>::removeLast(){
if (m_size == 0){
cout << "删除位置非法!" << endl;
return NULL;
}
m_size--;
if (m_size < m_capacity / 4){
resize(m_capacity / 2);
}
return m_data[m_size];
}
template <class T>
void Array<T>::removeElement(T num){
int flag = find(num);
if (flag != -1){
remove(flag);
}
}
template <class T>
void Array<T>::set(int index, T num){
if (index < 0 || index >= m_size){
cout << "修改位置非法!" << endl;
return;
}
m_data[index] = num;
}
template <class T>
T Array<T>::get(int index){
if (index < 0 || index >= m_size){
cout << "访问位置非法!" << endl;
return NULL;
}
return m_data[index];
}
template <class T>
int Array<T>::find(T num){
for (int i = 0; i < m_size; ++i){
if (m_data[i] == num){
return i;
}
}
return -1;
}
template <class T>
bool Array<T>::contains(T num){
for (int i = 0; i < m_size; ++i){
if (m_data[i] == num){
return true;
}
}
return false;
}
#endif