顺序表和链表

前言:在讲解顺序表和链表之前,我们先引入一个概念,线性表,其是具有相同特性的一类数据结构的统称。这里的相同特性指的是在逻辑结构上一定是线性的,但是在物理结构上不一定是线性的。当然,这里的顺序表和链表都是属于线性表的,但是两者相似却又不同,相似点在于它们在逻辑结构上都是线性的,不同点在于顺序表在物理结构上是线性,链表在物理结构上是非线性的。那么对于顺序表和链表我们有了一个大致了解,接下来让我带领大家深入了解其中的细节吧!

1.顺序表

1.1顺序表的概念和结构

顺序表是用一段物理地址连续的存储空间依次存储数据元素的线性结构,其只能从头开始连续存储。

顺序表的底层结构是数组,所以顺序表在逻辑结构上是线性的,在物理结构上也是线性的。此外这里的逻辑结构其实是一种想象,将顺序表的结构想象成一条线一样,每个元素依次紧密相连。这里的物理结构就是指数据元素存储在物理地址连续的存储空间中。

1.2顺序表的分类

顺序表分为两种,静态顺序表和动态顺序表

静态顺序表顾名思义,顺序表的大小是不变,一般采用定长数组其结构为:

//静态顺序表(一般用定长数组)
struct SeqList{
    int a[10];
    int size;//有效数据的个数
};

这种顺序表是有一定的不足和缺点的,主要表现在:如果我定义这个顺序表的时候,空间给小了,那就不够用了,在实际场景当中,如果数据因为空间不足而导致数据无法有效保存,就会造成数据丢失,可能就会造成巨大的损失,其次如果我一次性给太多的空间,也会造成空间的浪费。

所以我们也引入了动态顺序表,其有效的解决了上述的不足。

动态顺序表的结构为:

//动态顺序表
struct SeqList{
    int *a;
    int size;//有效数据的个数
    int capacity;//空间大小
};

1.3动态顺序表的实现

在实现动态顺表之前,我们首先要定义三个文件分别是SeList.h(包含顺序表结构定义,头文件的声明,顺序表一些接口函数的声明)SeList.c(主要包含接口函数的定义)    test.c(主要用来测试我们的接口函数)

首先我们先要定义一下这个顺序表

typedef int SLDataType;
typedef struct SeList
{
	SLDataType* a;
	int size;//有效数据的个数
	int capacity;//顺序表的空间大小
}SL;

将int类型取别名为SLDataType,这样方便我们使用顺序表存储其他类型的数据,这时我们就不用将每个类型都要修改。

a实质上是一个数组,用来存储SLDataType类型的数据

size表示顺序表中实际存储有效数据的个数

capacity表示顺序表的容量,能够存储数据的个数

最后我们将struct SeList这个结构类型取别名为SL,方便我们实现和检测结构函数时的使用。

在我们去实现一些接口函数之前我们首先要初始化这个顺序表

#define INIT_CAPACITY 4
//实现初始化
void SLInit(SL* ps) {
	assert(ps);//(防止ps为NULL)
	ps->capacity = INIT_CAPACITY;
	ps->a = (SLDataType*)malloc(INIT_CAPACITY * sizeof(SLDataType));
    //向内存中申请4个SLDataType的大小的空间,并将该空间首地址赋值给ps->a
	if (ps->a == NULL)
	{
		perror("calloc");
		return 1;//(如果开辟失败malloc返回NULL,则退出该函数)
	}
	ps->size = 0;
    //由于此时还顺序表中还未存储数据,所以有效数据个数为0
}

首先我们先要明确的一点是顺序表本质是一个结构体,我们要初始化这个结构体,就是要修改结构体,所以传参我们应该要传结构体的指针,从而初始化该顺序表。

首先我们先实现动态顺序表的尾插(SLPushBack)

