<!--author--Kang-->
<!--time--2020/7/26-->
線性表的順序存儲:
用一組連續的存儲單元一次存儲線性表的數據元素,通常這種存儲結構為線性表的順序表(Sequential List)。特點時邏輯上相鄰的數據元素再物理次序上也是相鄰的
a[i]的地址計算(偏移量):數組首地址+i*每個元素大小
是在計算機內存中以數組的形式保存的線性表,是用一組地址連續的存儲單元依次存儲相同數據類型的數據元素的線性結構。
順序表的增刪改查
一、順序表的創建
把學號為 1 的學生存放在數組下標為 0 的位置中,接着依次存儲即可。所以為了創建一個順序表,必須要在內存中開辟一塊空間,那么第一個數據元素的首地址就變得非常關鍵;
此外,既然順序表是容器,那么容器的容量一定要確定,就是最多允許存放多少個數據元素,而數組的長度就是這個容器的最大容量。
另外對於當前容器中已經存放了多少個數據元素也是很重要的。
根據上面的分析,我們發現順序表有三個必須的屬性:
1)實現機制:運用數組來存放數據元素,數組的首地址就是線性表的首地址。
2)最大容量:數組的長度。
3)線性表長度:數組中已經存放的數據元素的個數。
根據這些信息,我們可以定義出線性表的順序存儲的結構體類型了:
#define MAX_SIZE 100//定義順序表的容量 typedef int ElementType;//表中元素類型,以int為例,給int取別名(也可以是結構體類型) typedef struct seqential_list{ ElementType data[MAX_SIZE];//順序表的實現核心,使用數組存儲 int length;//順序表長度 }SEQ_LIST; 注意: //typedef int ElementType; 中ElementType為數據元素類型,當元素類型為結構體類型時,也適用 #define MAX_SIZE 100 typedef struct student{ int number; char name[32]; char gennder[4]; }STUDENT; typedef STUDENT ElementType;//此時元素類型為學生結構體類型,並給該類型取別名為ElementType typedef struct seqential_list{ ElementType data[MAX_SIZE];//順序表的實現核心,使用靜態數組分配方式存儲元素, /* 使用動態分配方式: */ /* ElementType *data; data = (ElementType *) malloc (sizeof(ElementType)*MAX_SIZE); */ int length;//順序表長度,表示當前順序表中存放元素個數(不是數組長度) }
順序表的創建:
首先時初始化操作,初始化的目的時要將順序表准備好,以便后續的其他操作,因此一般會在定義了順序表后調用一次。
使用靜態數組實現的順序表的初始化:
由於數組已經定義完成,而對於順序表的操作基本思都依賴於其商都,所以只需要在初始化時將長度賦為0即可,甚至於直接在定義該順序表時直接初始化即可:
//初始化函數: int init_list(SEQ_LIST *L){ L->length = 0; return 1; } //或者定義順序表時直接初始化化: SEQ_LIST list = {0};
使用動態分配內存的方式實現的順序表的初始化:
在函數中完成內存分配和清空操作
int init_list(SEQ_LIST *L){ L->data = (ElementType *) malloc(sizeof(ElementType) * MAX_SIZE); if(L->data == null)//內存可能會分配失敗 { return 0; } L->length = 0; return 1; }
注意:對於動態分配內存的方式實現的順序表,最好還需要補充一個操作,即銷毀線性表的操作,用來釋放程序中申請到的內存空間。
順序表的其他操作:
獲取順序表的長度:
int gei_length(SEQ_LIST L){ return L.length; }
判斷線性表是否為空:
int is_empty(SEQ_LIST L){ return L.length == 0; }
順序表的遍歷:
(實質為數組的循環輸出,其終止條件與順序表長度有關)
void show(SEQ_LIST *L){ for(int j = 0;j<L-length;j++){ printf("%d",L-data[j]);//這里以數組元素為int類型為例,若不是基本類型,需要在這里修改輸出內容 if(j<L-length-1){// 為了美觀,這里對於前面的數據輸出時加一個逗號 printf(","); }else{//最后一個符號用回車符 printf("\n"); } } }
順序表的插入:
步驟:
1)判斷順序表是否已滿,如果滿了,操作失敗;
2)判斷插入位置是否合理,不合理則操作失敗;
3)從最后一個數據元素開始向前遍歷到第index位置,分別將它們依次向后移動一位;
4)將要插入的元素填入第index位置處;
5)順序表長度+1;
//插入操作: int insert(SEQ_LIST *L, int i, ElementType e){//傳指針,把e插入到順序表第i個位置,i為下標,實際上要減一處理, //1)判斷順序表是否已滿,如果滿了,操作失敗; if (L->length == MAX_SIZE) { return 0;//0表示失敗 } //2)判斷插入位置是否合理,不合理則操作失敗; if (i<1||i>L->length+1)//合理位置:1<=index<=MAX_SIZE { return 0;//0表示失敗 } //3)從最后一個數據元素開始向前遍歷到第index位置,分別將它們依次向后移動一位; for (int j =L->length-1;j>=i-1; j--) { L->data[j + 1] = L->data[j];//將下標為j的元素賦值給下標為j+1的元素,完成元素后移一位操作 } //4)將要插入的元素填入第index位置處; L->data[i - 1] = e;//將元素e插入到第i個位置(i-1); //5)順序表長度 + 1; L->length++; return 1; }
//main函數
int main() { SEQ_LIST list; init_list(&list); /*int rlt = insert(&list,2,1); printf("%d\n", rlt);*///輸出為0,說明失敗,因為順序表需要按順序插入,第一個位置沒有插入數據,直接插入第二個位置會失敗 insert(&list, 1, 1); insert(&list, 2, 7); insert(&list, 3, 2); insert(&list, 4, 8); insert(&list, 5, 9); insert(&list, 6, 6); insert(&list, 7, 2); insert(&list, 8, 8); insert(&list, 9, 5); insert(&list, 10, 0); show(&list);//輸出:1,7,2,8,9,6,2,8,5,0 printf("=======================\n"); //插入已有元素位置 insert(&list, 1, 0); insert(&list, 10, 1);//此時數組長度為11位,前面插入了元素0,再從新數組第十個位置上再插入元素1 show(&list);//輸出:【0】,1,7,2,8,9,6,2,8,【1】,5,0 return 0; }
順序表的刪除
步驟:
1)判斷順序表是否為空,如果沒有數據,操作失敗;
2)判斷刪除位置是否何理,不合理則操作失敗;
3)從被刪除的數據開始,依次被后一位置上的數據覆蓋,直到最后一個數據
4)順序表當前長度減一
//刪除操作 int Delete(SEQ_LIST *L,int i) { //1)判斷順序表是否為空,如果沒有數據,操作失敗; if (L->length ==0) { return 0; } //2)判斷刪除位置是否何理,不合理則操作失敗; if (i<1 || i>L->length)//1<=index<=L->length 這里是第i個元素,不是下標 { return 0;//0表示失敗 } //3)從被刪除的數據開始,依次被后一位置上的數據覆蓋,直到最后一個數據 for (int j = i-1; j < L->length-1; j++)//第i個元素下標為i-1,j的取值范圍為 i-1 <= j <= L->length-1 { L->data[j] = L->data[j + 1]; } //4)順序表當前長度減一 L->length--; return 1; }
//main:接在上面插入操作main中調用后面 int rlt = Delete(&list, 6);//輸出0表示刪除失敗,輸出1表示刪除成功 if (rlt) { printf("刪除成功!\n"); show(&list); }else{ printf("刪除失敗!\n"); }
順序表的修改:
步驟:
1)判斷順序表是否為空,如果為空,則操作失敗;
2)判斷修改位置是否合理,不合理則操作失敗;
3)將i位置中的數據修改成最新值。
//修改操作 int update(SEQ_LIST *L,int i,ElementType e) { //1)判斷順序表是否為空,如果為空,則操作失敗; if (L->length == 0) { return 0; } //2)判斷修改位置是否合理,不合理則操作失敗; if (i < 1 || i > L->length) { return 0; } //3)將i位置中的數據修改成最新值。 L->data[i - 1] = e; return 1;//修改成功 }
//調用修改 int rlt2 = update(&list, 10, 2); if (rlt2) { printf("修改成功!\n");//0,1,7,2,8,6,2,8,1,【2】,0 show(&list); } else { printf("修改失敗!\n"); }
順序表的查詢操作:
步驟:
1)判斷順序表是否為空,如果為空,則操作失敗;
2)從第一個數據元素開始向后遍歷,比對是否與被查找數相等,直到最后一個數據;
3)如果找到,返回此數據元素在順序表的位置,否則返回0,代表數據表中無此數據元素
int serch(SEQ_LIST *L,ElementType e) { //1)判斷順序表是否為空,如果為空,則操作失敗; if (L->length ==0) { return 0; } //2)從第一個數據元素開始向后遍歷,比對是否與被查找數相等,直到最后一個數據; for (int i = 0; i < L->length; i++) { if (L->data[i]==e) { return i+1; } } //3)如果找到,返回此數據元素在順序表的位置,否則返回0,代表數據表中無此數據元素 return 0; }
//main函數調用: int rlt3 = serch(&list, 7); printf("rlt3所在位置為第%d個元素\n", rlt3);
優化后:
步驟:
1)判斷順序表是否為空,如果為空,則操作失敗;
2)從指定位置開始向后遍歷,比對是否與被查找數相等,直到最后一個數據;
3)如果找到,返回此數據元素在順序表的位置,否則返回0,代表數據表中無此數據元素
//方案二:查找操作優化版 int serch2(SEQ_LIST *L, int from,ElementType e) { //1)判斷順序表是否為空,如果為空,則操作失敗; if (L->length == 0) { return 0; } //2)從第from個(下標為 from-1)數據元素開始向后遍歷,比對是否與被查找數相等,直到最后一個數據; for (int i = from-1; i < L->length; i++) { if (L->data[i] == e) { return i + 1; } } //3)如果找到,返回此數據元素在順序表的位置,否則返回0,代表數據表中無此數據元素 return 0; }
//main: //查找操作方案二調用: int from = 0; while (1) { from = serch2(&list, from+1, 0); if (from == 0) { break; } printf("匹配到所查元素與順序表中第%d個元素相同\n",from); }
至此,順序表的增刪改查結束!
順序表操作的時間復雜度:
插入操作:如果元素剛好插在最后一個位置,那么線性表中數據元素無需移動,此時時間復雜度為O(1),即最好情況。如果元素插在第一個位置,那么所有元素都要向后移動一位,時間復雜度為O(n),即最壞情況。取其平均值,(n+1)/2所以插入的平均時間復雜度為O(n)。
刪除、查找與插入操作的時間復雜度相同;
修改操作的時間復雜度,因為基於固定位置無需進行比對或者移動,所以時間復雜度為O(1)
順序表的優缺點:
順序表的存儲在邏輯上和物理上都是連續的,因為物理空間連續,所以可以快速的存取表里任何數據,但是當需要增加或者減少數據元素時,就需要進行大量的移動操作;此外,線性表底層運用數組實現,一旦數組初始化確定長度后,很難再擴充容量或者需要額外的性能開銷。所以線性表的順序存儲有之獨特的優勢,也有很多無法忍受的缺陷:
優點: ①無需為表示表中元素之間的邏輯關系而增加額外的空間
②可以快速地存取表中任意位置的元素
缺點: ①插入和刪除需要大量地移動數據
②順序表的存儲容量難以擴充
P.S.源碼:
#include<stdio.h> #include <malloc.h> #define MAX_SIZE 100//定義順序表的容量 typedef int ElementType;//表中元素類型,以int為例,給int取別名(也可以是結構體類型) typedef struct seqential_list { ElementType data[MAX_SIZE]; //順序表的實現核心,使用數組存儲 int length;//順序表長度 }SEQ_LIST; //typedef int ElementType; 中ElementType為數據元素類型,當元素類型為結構體類型時,也適用 //#define MAX_SIZE 100 // //typedef struct student { // int number; // char name[32]; // char gennder[4]; //}STUDENT; // //typedef STUDENT ElementType;//此時元素類型為學生結構體類型,並給該類型取別名為ElementType // // //typedef struct seqential_list { // ElementType data[MAX_SIZE];//順序表的實現核心,使用靜態數組分配方式存儲元素, // /* // 使用動態分配方式: // */ // //ElementType *data; //data = (ElementType *)malloc(sizeof(ElementType)*MAX_SIZE); // //int length;//順序表長度,表示當前順序表中存放元素個數(不是數組長度) //}SEQ_lIST; // // //初始化函數: int init_list(SEQ_LIST *L) { L->length = 0; return 1; } //或者定義順序表時直接初始化化: //SEQ_LIST list = { 0 }; //獲取長度 int gei_length(SEQ_LIST L) { return L.length; } //判斷是否為空 int is_empty(SEQ_LIST L) { return L.length == 0; } //順序表遍歷: void show(SEQ_LIST *L) { for (int j = 0; j < L->length; j++) { printf("%d", L->data[j]);//這里以數組元素為int類型為例,若不是基本類型,需要在這里修改輸出內容 if (j < L->length - 1) {// 為了美觀,這里對於前面的數據輸出時加一個逗號 printf(","); } else {//最后一個符號用回車符 printf("\n"); } } } //插入操作: int insert(SEQ_LIST *L, int i, ElementType e){//在原數組上修改要傳指針,把e插入到順序表第i個位置,i為下標,實際上要減一處理, //1)判斷順序表是否已滿,如果滿了,操作失敗; if (L->length == MAX_SIZE) { return 0;//0表示失敗 } //2)判斷插入位置是否合理,不合理則操作失敗; if (i<1||i>L->length+1)//合理位置:1<=index<=L->length+1 ①這里是第i個元素,不是下標 ② 插入到最后一個元素后面則i為 length+1 { return 0;//0表示失敗 } //3)從最后一個數據元素開始向前遍歷到第index位置,分別將它們依次向后移動一位; for (int j =L->length-1;j>=i-1; j--) { L->data[j + 1] = L->data[j];//將下標為j的元素賦值給下標為j+1的元素,完成元素后移一位操作 } //4)將要插入的元素填入第index位置處; L->data[i - 1] = e;//將元素e插入到第i個位置(i-1); //5)順序表長度 + 1; L->length++; return 1; } //刪除操作 int Delete(SEQ_LIST *L,int i) { //1)判斷順序表是否為空,如果沒有數據,操作失敗; if (L->length ==0) { return 0; } //2)判斷刪除位置是否何理,不合理則操作失敗; if (i<1 || i>L->length)//1<=index<=L->length 這里是第i個元素,不是下標 { return 0;//0表示失敗 } //3)從被刪除的數據開始,依次被后一位置上的數據覆蓋,直到最后一個數據 for (int j = i-1; j < L->length-1; j++)//第i個元素下標為i-1,j的取值范圍為 i-1 <= j <= L->length-1 { L->data[j] = L->data[j + 1]; } //4)順序表當前長度減一 L->length--; return 1; } //修改操作 int update(SEQ_LIST *L,int i,ElementType e) { //1)判斷順序表是否為空,如果為空,則操作失敗; if (L->length == 0) { return 0; } //2)判斷修改位置是否合理,不合理則操作失敗; if (i < 1 || i > L->length) { return 0; } //3)將i位置中的數據修改成最新值。 L->data[i - 1] = e; return 1;//修改成功 } //查找操作 //方案一:所查找的元素無重復項,該方法只能查找第一個與所查元素相同的位置,后面重復的位置無法查找 int serch(SEQ_LIST *L,ElementType e) { //1)判斷順序表是否為空,如果為空,則操作失敗; if (L->length ==0) { return 0; } //2)從第一個數據元素開始向后遍歷,比對是否與被查找數相等,直到最后一個數據; for (int i = 0; i < L->length; i++) { if (L->data[i]==e) { return i+1; } } //3)如果找到,返回此數據元素在順序表的位置,否則返回0,代表數據表中無此數據元素 return 0; } //方案二:查找操作優化版 int serch2(SEQ_LIST *L, int from,ElementType e) { //1)判斷順序表是否為空,如果為空,則操作失敗; if (L->length == 0) { return 0; } //2)從第from個(下標為 from-1)數據元素開始向后遍歷,比對是否與被查找數相等,直到最后一個數據; for (int i = from-1; i < L->length; i++) { if (L->data[i] == e) { return i + 1; } } //3)如果找到,返回此數據元素在順序表的位置,否則返回0,代表數據表中無此數據元素 return 0; } int main() { SEQ_LIST list; init_list(&list); /*int rlt = insert(&list,2,1); printf("%d\n", rlt);*///輸出為0,說明失敗,因為順序表需要按順序插入,第一個位置沒有插入數據,直接插入第二個位置會失敗 insert(&list, 1, 1); insert(&list, 2, 7); insert(&list, 3, 2); insert(&list, 4, 8); insert(&list, 5, 9); insert(&list, 6, 6); insert(&list, 7, 2); insert(&list, 8, 8); insert(&list, 9, 5); insert(&list, 10, 0); show(&list);//輸出:1,7,2,8,9,6,2,8,5,0 printf("=======================\n"); //插入已有元素位置 insert(&list, 1, 0); insert(&list, 10, 1);//此時數組長度為11位,前面插入了元素0,再從新數組第十個位置上再插入元素1 show(&list);//輸出:【0】,1,7,2,8,9,6,2,8,【1】,5,0 //調用刪除 int rlt1 = Delete(&list, 6);//輸出0表示刪除失敗,輸出1表示刪除成功 if (rlt1) { printf("刪除成功!\n"); show(&list); }else{ printf("刪除失敗!\n"); } //調用修改 int rlt2 = update(&list, 10, 2); if (rlt2) { printf("修改成功!\n");//0,1,7,2,8,6,2,8,1,【2】,0 show(&list); } else { printf("修改失敗!\n"); } //調用查找操作: int rlt3 = serch(&list, 7); printf("rlt3所在位置為第%d個元素\n", rlt3); //查找操作方案二調用: int from = 0; while (1) { from = serch2(&list, from+1, 0); if (from == 0) { break; } printf("匹配到所查元素與順序表中第%d個元素相同\n",from); } return 0; }