在认识顺序表之前,我先介绍一下什么是线性表。
线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有顺序表、链表、栈、队列、字符串等。
线性表在逻辑结构上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理结构上存储时,通常以数组和链式结构的形式存储。
顺序表
一、顺序表
1.顺序表的概念和结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
2.静态顺序表和动态顺序表
1.静态顺序表:使用定长数组存储元素
缺点:1. 只能从头开始连续存储
2. 不知道需要多少空间
3. N给小了不够用,N给大了浪费
2.动态顺序表:使用动态开辟的数组存储
二、顺序表初始化和增容以及打印和销毁
静态顺序表只适用于确定需要存储多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,N定小了空间不够用。所以现实基本都是使用动态顺序表,根据需要动态的分配空间的大小,所以,下面我们实现动态顺序表。
(一)顺序表的初始化
//顺序表初始化 void SeqListInit(SeqList* ps1) { assert(ps1); //assert对指针进行判断,若判定为假则终止程序 ps1->array = NULL; //ps1 指向的 array初始化为空(NULL) ps1->size = 0; //ps1 指向的有效数据个数初始化为0 ps1->capacity = 0; //ps1 指向的空间容量大小初始化为0 }
(二)对顺序表空间进行检查,如果满了则进行增容
//对空间进行检查,如果满了则进行增容 void CheckCapacity(SeqList* ps1) { assert(ps1);//首先,代码使用断言assert来确保传入的动态顺序表指针ps1不为空 if (ps1->size == ps1->capacity)//判断有效数据个数是否和空间容量大小相等,相等则需要增容 { int newCapacity = ps1->capacity == 0 ? 4 : ps1->capacity * 2; //这里是三目运算,如果当前容量为0,表示动态顺序表为空,新的容量设置为4 //否则,新的容量设置为当前容量的两倍 SLDataType* tmp = (SLDataType*)realloc(ps1->array, sizeof(SLDataType)*newCapacity); //使用realloc函数来重新分配内存空间,将原来的数据重新分配到新的内存空间上 //注意,realloc函数可能会返回一个新的内存地址,所以需要使用一个临时变量tmp来接收 if (tmp == NULL)//如果realloc失败(即tmp为空),代码通过perror函数打印错误信息"realloc fail"并返回 { perror("realloc fail"); return; } //如果realloc成功,代码将临时变量tmp赋值给动态顺序表的数组指针ps1->array,同时更新容量大小ps1->capacity为新的容量大小newCapacity ps1->array = tmp; ps1->capacity = newCapacity; } }
(三)顺序表的打印
//顺序表的打印 void SeqListPrint(SeqList* ps1) { assert(ps1); //通过一个循环遍历动态顺序表的数据数组ps1->array,从下标0到下标size-1,也就是遍历有效数据的范围 for (int i = 0; i < ps1->size; i++) { printf("%d ", ps1->array[i]); } printf("\n"); }
(四)顺序表的销毁
void SeqListDestory(SeqList* ps1) { assert(ps1); //使用条件判断来检查动态顺序表的数组指针ps1->array是否为空。如果不为空,则表示动态顺序表已经分配了内存 if (ps1->array != NULL) { free(ps1->array); //使用free函数来释放动态顺序表的数组内存 ps1->array = NULL; //将动态顺序表的数组指针ps1->array置为NULL,表示动态顺序表的数组指针已经不再指向有效的内存 ps1->size = 0; ps1->capacity = 0; //将动态顺序表的有效数据个数ps1->size和容量ps1->capacity都设置为0,表示动态顺序表已经被销毁,不再包含任何元素 } }
三、顺序表接口实现(增删查改)
(一)顺序表尾插
//顺序表尾插 void SeqListPushBack(SeqList* ps1,SLDataType x) { assert(ps1); CheckCapacity(ps1);//调用CheckCapacity函数来检查动态顺序表的容量是否足够,如果不足则进行扩容操作 ps1->array[ps1->size] = x;//将新元素x赋值给动态顺序表中下标为ps1->size的位置,即插入到末尾 ps1->size++;//将动态顺序表的有效数据个数ps1->size加1,表示插入后有效数据个数增加了 }
(二)顺序表尾删
void SeqListPopBack(SeqList* ps1,SLDataType x) { assert(ps1); assert(ps1->size > 0);//使用断言assert来确保动态顺序表的有效数据个数ps1->size大于0,即确保动态顺序表非空 //这里也可以暴力检查 //if (psl->size == 0) // { // return; // } ps1->size--;//将动态顺序表的有效数据个数ps1->size减1,表示删除了一个末尾元素 }
(三)顺序表头插
void SeqListPushFront(SeqList* ps1,SLDataType x) { assert(ps1); CheckCapacity(ps1); int end = ps1->size - 1; //使用一个循环从末尾开始,将每个元素往后移动一位,为新元素腾出插入位置。循环的结束条件是end>=0,即从末尾元素到第一个元素进行移动 while (end >= 0) { ps1->array[end + 1] = ps1->array[end]; //将当前位置end的元素ps1->array[end]移动到下一个位置end+1上,即实现元素后移一位 --end; } ps1->array[0] = x;//将新元素x赋值给动态顺序表的第一个位置ps1->array[0],即插入到头部 ps1->size++;//将动态顺序表的有效数据个数ps1->size加1,表示插入后有效数据个数增加了 }
(四)顺序表头删
void SeqListPosFront(SeqList* ps1,SLDataType x) { assert(ps1); assert(ps1->size > 0);//使用断言assert来确保动态顺序表的有效数据个数ps1->size大于0,即确保动态顺序表非空 int begin = 1; //使用一个循环从第二个元素开始,将每个元素往前移动一位,覆盖掉前一个位置的元素。 //循环的结束条件是begin < ps1->size,即从第二个元素到最后一个元素进行移动 while (begin < ps1->size) { ps1->array[begin - 1] = ps1->array[begin]; //将当前位置begin的元素ps1->array[begin]移动到前一个位置begin-1上,即实现元素前移一位 ++begin; } ps1->size--;//将动态顺序表的有效数据个数ps1->size减1,表示删除了一个头部元素 }
(五)顺序表查找
//顺序表查找,查找指定元素的位置 int SeqListFind(SeqList* ps1,SLDataType x) { assert(ps1); //通过一个循环遍历动态顺序表的数据数组ps1->array,从下标0到下标size-1,也就是遍历有效数据的范围 for (int i = 0; i < ps1->size; i++) { //使用if语句判断当前位置的元素ps1->array[i]是否等于要查找的元素x。如果相等,则返回当前位置i,表示找到了该元素 if (ps1->array[i] == x) { return i; } } //如果循环结束后仍未找到元素,代码会返回-1,表示未找到该元素 return -1; }
(六)顺序表在pos位置插入x
void SeqListInsert(SeqList* ps1 ,int pos,SLDataType x) { assert(ps1); assert(pos >= 0 && pos <= ps1->size); //使用断言assert来确保插入位置pos符合要求,即大于等于0且小于等于动态顺序表的有效数据个数ps1->size CheckCapacity(ps1); int end = ps1->size - 1; //使用一个循环从末尾开始,将每个元素往后移动一位,为新元素腾出插入位置。循环的结束条件是end >= pos,即从末尾元素到插入位置进行移动 while (end >= pos) { ps1->array[end + 1] = ps1->array[end]; //将当前位置end的元素ps1->array[end]移动到下一个位置end+1上,即实现元素后移一位 --end; } ps1->array[pos] = x; //将新元素x赋值给动态顺序表的插入位置ps1->array[pos] ps1->size++; //将动态顺序表的有效数据个数ps1->size加1,表示插入元素后有效数据个数增加了 }
(七)顺序表删除pos位置的值
void SeqListErase(SeqList* ps1,int pos) { assert(ps1); assert(pos >= 0 && pos < ps1->size); //使用断言assert来确保删除位置pos符合要求,即大于等于0且小于动态顺序表的有效数据个数ps1->size int begin = pos + 1; //使用一个循环从删除位置的下一个位置开始,将每个元素往前移动一位,覆盖掉前一个位置的元素。 //循环的结束条件是begin < ps1->size,即从删除位置的下一个位置到最后一个元素进行移动 while (begin < ps1->size) { ps1->array[begin - 1] = ps1->array[begin]; //将当前位置begin的元素ps1->array[begin]移动到前一个位置begin-1上,即实现元素前移一位 ++begin; } ps1->size--;//将动态顺序表的有效数据个数ps1->size减1,表示删除了一个元素 }
四、顺序表总体实现
#include<stdio.h> #include<stdlib.h> #include<assert.h> //顺序表的动态存储 typedef int SLDataType; typedef struct SeqList { SLDataType* array; //指向动态开辟的数组 size_t size; //有效数据个数 size_t capacity; //容量空间的大小 }SeqList; //顺序表的初始化 void SeqListInit(SeqList* ps1); //对空间进行检查,如果满了则进行增容 void CheckCapacity(SeqList* ps1); //顺序表的打印 void SeqListPrint(SeqList* ps1); //顺序表尾插 void SeqListPushBack(SeqList* ps1,SLDataType x); //顺序表尾删 void SeqListPopBack(SeqList* ps1,SLDataType x); //顺序表头插 void SeqListPushFront(SeqList* ps1,SLDataType x); //顺序表头删 void SeqListPosFront(SeqList* ps1,SLDataType x); //顺序表查找 int SeqListFind(SeqList* ps1,SLDataType x); //在pos位置插入x void SeqListInsert(SeqList* ps1 ,int pos,SLDataType x); //删除pos位置的值 void SeqListErase(SeqList* ps1,int pos); //顺序表的销毁 void SeqListDestory(SeqList* ps1);
#include"sll_11_18.h" //顺序表初始化 void SeqListInit(SeqList* ps1) { assert(ps1); //assert对指针进行判断,若判定为假则终止程序 ps1->array = NULL; //ps1 指向的 array初始化为空(NULL) ps1->size = 0; //ps1 指向的有效数据个数初始化为0 ps1->capacity = 0; //ps1 指向的空间容量大小初始化为0 } //对空间进行检查,如果满了则进行增容 void CheckCapacity(SeqList* ps1) { assert(ps1);//首先,代码使用断言assert来确保传入的动态顺序表指针ps1不为空。 if (ps1->size == ps1->capacity)//判断有效数据个数是否和空间容量大小相等,相等则需要增容 { int newCapacity = ps1->capacity == 0 ? 4 : ps1->capacity * 2; //这里是三目运算,如果当前容量为0,表示动态顺序表为空,新的容量设置为4; //否则,新的容量设置为当前容量的两倍。 SLDataType* tmp = (SLDataType*)realloc(ps1->array, sizeof(SLDataType)*newCapacity); //使用realloc函数来重新分配内存空间,将原来的数据重新分配到新的内存空间上。 //注意,realloc函数可能会返回一个新的内存地址,所以需要使用一个临时变量tmp来接收。 if (tmp == NULL)//如果realloc失败(即tmp为空),代码通过perror函数打印错误信息"realloc fail"并返回。 { perror("realloc fail"); return; } //如果realloc成功,代码将临时变量tmp赋值给动态顺序表的数组指针ps1->array,同时更新容量大小ps1->capacity为新的容量大小newCapacity。 ps1->array = tmp; ps1->capacity = newCapacity; } } //顺序表的打印 void SeqListPrint(SeqList* ps1) { assert(ps1); //通过一个循环遍历动态顺序表的数据数组ps1->array,从下标0到下标size-1,也就是遍历有效数据的范围。 for (int i = 0; i < ps1->size; i++) { printf("%d ", ps1->array[i]); } printf("\n"); } //顺序表尾插 void SeqListPushBack(SeqList* ps1,SLDataType x) { assert(ps1); CheckCapacity(ps1);//调用CheckCapacity函数来检查动态顺序表的容量是否足够,如果不足则进行扩容操作。 ps1->array[ps1->size] = x;//将新元素x赋值给动态顺序表中下标为ps1->size的位置,即插入到末尾。 ps1->size++;//将动态顺序表的有效数据个数ps1->size加1,表示插入后有效数据个数增加了。 } //顺序表尾删 void SeqListPopBack(SeqList* ps1,SLDataType x) { assert(ps1); assert(ps1->size > 0);//使用断言assert来确保动态顺序表的有效数据个数ps1->size大于0,即确保动态顺序表非空。 //这里也可以暴力检查 //if (psl->size == 0) // { // return; // } ps1->size--;//将动态顺序表的有效数据个数ps1->size减1,表示删除了一个末尾元素。 } //顺序表头插 void SeqListPushFront(SeqList* ps1,SLDataType x) { assert(ps1); CheckCapacity(ps1); int end = ps1->size - 1; //使用一个循环从末尾开始,将每个元素往后移动一位,为新元素腾出插入位置。循环的结束条件是end>=0,即从末尾元素到第一个元素进行移动 while (end >= 0) { ps1->array[end + 1] = ps1->array[end]; //将当前位置end的元素ps1->array[end]移动到下一个位置end+1上,即实现元素后移一位 --end; } ps1->array[0] = x;//将新元素x赋值给动态顺序表的第一个位置ps1->array[0],即插入到头部 ps1->size++;//将动态顺序表的有效数据个数ps1->size加1,表示插入后有效数据个数增加了 } //顺序表头删 void SeqListPosFront(SeqList* ps1,SLDataType x) { assert(ps1); assert(ps1->size > 0);//使用断言assert来确保动态顺序表的有效数据个数ps1->size大于0,即确保动态顺序表非空 int begin = 1; //使用一个循环从第二个元素开始,将每个元素往前移动一位,覆盖掉前一个位置的元素。 //循环的结束条件是begin < ps1->size,即从第二个元素到最后一个元素进行移动 while (begin < ps1->size) { ps1->array[begin - 1] = ps1->array[begin]; //将当前位置begin的元素ps1->array[begin]移动到前一个位置begin-1上,即实现元素前移一位 ++begin; } ps1->size--;//将动态顺序表的有效数据个数ps1->size减1,表示删除了一个头部元素 } //顺序表查找,查找指定元素的位置 int SeqListFind(SeqList* ps1,SLDataType x) { assert(ps1); //通过一个循环遍历动态顺序表的数据数组ps1->array,从下标0到下标size-1,也就是遍历有效数据的范围 for (int i = 0; i < ps1->size; i++) { //使用if语句判断当前位置的元素ps1->array[i]是否等于要查找的元素x。如果相等,则返回当前位置i,表示找到了该元素 if (ps1->array[i] == x) { return i; } } //如果循环结束后仍未找到元素,代码会返回-1,表示未找到该元素 return -1; } //在pos位置插入x void SeqListInsert(SeqList* ps1 ,int pos,SLDataType x) { assert(ps1); assert(pos >= 0 && pos <= ps1->size); //使用断言assert来确保插入位置pos符合要求,即大于等于0且小于等于动态顺序表的有效数据个数ps1->size CheckCapacity(ps1); int end = ps1->size - 1; //使用一个循环从末尾开始,将每个元素往后移动一位,为新元素腾出插入位置。循环的结束条件是end >= pos,即从末尾元素到插入位置进行移动 while (end >= pos) { ps1->array[end + 1] = ps1->array[end]; //将当前位置end的元素ps1->array[end]移动到下一个位置end+1上,即实现元素后移一位 --end; } ps1->array[pos] = x; //将新元素x赋值给动态顺序表的插入位置ps1->array[pos] ps1->size++; //将动态顺序表的有效数据个数ps1->size加1,表示插入元素后有效数据个数增加了 } //删除pos位置的值 void SeqListErase(SeqList* ps1,int pos) { assert(ps1); assert(pos >= 0 && pos < ps1->size); //使用断言assert来确保删除位置pos符合要求,即大于等于0且小于动态顺序表的有效数据个数ps1->size int begin = pos + 1; //使用一个循环从删除位置的下一个位置开始,将每个元素往前移动一位,覆盖掉前一个位置的元素。 //循环的结束条件是begin < ps1->size,即从删除位置的下一个位置到最后一个元素进行移动 while (begin < ps1->size) { ps1->array[begin - 1] = ps1->array[begin]; //将当前位置begin的元素ps1->array[begin]移动到前一个位置begin-1上,即实现元素前移一位 ++begin; } ps1->size--;//将动态顺序表的有效数据个数ps1->size减1,表示删除了一个元素 } //顺序表的销毁 void SeqListDestory(SeqList* ps1) { assert(ps1); //使用条件判断来检查动态顺序表的数组指针ps1->array是否为空。如果不为空,则表示动态顺序表已经分配了内存 if (ps1->array != NULL) { free(ps1->array); //使用free函数来释放动态顺序表的数组内存 ps1->array = NULL; //将动态顺序表的数组指针ps1->array置为NULL,表示动态顺序表的数组指针已经不再指向有效的内存 ps1->size = 0; ps1->capacity = 0; //将动态顺序表的有效数据个数ps1->size和容量ps1->capacity都设置为0,表示动态顺序表已经被销毁,不再包含任何元素 } }
今天到此结束,个位大佬若有所收获,请“高抬贵手”留个赞,多多评论,多多收藏。
如有不足,也请各位大佬多多指点,请留言评论区。