四、【线性表】线性表的顺序表示和实现
線性表的順序表示和實現
前文我們提到過線性表是邏輯結構,只說明了數據元素之間的相互關系,想要使用線性表,我們還需要在計算機上表示出這些數據元素以及元素之間的關系。而對于同一種邏輯結構,可以有多種存儲結構來實現它。線性表作為一種基礎的數據結構,可以用順序存儲和鏈式存儲兩種不同方式來實現,本節主要說明線性表的順序表示。
1 線性表的順序表示
1.1 順序表的定義
線性表的順序存儲又稱為順序表, 它是用一組地址連續的存儲單元依次存儲線性表中的數據元素,從而使得邏輯上相鄰的兩個元素在物理地址上也相鄰。順序表的特點是表中元素的邏輯順序與其物理順序相同。順序表是一種存儲結構,關注的是如何在計算機中存儲數據元素及其關系。
1.2 順序表的特點
假設線性表的每個元素需占用 lll 個存儲單元,并以所占的第一個單元的存儲地址作為數據元素的存儲位置。則線性表中第 i+1i+1i+1 個數據元素的存儲位置 LOC(ai+1)LOC(a_i+1)LOC(ai?+1) 和第 iii 個數據元素的存儲位置 LOC(ai)LOC(a_i)LOC(ai?) 之間滿足下列關系:
LOC(ai+1)=LOC(ai)+lLOC(a_i+1) = LOC(a_i) +l LOC(ai?+1)=LOC(ai?)+l
一般來說,線性表的第 iii 個數據元素 aia_iai? 的存儲位置為:
LOC(ai)=LOC(a1)+(i?1)?lLOC(a_i) = LOC(a_1) +(i-1)*l LOC(ai?)=LOC(a1?)+(i?1)?l
其中 LOC(a1)LOC(a_1)LOC(a1?) 是線性表的第一個數據元素 a1a_1a1? 的存儲位置,也被稱為線性表的起始位置或基地址。
觀察上圖我們可以發現,順序表的每一個數據元素的存儲位置都和線性表的起始位置相差一個和數據元素在線性表中的位序成正比的常數。由此,只要確定了存儲線性表的起始位置,表中任一數據元素都可以隨機存取(也稱為直接訪問),所以線性表的順序存儲結構是一種隨機存取的存儲結構。更廣泛地說,順序存儲一般對應隨機存取。
總結
順序表有以下主要特點:
- 隨機存取,讀取(或查找)操作時間復雜度為 O(1)O(1)O(1) 。
- 存儲密度高,每個節點只存儲數據元素。
- 邏輯上相鄰的元素物理上也相鄰,因此插入和刪除操作需要移動大量元素。
2 順序表的實現
由于高級程序語言中的數組類型也有隨機存取的特性,因此通常用數組來描述數據結構中的順序存儲結構。
下文的默認實現語言為C++,因此一切未注明的語法都以C++為準。
2.1 定義
C++中的一維數組可以是靜態分配的,也可以是動態分配的。詳情見C++動態數組。
-
靜態分配時,由于數組的大小和空間已經事先決定了,一但空間占滿,再加入新的數據就會產生溢出,導致程序崩潰。
/*** 靜態數組實現順序表的類型定義 ***/ #define MaxSize 10typedef int elemType; // 定義新的類型elemType typedef struct{elemType data[MaxSize]; // ElemType為指定元素類型int length; } SqList; -
動態分配就不存在這樣的問題,一旦空間占滿,就另外開辟一塊更大的存儲空間,用以替換原來的空間。但要注意的是,動態分配并不是鏈式存儲,它同樣屬于順序存儲結構,依然是隨機存取的方式。但是動態數組需要用戶顯式地釋放內存。
/*** 動態數組實現順序表的類型定義 ***/ #define InitSize 50 // 線性表存儲空間的初始分配量 #define Increment 10 // 線性表存儲空間的分配增量typedef int elemType; // 定義新的類型elemType typedef struct{ elemType *data; // 存儲空間基地址int capacity, length // 當前分配的最大容量和數組長度 } SqList
2.2 主要操作的實現
在順序存儲下,容易實現線性表的某些操作,如隨機存取第 iii 個元素等,因此這里只討論順序表較復雜的插入、刪除和按值查找操作。
因為靜態數組的局限性較大,后文正文內皆以動態數組實現為例展示代碼,靜態數組實現將放于文末附錄。
2.2.1 插入操作
在順序表 LLL 的第 i(1≤i≤L.length+1)i (1 \le i \le L.\mathrm{length+1})i(1≤i≤L.length+1) 個位置插入新元素 eee。若 iii 的輸入不合法,則返回 false,表示插入失敗;否則,將第 iii 個元素及其后的所有元素依次往后移動一個位置,順序表長度增加1,返回 true 。
/*** 動態數組順序表實現插入操作 ***/ // 擴容操作 void increment(SqList &L){elemType *data = new elemType[L.capacity+Increment]; // 重新申請內存for (int i=0;i<L.capacity;i++){ // 將原來的元素移動到新的順序表中data[i] = L.data[i];}delete[] L.data; // 釋放內存L.data = data;L.capacity += Increment;printf("Finish Increment.\n"); }// 插入操作 bool listInsert(SqList &L, int i, elemType e){if (i<1 || i>L.length+1){ // 判斷i的范圍是否有效printf("Index is illegal!\n");return false;} if (L.length >= L.capacity){ // 判斷存儲空間是否還有剩余,printf("Need Increment.\n");increment(L); // 不夠則擴容}for (int j=L.length;j>=i;j--){ // 將位置i及之后的元素向后移動一位L.data[j] = L.data[j-1];}L.data[i-1] = e;L.length++;return true; }時間復雜度分析
對插入操作來說,插入元素只需要一步,而移動元素可能重復很多次,所以基本操作為移動元素。我們只需要關心每一次插入操作發生時,總共移動了多少個元素,而移動元素的個數取決于插入新元素的位置。
- 最優情況:在表尾插入(即 i=n+1i = n+1i=n+1),元素不用后移,時間復雜度為 O(1)O(1)O(1)。
- 最差情況:
- 靜態數組:在表頭插入(即 i=1i = 1i=1),現存所有元素(共 nnn 個)都需后移,時間復雜度為 O(n)O(n)O(n)。
- 動態數組:在表頭插入且空間不夠,需先執行擴容操作,移動表內現存的所有元素,共 nnn 次。然后再插入新的元素,將表內的舊元素全部后移一位,共 nnn 次。總共需要 2n2n2n 次移動,時間復雜度為 O(n)O(n)O(n)。
- 平均情況:令 pip_ipi? 為在第 iii 個位置上插入一個元素的概率,假設任意一個位置上發生插入元素的概率是相等的,那么 pi=1(n+1)p_i = \frac{1}{(n+1)}pi?=(n+1)1?。在長度為 nnn 的線性表中插入一個元素時,所需移動元素的平均次數為
∑i=1n+1pi(n?i+1)=n2\sum_{i=1}^{n+1} p_i(n-i+1) = \frac{n}{2} i=1∑n+1?pi?(n?i+1)=2n?
因此,平均情況的時間復雜度為 O(n)O(n)O(n)。
2.2.2 刪除操作
刪除順序表 LLL 中第 i(1≤i≤L.length+1)i(1 \le i \le L.\mathrm{length+1})i(1≤i≤L.length+1) 個位置的元素,用引用變量 eee 返回。若 iii 的值不合法,則返回 false ;否則,將被刪除元素賦給引用變量 eee ,并將第 i=1i=1i=1 個元素及其后的所有元素依次往前移動一個位置,返回 true 。
/*** 動態數組順序表實現刪除操作 ***/ // 刪除操作 bool listDelete(SqList &L, int i, elemType &e){if (i<1 || i>L.length){ // 判斷i的范圍是否有效printf("Index is illegal!\n");return false;}e = L.data[i-1]; // 將被刪除的元素的值賦給efor (int j=i;j<L.length;j++){ // 將位置i以后的元素向前移動一位L.data[j-1] = L.data[j];}L.length--;return true; }時間復雜度分析
分析和插入操作類似,刪除操作的基本操作也是對元素的移動。
- 最優情況:在表尾刪除(即 i=ni = ni=n),元素不用后移,時間復雜度為 O(1)O(1)O(1)。
- 最差情況:在表頭刪除(即 i=1i = 1i=1),現存所有元素(共 nnn 個)都需前移,時間復雜度為 O(n)O(n)O(n)。
- 平均情況:令 pip_ipi? 為在第 iii 個位置上刪除一個元素的概率,假設任意一個位置上發生插入元素的概率是相等的,那么 pi=1np_i = \frac{1}{n}pi?=n1?。在長度為 nnn 的線性表中插入一個元素時,所需移動元素的平均次數為
∑i=1npi(n?i)=n?12\sum_{i=1}^{n} p_i(n-i) = \frac{n-1}{2} i=1∑n?pi?(n?i)=2n?1?
因此,平均情況的時間復雜度為 O(n)O(n)O(n)。
2.2.3 按值查找操作
在順序表 LLL 中查找第一個元素值等于 eee 的元素,并返回其位序。
/*** 動態數組順序表實現按位查找操作 ***/ // 按值查找操作 int locateElem(SqList L, elemType e){for (int i=0;i<L.length;i++){if (L.data[i] == e){return i+1;}}return 0; }時間復雜度分析
查找操作的基本操作是比較元素,因此我們只關注元素的比較次數。
- 最優情況:查找元素就在表頭(即 i=1i = 1i=1),只用比較一次,時間復雜度為 O(1)O(1)O(1)。
- 最差情況:查找元素在表尾(即 i=ni = ni=n)或不存在時,需要比較表內的所有元素,共 nnn 次,時間復雜度為 O(n)O(n)O(n)。
- 平均情況:令 pip_ipi? 為查找元素出現在第 iii 個位置上的概率,假設任意一個位置上發生插入元素的概率是相等的,那么 pi=1np_i = \frac{1}{n}pi?=n1?。在長度為 nnn 的線性表中插入一個元素時,所需移動元素的平均次數為
∑i=1npi×i=∑i=1n1n×i=1nn(n+1)2=n+12\sum_{i=1}^{n} p_i \times i = \sum_{i=1}^{n}\frac{1}{n}\times i = \frac{1}{n} \frac{n(n+1)}{2} = \frac{n+1}{2} i=1∑n?pi?×i=i=1∑n?n1?×i=n1?2n(n+1)?=2n+1?
因此,平均情況的時間復雜度為 O(n)O(n)O(n)。
參考資料:
【C++】細說C++中的數組之“靜態”數組
【C++】細說C++中的數組之動態數組
相關章節
第一節 【緒論】數據結構的基本概念
第二節 【緒論】算法和算法評價
第三節 【線性表】線性表概述
第四節 【線性表】線性表的順序表示和實現
第五節 【線性表】線性表的鏈式表示和實現
第六節 【線性表】雙向鏈表、循環鏈表和靜態鏈表
第七節 【棧和隊列】棧
第八節 【棧和隊列】棧的應用
第九節 【棧和隊列】棧和遞歸
第十節 【棧和隊列】隊列
附錄
靜態數組實現順序表的完整代碼
/** File: SqList.h* -------------------------* Using struct to implement SqList*//********** 使用靜態數組實現順序表 **********/ #ifndef _STATIC_SEQUENTIAL_LIST_h_ #define _STATIC_SEQUENTIAL_LIST_h_#include <stdio.h> #include <iostream> using namespace std;/*** 靜態數組順序表的定義 ***/ #define MaxSize 10 // 默認靜態數組最大容量為10typedef int elemType; // 定義新的類型elemType typedef struct {elemType data[MaxSize];int length; // 記錄靜態數組的有效長度 } SqList;/*** 基本操作的實現 ***/ // 初始化操作 void initList(SqList &L){L.length = 0; // 將有效長度歸零 }// 銷毀操作 void destroyList(SqList &L){}// 判空操作 bool listEmpty(SqList L){return not L.length; }// 求表長操作 int listLength(SqList L){return L.length; }// 按位查找操作,如果存在返回1,否則返回0 int getElem(SqList L, int i, elemType &e){if (i<1 || i>L.length) {printf("Index is illegal!\n");return 0;} else {e = L.data[i-1];return 1;} }// 按值查找操作,返回第一個符合查找要求的元素的位置,如果不存在則返回0 int locateElem(SqList L, elemType e){for (int i=0;i<L.length;i++){if (L.data[i] == e){return i+1;}}return 0; }// 插入操作,在位值i處插入元素e bool listInsert(SqList &L, int i, elemType e){if (i<1 || i>L.length+1){ // 判斷i的范圍是否有效printf("Index is illegal!\n");return false;} if (L.length >= MaxSize){ // 判斷存儲空間是否還有剩余printf("Out of space!\n");return false;}for (int j=L.length;j>=i;j--){ // 將位置i及之后的元素向后移動一位L.data[j] = L.data[j-1];}L.data[i-1] = e;L.length++;return true; }// 刪除操作,刪除第i個位置的元素,并用e返回刪除元素的值 bool listDelete(SqList &L, int i, elemType &e){if (i<1 || i>L.length){ // 判斷i的范圍是否有效printf("Index is illegal!\n");return false;}e = L.data[i-1]; // 將被刪除的元素的值賦給efor (int j=i;j<L.length;j++){ // 將位置i以后的元素向前移動一位L.data[j-1] = L.data[j];}L.length--;return true; }// 輸出操作 void listPrint(SqList L){for (int i=0;i<L.length;i++){printf("%d ", L.data[i]);}printf("\n"); }#endif // _STATIC_SEQUENTIAL_LIST_h_動態數組實現順序表的完整代碼
/** File: SqList.h* -------------------------* Using struct to implement SqList*//********** 使用動態數組實現順序表 **********/ #ifndef _DYNAMIC_SEQUENTIAL_LIST_h_ #define _DYNAMIC_SEQUENTIAL_LIST_h_#include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std;/***** 動態數組順序表的定義 *****/ #define InitSize 10 // 動態數組初始容量為10 #define Increment 10 // 線性表存儲空間的分配增量typedef int elemType; // 定義新的類型elemType typedef struct {elemType *data;int capacity, length; // 動態數組當前最大容量和有效長度 } SqList;/***** 基本操作的實現 *****/ // 初始化操作 void initList(SqList &L){L.data = new elemType[InitSize]; // 申請內存L.length = 0;L.capacity = InitSize; }// 銷毀操作 void destroyList(SqList &L){if (L.data != NULL){delete[] L.data;L.length = 0;L.capacity = 0;} }// 判空操作 bool listEmpty(SqList L){return not L.length; }// 求表長操作 int listLength(SqList L){return L.length; }// 按位查找操作 int getElem(SqList L, int i, elemType &e){if (i<1 || i>L.length) {printf("Index is illegal!\n");return 0;} else {e = L.data[i-1];return 1;} }// 按值查找操作 int locateElem(SqList L, elemType e){for (int i=0;i<L.length;i++){if (L.data[i] == e){return i+1;}}return 0; }// 擴容操作 void increment(SqList &L){elemType *data = new elemType[L.capacity+Increment]; // 重新申請內存for (int i=0;i<L.capacity;i++){ // 將原來的元素移動到新的順序表中data[i] = L.data[i];}delete[] L.data; // 釋放內存L.data = data;L.capacity += Increment;printf("Finish Increment.\n"); }// 插入操作 bool listInsert(SqList &L, int i, elemType e){if (i<1 || i>L.length+1){ // 判斷i的范圍是否有效printf("Index is illegal!\n");return false;} if (L.length >= L.capacity){ // 判斷存儲空間是否還有剩余,printf("Need Increment.\n");increment(L); // 不夠則擴容}for (int j=L.length;j>=i;j--){ // 將位置i及之后的元素向后移動一位L.data[j] = L.data[j-1];}L.data[i-1] = e;L.length++;return true; }// 刪除操作 bool listDelete(SqList &L, int i, elemType &e){if (i<1 || i>L.length){ // 判斷i的范圍是否有效printf("Index is illegal!\n");return false;}e = L.data[i-1]; // 將被刪除的元素的值賦給efor (int j=i;j<L.length;j++){ // 將位置i以后的元素向前移動一位L.data[j-1] = L.data[j];}L.length--;return true; }// 輸出操作 void listPrint(SqList L){for (int i=0;i<L.length;i++){printf("%d ", L.data[i]);}printf("\n"); }#endif // _DYNAMIC_SEQUENTIAL_LIST_h_順序表檢測程序
/** File name: SqList.cpp* -----------------------* Using struct to implement SqList*/#include "SqList.h"int main(){SqList L;initList(L);int n, i, res;elemType e;char helpInfo[] = "*****************************\n" \"Sequential list check: \n" "\t1-Insert element\n" \"\t2-Delete element\n" \"\t3-Print\n" \"\t4-Empty check\n" \"\t5-Get Length\n" \"\t6-Search by value\n" \"\t7-Search by location\n" \"\t8-Initialization\n" \"\t9-Quit\n" \"*****************************\n"; while (n!= 9){printf(helpInfo);scanf("%d", &n);switch(n){case 1:printf("Please enter the location: ");scanf("%d", &i);printf("Please enter the element: ");scanf("%d", &e);listInsert(L, i, e);break;case 2:printf("Please enter the location: ");scanf("%d", &i);listDelete(L, i, e);printf("Element in location %d with value %d has been deleted.\n", i, e);break;case 3:printf("List: ");listPrint(L);break;case 4:if (listEmpty(L)) {printf("This list is empty!\n");}else {printf("This list is not empty!\n");}break;case 5:printf("Length of list is: %d\n", listLength(L));break;case 6:printf("Please enter the target value: ");scanf("%d", &e);res = locateElem(L, e);if (res) {printf("The index of target is %d\n", res);} else {printf("We didn't find the value.\n");}break;case 7:printf("Please enter the target index: ");scanf("%d", &i);res = getElem(L, i, e);if (res) {printf("The value of target is %d\n", e);}break;case 8:initList(L);printf("List has been initialized.\n");break;}}destroyList(L);return 0; }總結
以上是生活随笔為你收集整理的四、【线性表】线性表的顺序表示和实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三、【线性表】线性表概述
- 下一篇: 五、【线性表】线性表的链式表示和实现