綱要:api
順序表是用一段物理地址連續的存儲單元依次存儲數據元素的線性結構,通常狀況下采用數組存儲。在數組上完成數據的增刪查改。數組
順序表通常能夠分爲:數據結構
1. 靜態順序表:使用定長數組存儲。ide
2. 動態順序表:使用動態開闢的數組存儲函數
如:測試
// 順序表的靜態存儲 #define N 100 typedef int SLDataType; typedef struct SeqList { SLDataType array[N]; // 定長數組 size_t size; // 有效數據的個數 }SeqList;
// 順序表的動態存儲 typedef struct SeqList { SLDataType* array; // 指向動態開闢的數組 size_t size ; // 有效數據個數 size_t capicity ; // 容量空間的大小 }SeqList;
對於靜態順序表的操做就是簡單的數組操做,咱們主要重點講解動態順序表的操做。spa
當咱們來寫順序表的時候,咱們要想到咱們在表裏所儲存的類型是否能夠直接寫爲普通的數據類型(如 int, float ...),它是不是一成不變的,當咱們有一天須要改變類型,咱們又該怎麼作?3d
因此咱們就來用一個宏來實現咱們的數據類型的定義:調試
如:code
//將int做爲咱們的數據類型,如有改變咱們多數狀況下只需改變這一行便可,即將int改成別的數據類型 typedef int DataType;
而後再來定義咱們的順序表:
//將結構體重命名,是爲了方便使用 typedef struct SeqList { DataType* data; // 指向動態開闢的數組 size_t size ; // 有效數據個數 size_t capicity ; // 容量空間的大小 }SeqList;
注意到定義結構體時,咱們是不能進行直接賦值的,因此咱們來寫一個順序表初始化函數:
void SeqListInit(SeqList *p) { //傳地址進來的時候要記得斷言,要保證它不爲NULL,否則會形成非法訪問等錯誤 assert(p); // 指向動態開闢的數組 p->data = NULL; // 有效數據個數 p->size = 0; // 容量空間的大小 p->capicity = 1; }
咱們在寫代碼的時候要記得寫完一個模塊咱們就要測試一下,防止到最終代碼寫完了,一運行不知道是哪的錯,所謂 「碼代一時爽,bug找斷腸」
因此咱們來測試一下:
void SeqListTest1() { //先來定義一個順序表變量 SeqList list; //將其地址賦值於 p,咱們在修改時,要傳 址 進去,這樣才能夠改變他的值,切不可傳 值 進去! SeqList* p =&list; //對於一個結構體咱們在其定義時,是不能夠直接賦值進去的 //如: //typedef struct SeqList //{ // // DataType* data = NULL; // 指向動態開闢的數組 // // size_t size = 0; // 有效數據個數 // // size_t capicity =1; // 容量空間的大小 //}SeqList; //上述代碼是錯誤示例!!! //因此咱們要寫一個初始化函數 SeqListInit(p);//傳 址 進去 }
運行結果:
初始化完了咱們就該插入數據了,首先咱們先來最簡單的尾插,不過咱們先要來想想,插入的時候咱們是否是要先檢查一下咱們的數據個數是否已經等於了容量,若是這樣咱們就須要先擴容一下。
//擴容函數 void CheckCapacity(SeqList* p) {
assert(p); //若是容量於數據個數相同就擴容,還有一種狀況p->size=0的時候也須要擴容 //1.p->size = p->capacity 的時候,這時若是咱們要插入數據,容量不夠,形成非法訪問 //2.p->size = 0的時候,這時說明咱們剛剛初始化完成,此時的p->data仍是NULL,咱們不能直接對於NULL進行解引用和修改 if(p->size==p->capicity||p->size==0) { //擴容擴大先前容量的兩倍 DataType* ptr=(DataType*)realloc(p->data,sizeof(DataType)*(p->capicity*2)); if(ptr==NULL) { //錯誤提示,下面兩個效果相同 //printf("REALLOC ERROR!:%s",strerror(errno)); perror("REALLOC ERROR!\n"); exit(-1); } //賦值,realloc可在原地址開闢,也可在一個新的地址開闢,咱們用p原接收回來 p->data = ptr; //容量擴大兩倍 p->capicity*=2; } //不須要擴容就什麼也不幹 }
注意:
擴容的兩種狀況:
1.p->size = p->capacity 的時候,這時若是咱們要插入數據,容量不夠,形成非法訪問
2.p->size = 0的時候,這時說明咱們剛剛初始化完成,此時的p->data仍是NULL,咱們不能直接對於NULL進行解引用和修改
尾插:
void SeqListPushBack(SeqList* p,DataType InsertData) {
assert(p); //檢查是否須要擴容 CheckCapacity(p); //尾插就是在末尾插入,即p->size的位置插入數據 p->data[p->size]=InsertData; //插完以後數據個數加一 p->size++; }
固然了,咱們要是想看到結果,最好將它打印一下:
void SeqListPrint(SeqList* p) {
assert(p); int i =0; for(i=0;i<p->size;i++) { printf("%d ",p->data[i]); } printf("\n"); }
測試:
SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p);
運行結果:
細心一點的朋友已經發現了,咱們並無釋放這塊空間,因此銷燬函數:
//銷燬函數 void SeqListDistory(SeqList* p) { assert(p); free(p->data); }
接下來就是頭插了,咱們想想頭插咱們應該怎麼實現,能不能像尾插那樣簡單——固然不能了,因此咱們接下來思考的重點就是怎麼樣將頭部的位置空出來:
void SeqListPushFront(SeqList* p,DataType InsertData) { assert(p); //檢查是否須要擴容 CheckCapacity(p); //頭插,咱們須要先將數據從後往前移 int i =0; for(i=p->size+1;i>0;i--) { //從後往前依次挪動 p->data[i]=p->data[i-1]; } //挪完之後數據數首位置插入 p->data[0]=InsertData; //插入以後數據加一 p->size++; }
SeqListInit(p);//傳 址 進去 SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p); SeqListPushFront(p,1); SeqListPushFront(p,2); SeqListPushFront(p,3); SeqListPushFront(p,4); SeqListPushFront(p,5); SeqListPrint(p); SeqListDistory(p);
運行結果:
接下來是尾刪:
void SeqListPopBack(SeqList* p) { assert(p); //若是p->size=0 說明就沒有數據可供刪除 assert(p->size>0); //由於是尾刪,因此咱們能夠直接讓 p->size - 1便可,不須要其餘操做 p->size--; }
SeqListInit(p);//傳 址 進去 SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p); SeqListPushFront(p,1); SeqListPushFront(p,2); SeqListPushFront(p,3); SeqListPushFront(p,4); SeqListPushFront(p,5); SeqListPrint(p); SeqListPopBack(p); SeqListPopBack(p); SeqListPopBack(p); SeqListPrint(p); SeqListDistory(p);
運行結果:
遵循上面的規律,接下來就是頭刪了,一樣咱們來想想它的挪動
void SeqListPopFront(SeqList* p) { assert(p); //若是p->size=0 說明就沒有數據可供刪除 assert(p->size>0); //頭刪,咱們採起從前日後移 int i =0; for(i=0;i<p->size;i++) { //從前日後依次挪動 p->data[i]=p->data[i+1]; } //刪除以後數據減一 p->size--; }
SeqListInit(p);//傳 址 進去 SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p); SeqListPushFront(p,1); SeqListPushFront(p,2); SeqListPushFront(p,3); SeqListPushFront(p,4); SeqListPushFront(p,5); SeqListPrint(p); SeqListPopBack(p); SeqListPopBack(p); SeqListPopBack(p); SeqListPrint(p); SeqListPopFront(p); SeqListPopFront(p); SeqListPopFront(p); SeqListPrint(p); SeqListDistory(p);
運行結果:
到這特殊的增長與刪除就結束了,接下來咱們談談普通位置的添加與刪除
普通位置的增長就是添加一個位置進去,以那個位置爲界,採起頭插的挪法:
void SeqListInsert(SeqList*p ,int pos,DataType InsertData) { assert(p); //確保插入的位置有效 assert(pos>=0); assert(pos<=p->size); //檢查容量 CheckCapacity(p); int i=0; //到 pos 位中止 for(i=p->size+1;i>pos;i--) { //從後往前移 p->data[i]=p->data[i-1]; } //在pos位插入數據 p->data[pos]=InsertData; //插入以後數據數加一 p->size++; }
void SeqListTest2() { SeqList list; SeqList* p =&list; SeqListInit(p); SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p); SeqListPushFront(p,1); SeqListPushFront(p,2); SeqListPushFront(p,3); SeqListPushFront(p,4); SeqListPushFront(p,5); SeqListPrint(p); SeqListInsert(p,0,0); SeqListInsert(p,p->size,0); SeqListInsert(p,p->size/2,0); SeqListInsert(p,p->size/2/2,0); SeqListInsert(p,p->size/2+p->size/2/2,0); SeqListPrint(p); SeqListDistory(p); }
運行結果:
普通位置的刪除,方法也同頭刪,只需將pos設爲界便可:
void SeqListErase(SeqList* p,int pos) { assert(p); //確保刪除的位置有效 assert(pos>=0); assert(pos<=p->size); int i =0; for(i=pos;i<p->size;i++) { //從前日後依次挪動 p->data[i]=p->data[i+1]; } //刪除以後數據減一 p->size--; }
void SeqListTest2() { SeqList list; SeqList* p =&list; SeqListInit(p); SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p); SeqListPushFront(p,1); SeqListPushFront(p,2); SeqListPushFront(p,3); SeqListPushFront(p,4); SeqListPushFront(p,5); SeqListPrint(p); SeqListInsert(p,0,0); SeqListInsert(p,p->size,0); SeqListInsert(p,p->size/2,0); SeqListInsert(p,p->size/2/2,0); SeqListInsert(p,p->size/2+p->size/2/2,0); SeqListPrint(p); SeqListErase(p,0); SeqListErase(p,p->size); SeqListErase(p,p->size/2); SeqListErase(p,p->size/2/2-1); SeqListErase(p,p->size/2/2+p->size/2); SeqListPrint(p); SeqListDistory(p); }
運行結果:
注:
到這咱們能夠把咱們的特殊位置的增長和刪除與任意位置來整合一下,這樣有什麼好處呢?
共用一個接口的時候,要是接口沒錯,那麼都不會錯。反之,要是一個函數錯誤,還得去分析是哪的錯誤。
因此:
void SeqListPushBack(SeqList* p,DataType InsertData) { // assert(p); // //檢查是否須要擴容 // CheckCapacity(p); // //尾插就是在末尾插入,即p->size的位置插入數據 // p->data[p->size]=InsertData; // //插完以後數據個數加一 // p->size++; SeqListInsert(p,p->size,InsertData); } void SeqListPushFront(SeqList* p,DataType InsertData) { // assert(p); // //檢查是否須要擴容 // CheckCapacity(p); // //頭插,咱們須要先將數據從後往前移 // int i =0; // for(i=p->size+1;i>0;i--) // { // //從後往前依次挪動 // p->data[i]=p->data[i-1]; // } // //挪完之後數據數首位置插入 // p->data[0]=InsertData; // //插入以後數據加一 // p->size++; SeqListInsert(p,0,InsertData); } void SeqListPopBack(SeqList* p) { // assert(p); // //若是p->size=0 說明就沒有數據可供刪除 // assert(p->size>0); // //由於是尾刪,因此咱們能夠直接讓 p->size - 1便可,不須要其餘操做 // p->size--; SeqListErase(p,p->size); } void SeqListPopFront(SeqList* p) { // assert(p); // //若是p->size=0 說明就沒有數據可供刪除 // assert(p->size>0); // //頭刪,咱們採起從前日後移 // int i =0; // for(i=0;i<p->size;i++) // { // //從前日後依次挪動 // p->data[i]=p->data[i+1]; // } // //刪除以後數據減一 // p->size--; SeqListErase(p,0); }
對於此,咱們直接能夠採用遍歷循環的方法進行查找及修改
修改:
void SeqListModify(SeqList* p,int pos,DataType ModifyData) { assert(p); //確保修改的位置有效 assert(pos>=0); assert(pos<=p->size); //在pos位進行修改 p->data[pos]=ModifyData; }
查找:
int SeqListFind(SeqList* p,DataType SearchData) { assert(p); int i =0; for(i=0;i<p->size;i++) { if(p->data[i]==SearchData) { //找到就返回下標 return i; } } //找不到就返回-1 return -1; }
void SeqListTest2() { SeqList list; SeqList* p =&list; SeqListInit(p); SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p); SeqListPushFront(p,1); SeqListPushFront(p,2); SeqListPushFront(p,3); SeqListPushFront(p,4); SeqListPushFront(p,5); SeqListPrint(p); SeqListInsert(p,0,0); SeqListInsert(p,p->size,0); SeqListInsert(p,p->size/2,0); SeqListInsert(p,p->size/2/2,0); SeqListInsert(p,p->size/2+p->size/2/2,0); SeqListPrint(p); SeqListErase(p,0); SeqListErase(p,p->size); SeqListErase(p,p->size/2); SeqListErase(p,p->size/2/2-1); SeqListErase(p,p->size/2/2+p->size/2); SeqListPrint(p); SeqListModify(p,0,0); SeqListModify(p,p->size,0); SeqListModify(p,4,0); SeqListModify(p,5,0); SeqListPrint(p); SeqListDistory(p); }
運行結果:
1. 中間/頭部的插入刪除,時間複雜度爲O(N)
2. 增容須要申請新空間,拷貝數據,釋放舊空間。會有不小的消耗。
3. 增容通常是呈2倍的增加,勢必會有必定的空間浪費。例如當前容量爲100,滿了之後增容到200, 咱們再繼續插入了5個數據,後面沒有數據插入了,那麼就浪費了95個數據空間。
最後總代碼以下:
//保證編譯時只包含一次,防止重複包含 #ifndef TEST_TEST_H #define TEST_TEST_H #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <assert.h> //將int做爲咱們的數據類型,如有改變咱們多數狀況下只需改變這一行便可,即將int改成別的數據類型 typedef int DataType; //將結構體重命名,是爲了方便使用 typedef struct SeqList { DataType* data; // 指向動態開闢的數組 size_t size ; // 有效數據個數 size_t capicity ; // 容量空間的大小 }SeqList; //順序表初始化函數 void SeqListInit(SeqList* p); //打印內容 void SeqListPrint(SeqList* p); //銷燬函數 void SeqListDistory(SeqList* p); //尾插 void SeqListPushBack(SeqList* p,DataType InsertData); //頭插 void SeqListPushFront(SeqList* p,DataType InsertData); //尾刪 void SeqListPopBack(SeqList* p); //頭刪 void SeqListPopFront(SeqList* p); //任意位置插入 void SeqListInsert(SeqList*p ,int pos,DataType InsertData); //任意位置刪除 void SeqListErase(SeqList* p,int pos); //修改 void SeqListModify(SeqList* p,int pos,DataType ModifyData); #endif //TEST_TEST_H
// 強調!!! //調試請加setbuf(stdout,NULL)!!! #include "test.h" //擴容函數 void CheckCapacity(SeqList* p) { assert(p); //若是容量於數據個數相同就擴容,還有一種狀況p->size=0的時候也須要擴容 //1.p->size = p->capacity 的時候,這時若是咱們要插入數據,容量不夠,形成非法訪問 //2.p->size = 0的時候,這時說明咱們剛剛初始化完成,此時的p->data仍是NULL,咱們不能直接對於NULL進行解引用和修改 if(p->size==p->capicity||p->size==0) { //擴容擴大先前容量的兩倍 DataType* ptr=(DataType*)realloc(p->data,sizeof(DataType)*(p->capicity*2)); if(ptr==NULL) { //錯誤提示,下面兩個效果相同 //printf("REALLOC ERROR!:%s",strerror(errno)); perror("REALLOC ERROR!\n"); exit(-1); } //賦值,realloc可在原地址開闢,也可在一個新的地址開闢,咱們用p原接收回來 p->data = ptr; //容量擴大兩倍 p->capicity*=2; } //不須要擴容就什麼也不幹 } void SeqListPrint(SeqList* p) { assert(p); int i =0; for(i=0;i<p->size;i++) { printf("%d ",p->data[i]); } printf("\n"); } void SeqListDistory(SeqList* p) { assert(p); free(p->data); } void SeqListInit(SeqList *p) { //傳地址進來的時候要記得斷言,要保證它不爲NULL,否則會形成非法訪問等錯誤 assert(p); // 指向動態開闢的數組 p->data = NULL; // 有效數據個數 p->size = 0; // 容量空間的大小 p->capicity = 1; } void SeqListPushBack(SeqList* p,DataType InsertData) { // assert(p); // //檢查是否須要擴容 // CheckCapacity(p); // //尾插就是在末尾插入,即p->size的位置插入數據 // p->data[p->size]=InsertData; // //插完以後數據個數加一 // p->size++; SeqListInsert(p,p->size,InsertData); } void SeqListPushFront(SeqList* p,DataType InsertData) { // assert(p); // //檢查是否須要擴容 // CheckCapacity(p); // //頭插,咱們須要先將數據從後往前移 // int i =0; // for(i=p->size+1;i>0;i--) // { // //從後往前依次挪動 // p->data[i]=p->data[i-1]; // } // //挪完之後數據數首位置插入 // p->data[0]=InsertData; // //插入以後數據加一 // p->size++; SeqListInsert(p,0,InsertData); } void SeqListPopBack(SeqList* p) { // assert(p); // //若是p->size=0 說明就沒有數據可供刪除 // assert(p->size>0); // //由於是尾刪,因此咱們能夠直接讓 p->size - 1便可,不須要其餘操做 // p->size--; SeqListErase(p,p->size); } void SeqListPopFront(SeqList* p) { // assert(p); // //若是p->size=0 說明就沒有數據可供刪除 // assert(p->size>0); // //頭刪,咱們採起從前日後移 // int i =0; // for(i=0;i<p->size;i++) // { // //從前日後依次挪動 // p->data[i]=p->data[i+1]; // } // //刪除以後數據減一 // p->size--; SeqListErase(p,0); } void SeqListInsert(SeqList*p ,int pos,DataType InsertData) { assert(p); //確保插入的位置有效 assert(pos>=0); assert(pos<=p->size); //檢查容量 CheckCapacity(p); int i=0; //到 pos 位中止 for(i=p->size+1;i>pos;i--) { //從後往前移 p->data[i]=p->data[i-1]; } //在pos位插入數據 p->data[pos]=InsertData; //插入以後數據數加一 p->size++; } void SeqListErase(SeqList* p,int pos) { assert(p); //確保刪除的位置有效 assert(pos>=0); assert(pos<=p->size); int i =0; for(i=pos;i<p->size;i++) { //從前日後依次挪動 p->data[i]=p->data[i+1]; } //刪除以後數據減一 p->size--; } void SeqListModify(SeqList* p,int pos,DataType ModifyData) { assert(p); //確保修改的位置有效 assert(pos>=0); assert(pos<=p->size); //在pos位進行修改 p->data[pos]=ModifyData; } int SeqListFind(SeqList* p,DataType SearchData) { assert(p); int i =0; for(i=0;i<p->size;i++) { if(p->data[i]==SearchData) { //找到就返回下標 return i; } } //找不到就返回-1 return -1; }
#include "test.h" //測試一 void SeqListTest1() { //先來定義一個順序表變量 SeqList list; //將其地址賦值於 p,咱們在修改時,要傳 址 進去,這樣才能夠改變他的值,切不可傳 值 進去! SeqList* p =&list; //對於一個結構體咱們在其定義時,是不能夠直接賦值進去的 //如: //typedef struct SeqList //{ // // DataType* data = NULL; // 指向動態開闢的數組 // // size_t size = 0; // 有效數據個數 // // size_t capicity =1; // 容量空間的大小 //}SeqList; //上述代碼是錯誤示例!!! //因此咱們要寫一個初始化函數 SeqListInit(p);//傳 址 進去 SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p); SeqListPushFront(p,1); SeqListPushFront(p,2); SeqListPushFront(p,3); SeqListPushFront(p,4); SeqListPushFront(p,5); SeqListPrint(p); SeqListPopBack(p); SeqListPopBack(p); SeqListPopBack(p); SeqListPrint(p); SeqListPopFront(p); SeqListPopFront(p); SeqListPopFront(p); SeqListPrint(p); SeqListDistory(p); } void SeqListTest2() { SeqList list; SeqList* p =&list; SeqListInit(p); SeqListPushBack(p,1); SeqListPushBack(p,2); SeqListPushBack(p,3); SeqListPushBack(p,4); SeqListPushBack(p,5); SeqListPrint(p); SeqListPushFront(p,1); SeqListPushFront(p,2); SeqListPushFront(p,3); SeqListPushFront(p,4); SeqListPushFront(p,5); SeqListPrint(p); SeqListInsert(p,0,0); SeqListInsert(p,p->size,0); SeqListInsert(p,p->size/2,0); SeqListInsert(p,p->size/2/2,0); SeqListInsert(p,p->size/2+p->size/2/2,0); SeqListPrint(p); SeqListErase(p,0); SeqListErase(p,p->size); SeqListErase(p,p->size/2); SeqListErase(p,p->size/2/2-1); SeqListErase(p,p->size/2/2+p->size/2); SeqListPrint(p); SeqListModify(p,0,0); SeqListModify(p,p->size,0); SeqListModify(p,4,0); SeqListModify(p,5,0); SeqListPrint(p); SeqListDistory(p); } int main() { //不放在main函數裏寫,是爲了方便測試 SeqListTest1(); SeqListTest2(); return 0; }
|--------------------------------------------------------
關於順序表的知識到這便結束了
因筆者水平有限,如有錯誤,還望指正