💊1.1顺序的定义
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。(要求数据在物理上是连续存储的)
💊1.2顺序表的分类
💊1.2.1静态顺序表
静态顺序表使用定长数组存储数据
💊1.2.2静态顺序表的定义
首先我们要实现的是静态顺序表,所以我们一定要采用数组,其次作为一个静态顺序表它还需要一个变量来存储它内部所存储的数据的数量;
所以我们可以构建一个结构体,其中包含两个变量:
一个定长的数组(可以采用宏定义的方法来控制数组大小,方便后期随时修改);一个整型变量(用来存储当前静态顺序表中所存储的数据的个数);
上代码:
//创建静态的顺序表 #define N 100 struct SeqList { int a[N]; int size; };
💊1.2.3动态顺序表
对于静态顺序表来说我们使用的比较少,而且实现起来也比较简单,所以本文主要讲解关于动态顺序表的实现
💊1.2.4动态顺序表的定义
首先动态顺序表是由动态开辟的数组存储数据,和静态顺序表相比他的存储空间可以根据需要来进行扩充。所以动态顺序表利用的就是一段动态开辟的数组存储;
💊1.2.5动态顺序表的实现
对于动态顺序表,所以我们一定要采用指针(用于指向动态开辟的数组,方便后期的扩容),作为一个动态顺序表它还需要一个变量来存储它内部所存储的数据的数量,由于动态顺序表当空间不足时需要对数组的空间进行扩容,所以相比较静态顺序表,动态顺序表还要多一个变量来记录当前顺序表的大小;
所以我们可以构建一个结构体,其中包含三个变量:
一个指针(类型可以用 typedef 来声明一下,方便后期修改数据类型);
一个整型变量(用来存储当前动态顺序表中所存储的数据的个数);
一个整型变量(用来存储当前动态顺序表的容量);
上代码:
typedef int SeqType; typedef struct SeqList { SeqType* a; int size; //有效数据的个数 int capacity; //容量大小 }SeqList;
对于动态顺序表来说我们主要实现以下几个函数功能:
//顺序表的初始化 void SeqListInit(SeqList* pSeq); //顺序表的销毁 void SeqListDestroy(SeqList* pSeq); //打印顺序表中的数据 void SeqListPrint(SeqList* pSeq); //检查顺序表的大小是否够用 void CheckSeqList(SeqList* pSeq); //顺序表的尾插 void SeqListPushBack(SeqList* pSeq, SeqType x); //顺序表的头插 void SeqListPushFront(SeqList* pSeq, SeqType x); //顺序表的尾删 void SeqListPopBack(SeqList* pSeq); //顺序表的头删 void SeqListPopFront(SeqList* pSeq); //在顺序表寻找元素 int SeqListFind(SeqList* pSeq, SeqType x); //在顺序表任意位置插入 void SeqListInsert(SeqList* pSeq,int pos ,SeqType x); //在顺序表任意位置删除 void SeqListErase(SeqList* pSeq, int pos); //给顺序表修改 void SeqListModify(SeqList* pSeq, int pos, SeqType x);
所以接下来将细讲,以上函数功能该如何实现,并且实现的过程中存在那些细节和要注意的一些点;
①首先对于第一个函数功能对顺序表进行初始化;
void SeqListInit(SeqList* pSeq);对于初始化,我们要实现对于动态顺序表的数组指针置空,并且size和capacity全部置零;
所以该函数的实现非常简单,我们直接上代码:
//顺序表的初始化 void SeqListInit(SeqList* pSeq) { //断言 //如果pSeq为空指针则终止程序,并且显示程序错误的位置 //assert(pSeq != NULL); assert(pSeq); pSeq->a = NULL; pSeq->size = pSeq->capacity = 0; }
注意:我们观察函数声明发现函数的参数类型选用了指针类型,这是为什么呢,因为我们在C语言的函数中,函数形参只是实参的一份临时拷贝,如果我们不用指针来修改顺序表的参数的话,那么我们是不能达到修改的效果的(在函数内部修改,并不会影响函数外部的情况);
②顺序表销毁函数:
void SeqListDestroy(SeqList* pSeq);
对于顺序表的销毁,我们要实现功能非常简单,只需要释放掉数组指针的动态内存空间,然后再将顺序表的size和capacity全部置零;
上代码:
//顺序表的销毁 void SeqListDestroy(SeqList* pSeq) { assert(pSeq); free(pSeq->a); pSeq->a = NULL; pSeq->size = pSeq->capacity = 0; }
注意:这个函数功能非常简单所以需要注意的点就是对于动态内存空间的释放,释放结束后一定要记得对数组指针进行置空,防止野指针问题;
③打印顺序表中的数据:void SeqListPrint(SeqList* pSeq);
该函数的功能就是实现将顺序表中的数据从头到尾依次打印出来即可;
代码:
//打印顺序表中的数据 void SeqListPrint(SeqList* pSeq) { assert(pSeq); for (int i = 0; i < pSeq->size; i++) { printf("%d ", pSeq->a[i]); } printf("\n"); }
注意:这个函数有一个点需要注意,更换了SeqType之后,也就是更换了顺序表的数据类型的时候,这块printf函数内部的参数需要进行手动修改。
④检查顺序表的容量是否需要扩充
void CheckSeqList(SeqList* pSeq);
该函数的功能就是在每次需要向顺序表中插入数据的时候,检查顺序表的容量是否足够,如果不足对其进行扩容;
上代码:
//检查顺序表的大小是否够用 void CheckSeqList(SeqList* pSeq) { //如果空间不够则需要增容 if (pSeq->size == pSeq->capacity) { int newcapacity = pSeq->capacity == 0 ? 4 : pSeq->capacity * 2; SeqList* newA = (SeqList*)realloc(pSeq->a, sizeof(SeqList) * newcapacity); if (newA == NULL) { printf("realloc is fail\n SeqListPushBack"); exit(-1); } pSeq->a = newA; pSeq->capacity = newcapacity; } }
注意:这个函数内部使用的是realloc函数申请动态内存空间,因为realloc函数的功能可以在扩充内存的同时,将原本空间中的数据也拷贝到新申请的更大的空间中;
⑤在顺序表任意位置插入数据
void SeqListInsert(SeqList* pSeq,int pos ,SeqType x);该函数的功能就是实现在顺序表的任意位置插入数据,所以函数总共由三个参数,第一个顺序表数组指针,第二个是要插入的位置,第三个是要插入的数据;因为插入数据存在顺序表的容量是否足够的问题,所以我们在该函数内部调用了
void CheckSeqList(SeqList* pSeq);以应对容量不足的情况;
上代码:
//在顺序表任意位置插入 void SeqListInsert(SeqList* pSeq, int pos, SeqType x) { assert(pSeq); assert(pos >= 0 && pos <= pSeq->size); CheckSeqList(pSeq); for (int i = pSeq->size; i > pos; i--) { pSeq->a[i] = pSeq->a[i - 1]; } pSeq->a[pos] = x; pSeq->size++; }
注意:在这个函数内部牵扯到移动顺序表中数据的情况,所以我们要先进行容量大小的判断。然后在进行数据的插入;
对于挪动数据的时候我们要采取从后往前的挪的方法,因为这样的话就可以保证原本顺序表中的数据不会被覆盖而丢失;
⑥在顺序表任意位置删除数据
void SeqListErase(SeqList* pSeq, int pos);这个函数的功能就是实现在顺序表的任意位置插入数据,参数与上一个函数类似,就不再介绍了;
直接上代码:
//在顺序表任意位置删除 void SeqListErase(SeqList* pSeq, int pos) { assert(pSeq); assert(pos >= 0 && pos <= pSeq->size); for (int i = pos; i < pSeq->size - 1; i++) { pSeq->a[i] = pSeq->a[i + 1]; } pSeq->size--; }
注意:这个函数是直接从要插入的位置从前往后开始移动数据,直接将要删除的数据进行覆盖,任意位置插入函数的操作正好相反
⑦顺序表的头插头删:
void SeqListPushFront(SeqList* pSeq, SeqType x);
void SeqListPopFront(SeqList* pSeq);首先来说头删函数,对于这个函数其实就是执行了上面的在顺序表的任意位置插入数据函数,只是将其中的pos参数修改为0即可。同理头删函数,也可以理解为是在顺序表的任意位置删除数据函数,将其中的pos参数修改为0即可;
所以上代码:
//头部插入 void SeqListPushFront(SeqList* pSeq, SeqType x) { SeqListInsert(pSeq, 0, x); } //头部删除 void SeqListPopFront(SeqList* pSeq) { SeqListErase(pSeq, 0); }
⑧顺序表的尾插尾删:
void SeqListPushBack(SeqList* pSeq, SeqType x);
void SeqListPopBack(SeqList* pSeq);这两个函数的实现可以说和上面的两个函数非常相似,只是将任意位置插入函数和任意位置删除函数的参数pos换成ps->size;
所以上代码:
//尾插 void SeqListPushBack(SeqList* pSeq, SeqType x) { SeqListInsert(pSeq, pSeq->size, x); } //尾部删除 void SeqListPopBack(SeqList* pSeq) { SeqListErase(pSeq, pSeq->size); }
⑨在顺序表中寻找数据:
int SeqListFind(SeqList* pSeq, SeqType x);
该函的功能是实现在顺序表中寻找数据,就是简单的遍历整个顺序表,当找到数据时就返回下标;所以直接上代码:
//在顺序表寻找元素 int SeqListFind(SeqList* pSeq, SeqType x) { for (int i = 0; i < pSeq->size; i++) { if (x == pSeq->a[i]) { return i; } } return -1; }
注意:但是对于上面的函数有一个缺点,若是寻找的数据在顺序表中存在多个,那么这个函数只会返回第一个匹配数据的下标
而不能返回剩下元素的下标;所以我们对该函数进行改造,再此基础上引入一个新的参数:int begin,目的就是让函数从begin的位置开始寻找数据,可以跳过之前所匹配的函数从而找出所有匹配的数据;、
上代码:
//在顺序表寻找元素 int SeqListFind(SeqList* pSeq, SeqType x,int begin) { for (int i = begin; i < pSeq->size; i++) { if (x == pSeq->a[i]) { return i; } } return -1; }
⑩修改顺序表中指定位置的数据:
void SeqListModify(SeqList* pSeq, int pos, SeqType x);
该函数实现的是修改顺序表中指定位置的数据,所以函数的参数包括,顺序表的数组指针,要修改的位置pos,和要修改成的数据x;函数的功能实现非常简单,所以我们直接上代码:
//给顺序表修改 void SeqListModify(SeqList* pSeq, int pos, SeqType x) { assert(pos >= 0 && pos < pSeq->size); pSeq->a[pos] = x; }
注意:该函数内部需要对size 的范围进行判断,以防止产生越界访问
对于以上函数有一个要注意的细节就是我们在删除顺序表中的数据的时候,我们不能无线的删除,当size==0的时候我们就不能进行删除操作了,对于这个问题,我的代码中采取的是断言的方法,比较粗暴的停止程序,但是当程序停止的时候我们可以迅速知道是哪里出了问题;
完整代码:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<assert.h> #include<stdlib.h> 创建静态的顺序表 // //#define N 100 //struct SeqList //{ // int a[N]; // int size; //}; typedef int SeqType; typedef struct SeqList { SeqType* a; int size; //有效数据的个数 int capacity; //容量大小 }SeqList; //顺序表的初始化 void SeqListInit(SeqList* pSeq); //顺序表的销毁 void SeqListDestroy(SeqList* pSeq); //打印顺序表中的数据 void SeqListPrint(SeqList* pSeq); //检查顺序表的大小是否够用 void CheckSeqList(SeqList* pSeq); //顺序表的增删查改的接口 // void SeqListPushBack(SeqList* pSeq, SeqType x); void SeqListPushFront(SeqList* pSeq, SeqType x); void SeqListPopBack(SeqList* pSeq); void SeqListPopFront(SeqList* pSeq); //在顺序表寻找元素 int SeqListFind(SeqList* pSeq, SeqType x); //在顺序表任意位置插入 void SeqListInsert(SeqList* pSeq,int pos ,SeqType x); //在顺序表任意位置删除 void SeqListErase(SeqList* pSeq, int pos); //给顺序表修改 void SeqListModify(SeqList* pSeq, int pos, SeqType x); #define _CRT_SECURE_NO_WARNINGS 1 #include"SeqList.h" //顺序表的初始化 void SeqListInit(SeqList* pSeq) { //断言 //如果pSeq为空指针则终止程序,并且显示程序错误的位置 //assert(pSeq != NULL); assert(pSeq); pSeq->a = NULL; pSeq->size = pSeq->capacity = 0; } //顺序表的销毁 void SeqListDestroy(SeqList* pSeq) { assert(pSeq); free(pSeq->a); pSeq->a = NULL; pSeq->size = pSeq->capacity = 0; } //打印顺序表中的数据 void SeqListPrint(SeqList* pSeq) { assert(pSeq); for (int i = 0; i < pSeq->size; i++) { printf("%d ", pSeq->a[i]); } printf("\n"); } //检查顺序表的大小是否够用 void CheckSeqList(SeqList* pSeq) { //如果空间不够则需要增容 if (pSeq->size == pSeq->capacity) { int newcapacity = pSeq->capacity == 0 ? 4 : pSeq->capacity * 2; SeqList* newA = (SeqList*)realloc(pSeq->a, sizeof(SeqList) * newcapacity); if (newA == NULL) { printf("realloc is fail\n SeqListPushBack"); exit(-1); } pSeq->a = newA; pSeq->capacity = newcapacity; } } //尾插 void SeqListPushBack(SeqList* pSeq, SeqType x) { SeqListInsert(pSeq, pSeq->size, x); //assert(pSeq); 如果空间不够则需要增容 //CheckSeqList(pSeq); //pSeq->a[pSeq->size] = x; //pSeq->size++; } //头插 void SeqListPushFront(SeqList* pSeq, SeqType x) { SeqListInsert(pSeq, 0, x); //assert(pSeq); //CheckSeqList(pSeq); //int end = pSeq->size - 1; //while (end >= 0) //{ // pSeq->a[end + 1] = pSeq->a[end]; // end--; //} //pSeq->a[0] = x; //pSeq->size++; } //尾部删除 void SeqListPopBack(SeqList* pSeq) { SeqListErase(pSeq, pSeq->size); //assert(pSeq); //assert(pSeq->size > 0);//判断size是否大于零 //pSeq->size--; } //头部删除 void SeqListPopFront(SeqList* pSeq) { SeqListErase(pSeq, 0); //assert(pSeq); //assert(pSeq->size > 0);//判断size是否大于零 //for (int i = 1; i < pSeq->size; i++) //{ // pSeq->a[i - 1] = pSeq->a[i]; //} //pSeq->size--; } //在顺序表寻找元素 int SeqListFind(SeqList* pSeq, SeqType x) { for (int i = 0; i < pSeq->size; i++) { if (x == pSeq->a[i]) { return i; } } return -1; } //在顺序表任意位置插入 void SeqListInsert(SeqList* pSeq, int pos, SeqType x) { assert(pSeq); assert(pos >= 0 && pos <= pSeq->size); CheckSeqList(pSeq); for (int i = pSeq->size; i > pos; i--) { pSeq->a[i] = pSeq->a[i - 1]; } pSeq->a[pos] = x; pSeq->size++; } //在顺序表任意位置删除 void SeqListErase(SeqList* pSeq, int pos) { assert(pSeq); assert(pos >= 0 && pos <= pSeq->size); for (int i = pos; i < pSeq->size - 1; i++) { pSeq->a[i] = pSeq->a[i + 1]; } pSeq->size--; } //给顺序表修改 void SeqListModify(SeqList* pSeq, int pos, SeqType x) { assert(pos >= 0 && pos < pSeq->size); pSeq->a[pos] = x; }
以上就是动态顺序表的实现和各个函数需要注意的细节,如果有错误可以在评论区指正!