目录
一.顺序表的定义
顺序表的概念:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。

数组和顺序表的区别:
数组(Array)一段连续的内存空间,用来存储相同类型的数据。只负责存放数据,不自带长度管理、插入、删除、扩容等操作。大小在定义时基本固定(静态数组),越界直接出错。
顺序表(Sequential List)逻辑上相邻,物理上也相邻的线性表。绝大多数语言实现的顺序表底层就是数组,并且额外封装了:当前元素个数、最大容量、插入、删除、查找、扩容等完整操作。
顺序表分为静态顺序表和动态顺序表
静态顺序表(使用定长数组存储元素)的表现形式如下:
typedef int SLDataType;
#define N 7
typedef struct SeqList{
SLDataType a[N];//定长数组
int size;//有效长度
}SL;
typedef的作用是什么?是取别名,起到一键修改的作用。方便我们后续在使用顺序表时对存储的数据类型做更改,比如后续我们不想存储int类型数据了,就可以很方便的在这里对数组类型做更改.比如改成char类型,或者double类型,甚至改成任意自己构造的结构类型。
有人说,不用typedef也可以啊,我用CTRL+F不也可以一键修改吗?原因在于:如果我们想要将char型修改为int型,ctrl+f是修改所以的相关类型,比如我们有:char a='ch';这句代码,用ctrl+f,修改为int型,这是不合法的,所以为了防止这种情况出现,我们可以在想要修改的变量前加上typedef即可。
静态顺序表的缺陷也很明显,空间给少了不够用,给多了造成浪费。所以一般情况下顺序表我们都采用动态顺序表
那么,什么是动态顺序表呢?它的表现形式如下:
typedef struct SeqList
{
SLDatatType*a;
int size;//有效数据个数
int capacity;//空间容量
}SL;
既然是动态顺序表,那么它的内存肯定就是动态内存管理的了。
二.顺序表的初始化
typedef int SLDataType;//将结构体数组重命名,方便后面修改线性表的成员
typedef struct SeqList //对结构体重命名为SL
{
SLDataType *a; //
int size; //有效数据个数
int capacity; //空间容量
}SL;
//顺序表的初始化
void SLInit(SL*ps)
{
assert(ps);
int newcapacity = ps->capacity = 0 ? 4 : 2*capacity;//初始化capacity
ps->arr =(SLDataType*)malloc(sizeof(SLDataType)* newcapacity); //开辟空间
if (ps->arr == NULL)
{
perror("malloc fail"); //如果开辟失败,打印错误信息
return;
}
ps->size = 0; //将顺序表初始元素设为0
ps->capacity = capacity; //将顺序表初始容量设为INIT_CAPACITY
}
需要注意的是:
对于动态内存开辟的空间,一定要进行检验是否空间成功开辟,因为如果未开辟成功,返回的是空指针,相当于没有内存存放创建的变量,那么我们就无法访问,如果试图访问,会导致程序崩溃。如果申请内存成功,但是忘记释放内存,那么会导致内存泄漏。
顺序表中要求ps不能为空,是因为一旦ps为空,那么传入的指针一定是一个非法的空指针,因为ps为空,不仅代表arr为NULL,还代表size,capacity也都为NULL.这意味着整个顺序表都不存在。
三.顺序表的扩容
顺序表扩容四步骤:
1.操作之前先使用assert检查一下ps是否为空指针.
2.判断size与capacity的关系,当顺序表的元素个数一旦等于顺序表的容量时,使用realloc()函数进行扩容.(根据概率论,每次扩容的容量是扩容前的2倍时比较合理.)
3.使用realloc()函数和malloc()函数一样,当遇到未开辟成功的情况时需要抛出错误信息.
4.最后记得扩容后要给capacity的值也乘2,和空间真实容量保持一致.
代码如下:
//顺序表扩容
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * ps->capacity * 2);//扩容二倍
if (tmp == NULL)
{
perror("realloc fail!");
return;
}
ps->arr = tmp;
ps->capacity *= 2;
}
}
四.顺序表的元素插入
顺序表每次进行元素插入时,我们都要注意是否要扩容。
顺序表元素的插入有三种方式:分别是尾插,头插和任意指定位置插入。
1.尾插
尾插:将元素插入到顺序表的最后一个位置,只需要在数组的末尾添加元素即可。该算法的时间复杂度为O(1).
代码实现如下:
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps); //首先要检查容量,如果不够要增容
ps->arr[ps->size] = x; //最后将数据赋值给arr数组中的最后一个元素
++ps->size;
}
2.头插
头插:将元素插入到顺序表的第一个位置,需要将原有的所有元素都向后移动一位。该算法的时间复杂度为O(n).
头插代码如下:
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for(int i = ps->size;i > 0;i--)
{
ps->arr[i] = ps->arr[i-1];
}
ps->arr[0] = x;
}

