1. 程式人生 > >資料結構實現(一):動態陣列(C++版)

資料結構實現(一):動態陣列(C++版)

資料結構實現(一):動態陣列(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;      //陣列大小
};

這裡為了避免重複設計就可以相容更多資料型別,引入了 泛型

,即 模板 的概念。(模板的關鍵字是 classtypename
這裡的 陣列容量 表示陣列最多可存放空間的大小,而 陣列大小 指的是陣列實際佔用空間的大小。為了保護資料,這兩個變數以及 陣列資料 都設定為 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){