數據結構之 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 $,其中單個元素佔 個單位空間。
- 對於任何, A[i]都是A[j]的前驅,特別地,當時,A[i]是[j]的直接前驅,A[j]的所有前驅稱爲其前綴;相應地,A[j]都是A[i]的後繼,特別地,當時,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次,平均移動 次,即時間複雜度爲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