3.任意位置插入
任意指定位置插入元素:将元素插入到顺序表中的任意位置,需要将插入位置后的所有元素都向后移动一位。该算法的时间复杂度为O(n).
任意指定位置插入的逻辑和头插差不多,只不过头插是把所有位置的元素都向后挪动,使头空出来,好插入元素.而指定位置插入是将指定位置后的所有元素向后挪动一位,使指定位置空出来,好在指定位置插入元素.
当然,在挪动和插入操作前,我们还是要先检查一下顺序表当前容量是否满了以及检查待插入位置pos是否合法(pos<0或pos>ps->size都是越界访问).
代码如下:
//有定点删除之后就可以不要头插尾插了,直接传参0或者size就行
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
//定点插入,插入点必须在0~size之间(判断).
assert(pos >= 0 && pos <= ps->size);
//pos<0或pos>ps->size都是越界访问
//pos==0,相当于头插,pos==ps->size,相当于尾插
SLCheckCapacity(ps);
//pos及之后的数据整体向后挪动一位
for(int i = ps->size;i > pos;i--)
{
ps->arr[i] = ps->arr[i-1];
}
ps->arr[pos] = x;
--ps->size;
}
有了任意指定位置插入函数后我们容易发现,当要求在pos=0的位置插入元素时,相当于顺序表的头插,当要求在pos=size的位置插入元素时,相当于顺序表的尾插.
因此,任意指定位置插入函数可以实现头插和尾插的功能.

五.顺序表的元素删除
顺便表删除元素需要检查顺序表是否为空表。
顺序表元素的删除同样有三种方式:分别是尾删,头删和任意指定位置删除.
1.尾删
尾删元素:将顺序表的最后一个位置的元素删除,只需要将顺序表的元素个数size-1即可。可以不需要将该位置的数据置为0,因为给size-1实际上是拿走了这块位置的访问权限,没有访问权限时该位置的数据是什么都没有意义.该算法的时间复杂度为O(1)。
代码如下:
void SLPopBack(SL* ps)
{
//先判断,如果size已经为0了,就不要再删了
if (ps->size == 0)
return;
--ps->size;
//这里没必要把数据置为0,因为size减少后原位置的数据就无法访问了
//其次是这里没法把删除的空间free掉,动态内存申请的空间没办法单独释放某块内存的空间,要申请一起申请,要释放只能从头一起释放.
}
注意:动态内存申请的空间没办法单独释放某块内存的空间,要申请一起申请,要释放只能从头一起释放。

2.头删
头删元素:将顺序表的第一个位置的元素删除掉,需要将除了首元素之外的所有元素都向前移动一位,这样刚好原来第一位元素的数据就会被覆盖,即第一个元素被"删除"了。该算法的时间复杂度为O(n)。
代码如下:
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
for(int i = 0;i < size-1;i++)
{
ps->arr[i] = ps->arr[i+1];
}
--ps->size;
}

3.任意位置删除
任意指定位置删除元素:将顺序表的任意位置的元素删除,需要将删除位置后的所有元素都向前移动一位。该算法的时间复杂度为O(n).
任意指定位置删除的逻辑和头删差不多,只不过头删是把所有第一个元素后的元素都向前挪动,使头位置元素被覆盖.而指定位置删除是将指定位置后的所有元素向前挪动一位,使指定位置被覆盖,以达到删除该元素的效果.
当然,在挪动删除操作前,我们还是照例要先检查一下顺序表当前是不是空表,以及检查待删除位置pos是否合法(pos<0或pos>=ps->size都是越界访问).
代码如下:
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);//pos<0或pos>=ps->size都是越界访问
for(int i = pos;i < ps->size - 1;i++)
{
ps->arr[i] = ps->arr[i+1];
}
--ps->size;
}
有了任意指定位置删除函数后我们发现,当要求在pos=0的位置删除元素时,相当于顺序表的头删,当我们要求在pos=size-1的位置删除元素时,相当于顺序表的尾删。

