纲要: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; }
|--------------------------------------------------------
关于顺序表的知识到这便结束了
因笔者水平有限,如有错误,还望指正