void SLCheckCapacity(SL* ps) {
	if (ps->capacity == ps->size)
	{
		SLDataType* tmp = (SLDataType*)realloc(ps->a, 2 * INIT_CAPACITY * sizeof(SLDataType));
    //如果顺序表容量大小等于有效数据的个数,就表明顺序表已满,如果要再添加数据需要再次扩容
		if (tmp==NULL)
		{
			perror("realloc");
			return 1;
		}
		ps->a = tmp;//将开辟好空间的首地址赋值给ps->a
		tmp = NULL;
		ps->capacity = 2 * INIT_CAPACITY ;//容量也要根据扩容的大小改变
	}
}
//实现头部插入删除/ 尾部插入删除
//尾插
void SLPushBack(SL* ps, SLDataType x) {
	assert(ps);
    //再插入数据之前我们首先要判断一下顺序表的容量能否可以再次插入数据,如果空间大小足够,那我们就直 
    //接尾插,如果空间不够我们还要根据实际情况的需求来扩容。
	SLCheckCapacity(ps);
	ps->a[ps->size] = x;//这里顺序表有效数据的个数作为数据下标指向的是最后一个有效数据的下一个位置
	ps->size++;//每插入一个数据,有效数据个数加1
}

为了验证我们是尾插成功,我们还需要在定义一个接口函数打印函数,将顺序表中存储的数据打印出来从而来验证。

void SLPrint(SL* ps) {
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

动态顺序表的尾删(SLPopBack)

//判断顺序表是否为空
bool SqIsEmpty(SL* ps)
{
	assert(ps);
	return ps->size == 0;
}
//尾删
void SLPopBack(SL* ps) {
	assert(ps);
	assert(!SqIsEmpty(ps));//在尾删之前我们还要检测一下顺序表是否为空,如果为空,则不能尾删,所以
    //在这设置一个断言,如果顺序表为空,则终止程序
	ps->size--;//因为尾删的数据可能也要被修改,所以这里尾删只需要将有效数据减一即可,无需再将数据置
    //0或其他数据
}

动态顺序表的头插:

void SLPushFront(SL* ps, SLDataType x) {
	assert(ps);
	SLCheckCapacity(ps);//同样要进行空间大小的检查,空间不够先扩容
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
    //头插我们需要将头的位置给腾出来,来插入新的数据,这样就要求原顺序表中的数据依次向后面移向下一个
    //空间,这里需要注意的是我们应该从尾到头依次将数据移向其位置对应的下一个位置
	ps->a[0] = x;//将数据插入头部
	ps->size++;//有效数据加一
}

动态顺序表的头删:

void SLPopFront(SL* ps) {
	assert(ps);
	assert(!SqIsEmpty(ps));//判空
	for (int i = 0; i <ps->size-1 ; i++)
	{
		ps->a[i] = ps ->a[i + 1];
	}
    //将头部位置的下一个数据依次向前覆盖,直到将最后一个数据移向其前一个位置结束为止
	ps->size--;//删除后顺序表的有效个数-1
}

动态顺序表在指定位置之前插入/删除

//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x) {
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > pos; i--) {
		ps->a[i] = ps->a[i - 1];
	}
    //需要将指定位置及其之后的数据元素都要移向其对应的下一个位置,这里要注意需要先从最后一个元素开始
    //直到将pos位置元素移动后结束
	ps->a[pos] = x;//将pos位置赋值为x
	ps->size++;//顺序表的有效个数+1
}
void SLErase(SL* ps, int pos) {
	assert(ps);
	assert(!SqIsEmpty(ps));
	for (int i = pos; i < ps->size-1; i++) {
		ps->a[i] = ps->a[i + 1];
	}
    //这里需要将pos位置之后数据元素向前面覆盖,从pos下一个位置开始,到将最后一个数据元素移动后为止
	ps->size--;//有效数据个数-1
}

同时为了方便找到对应数据的位置,又定义了一个接口函数,来寻找数据在数组中的下标。

int SLFind(SL* ps, SLDataType x) {
	assert(ps);
	for (int i = 0; i < ps->size; i++) {
		if (ps->a[i] == x) {
			return i;
		}
        //遍历数组,如果找到所对应的数据元素,就返回其数组下标
        //找不到就返回0
	}
	return 0;
}

在使用顺序表之后,我们需要销毁这个顺序表

动态顺序表的销毁:

void SLDestroy(SL* ps) {
	assert(ps);
	ps->capacity = ps->size = 0;//将容量和有效数据个数赋值为0
	free(ps->a);//释放动态开辟的空间
	ps->a = NULL;//将ps->a置空避免野指针
}