六.顺序表元素查找
顺序表元素的查找类似于数组的元素查找,都是在合法数组范围内遍历整个顺序表的元素.
如果在遍历的过程中找到了要查找的元素,就返回该元素的下标,如果遍历完还没找到该元素,则意味着该元素不在顺序表中,因此返回-1.
代码如下:
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i; //找到了,返回元素下标
}
}
return -1; //未找到,返回-1
}
七.顺序表的打印
顺序表的打印逻辑和查找一样,使用循环遍历打印元素.
代码如下:
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
八.顺序表的销毁
当我使用完顺序表想要退出程序时,应该将之前动态开辟的内存释放掉,还给操作系统.即销毁顺序表操作.
使用free()函数释放掉之前动态开辟的数组arr,将arr置为空指针,最后将size,capacity的值置为0即可.
代码如下:
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->arr);//释放动态内存
ps->arr = NULL;//将arr置为空指针
ps->capacity = ps->capacity = 0;
}
九.完整代码如下:
SeqList.c:
#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//增容
//realloc第二个参数,单位是字节
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
//判断空间是否足够
SLCheckCapacity(ps);
//空间足够的情况下
ps->arr[ps->size++] = x;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
//温柔的处理方式
//if (ps == NULL)
//{
// return;
//}
assert(ps != NULL);
//判断空间是否足够
SLCheckCapacity(ps);
//将顺序表中所有数据向后挪动一位
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
++ps->size;
}
//尾删
void SLPopBack(SL* ps)
{
//ps:限制参数不能直接给NULL
//ps->size:顺序表为空
assert(ps && ps->size);
--ps->size;
}
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//头删
void SLPopFront(SL* ps)
{
assert(ps && ps->size);
for (int i = 0; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
//指定位置之前插⼊数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
//pos及之后的数据整体向后挪动一位
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
++ps->size;
}
// 删除POS位置的数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
//pos之后的数据整体向前挪动一位
for (int i = pos; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
//查找
int SLFind(SL* ps, SLDataType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
//找到了
return i;
}
}
//未找到
return -1;
}
//销毁
void SLDestroy(SL* ps)
{
assert(ps);
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
SeqList.h:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义动态顺序表的结构
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;
int size; //有效数据个数
int capacity; //空间容量
}SL;
//typedef struct SeqList SL;
void SLPrint(SL* ps);
//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//尾插
void SLPushBack(SL* ps, SLDataType x);
//头插
void SLPushFront(SL* ps, SLDataType x);
//尾删
void SLPopBack(SL* ps);
//头删
void SLPopFront(SL* ps);
//指定位置之前插⼊数据
void SLInsert(SL* ps, int pos, SLDataType x);
// 删除POS位置的数据
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps, SLDataType x);
test.c:
#include"SeqList.h"
void test01()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPrint(&sl);
//SLPushBack(&sl, 5);
//SLPushFront(&sl, 1);
//SLPushFront(&sl, 2);
//SLPushFront(&sl, 3);
//SLPushFront(&sl, 4);//4 3 2 1
//SLPushFront(NULL, 1);
//
//SLPopBack(&sl);
//SLPrint(&sl);
//SLPopBack(&sl);
//SLPrint(&sl);
//SLPopBack(&sl);
//SLPrint(&sl);
//SLPopBack(&sl);
//SLPrint(&sl);
//SLPopBack(&sl);
//
//SLPopFront(&sl);
//SLPrint(&sl);
//SLPopFront(&sl);
//SLPrint(&sl);
//SLPopFront(&sl);
//SLPrint(&sl);
//SLPopFront(&sl);
//SLPrint(&sl);
//SLPopFront(&sl);
//SLPrint(&sl);
//
//SLInsert(&sl, 4, 100);
//SLPrint(&sl);
//SLErase(&sl, 3);
//SLPrint(&sl);
//int find = SLFind(&sl, 22222);
//if (find != -1)
//{
// printf("ҵˣ\n");
//}
//else {
// printf("δҵ\n");
//}
SLDestroy(&sl);
}
int main()
{
test01();
return 0;
}


被折叠的 条评论
为什么被折叠?