总的结构主要包括;

//SeList.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
#define INT_CAPACITY 4

typedef int SLDataType;
typedef struct SeList
{
	SLDataType* a;
	int size;//有效数据的个数
	int capacity;//顺序表的空间大小
}SL;

//初始化,销毁和打印数据
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
//检查顺序表容量并扩容
void SLCheckCapacity(SL* ps);

//头部插入删除/ 尾部插入删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);

//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType x);
//SeList.c
#include"SeList.h"
#define INIT_CAPACITY 4
//实现初始化,销毁和打印数据
void SLInit(SL* ps) {
	assert(ps);
	ps->capacity = INIT_CAPACITY;
	ps->a = (SLDataType*)malloc(INIT_CAPACITY * sizeof(SLDataType));
	if (ps->a == NULL)
	{
		perror("calloc");
		return 1;
	}
	ps->size = 0;
}
void SLDestroy(SL* ps) {
	assert(ps);
	ps->capacity = ps->size = 0;
	free(ps->a);
	ps->a = NULL;
}
void SLPrint(SL* ps) {
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
void SLCheckCapacity(SL* ps) {
	if (ps->capacity == ps->size)
	{
		SLDataType* tmp = (SLDataType*)realloc(ps->a, 2 * INIT_CAPACITY * sizeof(SLDataType));
		if (tmp==NULL)
		{
			perror("realloc");
			return 1;
		}
		ps->a = tmp;
		tmp = NULL;
		ps->capacity = 2 * INIT_CAPACITY ;
	}
}
//实现头部插入删除/ 尾部插入删除
//尾插
void SLPushBack(SL* ps, SLDataType x) {
	assert(ps);
	SLCheckCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;
}
//判断顺序表是否为空
bool SqIsEmpty(SL* ps)
{
	assert(ps);
	return ps->size == 0;
}
//尾删
void SLPopBack(SL* ps) {
	assert(ps);
	assert(!SqIsEmpty(ps));
	ps->size--;
}
//头插
void SLPushFront(SL* ps, SLDataType x) {
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;
	ps->size++;
}
//头删
void SLPopFront(SL* ps) {
	assert(ps);
	assert(!SqIsEmpty(ps));
	for (int i = 0; i <ps->size-1 ; i++)
	{
		ps->a[i] = ps ->a[i + 1];
	}
	ps->size--;
}
//指定位置之前插入/删除数据
void SLInsert(SL* ps, int pos, SLDataType x) {
	assert(ps);
	SLCheckCapacity(ps);
	for (int i = ps->size; i > pos; i--) {
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;
}
void SLErase(SL* ps, int pos) {
	assert(ps);
	assert(!SqIsEmpty(ps));
	for (int i = pos; i < ps->size-1; i++) {
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}
int SLFind(SL* ps, SLDataType x) {
	assert(ps);
	for (int i = 0; i < ps->size; i++) {
		if (ps->a[i] == x) {
			return i;
		}
	}
	return 0;
}
#include"SeList.h"
void Seqtest03() {
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPrint(&sl);
	SLInsert(&sl, 2, 8);
	SLErase(&sl, 2);
	SLPrint(&sl);
}
void Seqtest02() {
	SL sl;
	SLInit(&sl);
	SLPushFront(&sl, 4);
	SLPushFront(&sl, 3);
	SLPushFront(&sl, 2);
	SLPushFront(&sl, 1);
	SLPrint(&sl);
	SLPopFront(&sl);
	SLPrint(&sl);
}
void Seqtest01() {
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLPrint(&sl);
	SLPopBack(&sl); 
	SLPrint(&sl);
}
int main()
{
	Seqtest02();
	return 0;
}

今天的介绍就到此结束了,随后链表的介绍我放在下一期,希望我的介绍能够对你的学习有所帮助,觉得不错的话帮博主点个赞吧,十分感谢,你们的点赞是我继续努力的动力,你们也辛苦了,又努力的学习了一天,如果我有讲的的错误的地方希望你们能指出来,我们共同进步,拜拜,下期见

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值