C语言实现顺序表

顺序表定义

顺序表是一种常见的数据结构,用于存储一组具有相同数据类型的元素,并按照线性顺序排列。在顺序表中,每个元素都有一个唯一的索引值(即我们通常所说的下标),用于标识其在表中的位置。这个索引值通常从0开始,依次递增。

顺序表有两种实现方式:静态顺序表和动态顺序表。

静态顺序表的适用场景:明确知道需要储存数据的个数。

动态顺序表的适用场景:一般情况下都适用。

PS:本文主要讲动态顺序表的实现!!!

静态顺序表:

使用定长数组存储元素

动态顺序表: 

使用动态开辟的数组存储。

顺序表的特点:

随机访问:由于顺序表使用数组实现,通过索引值可以快速访问任意位置的元素。
顺序存储:元素在物理空间上是连续存储的,因此可以直接通过内存地址计算得到元素位置,无需额外的指针来链接元素。
固定或可动态调整大小:静态顺序表在创建时大小固定,而动态顺序表可以根据需要进行动态扩容或缩容。
线性结构:顺序表的元素之间存在一对一的前后关系,形成线性结构。


源码

废话不多说,先贴出代码。顺序表我是用一个头文件SeqList.h和一个.cpp文件SeqList.cpp实现的。

SeqList.h

//*********************************
// 作者:suwu
// 环境: vs2019 win10
// 最后修改时间:2023/7/23 15:51
//*********************************
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int SLDateType;//顺序表里一个存储单元的数据类型,如需修改,只需在此把int修改为其他数据类型

// 自定义顺序表的数据结构
typedef struct SeqList
{
	SLDateType* a;//顺序表数据
	int size;//现有存储数据个数
	int capacity;//顺序表容量
}SeqList;

// 对数据的管理:增删查改 

// 函数名称:SeqListInit
// 函数功能:初始化顺序表,为顺序表申请空间,顺序表容量初始化为3个。
// 函数参数:顺序表指针ps
// 返回类型及内容:空
void SeqListInit(SeqList* ps);//初始化

// 函数名称:SeqListDestroy
// 函数功能:销毁顺序表,释放顺序表申请过的空间,并把指针置空。
// 函数参数:顺序表指针ps
// 返回类型及内容:空
void SeqListDestroy(SeqList* ps);//销毁

// 函数名称:SeqListPrint
// 函数功能:按顺序表里数据排放顺序打印数据。
// 函数参数:顺序表指针ps
// 返回类型及内容:空
void SeqListPrint(SeqList* ps);//打印

// 顺序表查找
// 函数名称:SeqListFind
// 函数功能:查找顺序表里面的数据,和x相同的第一个数据的下标将被返回。
// 函数参数:顺序表指针ps,比对数据x
// 返回类型及内容:和x相同的第一个数据的下标,如果没找到或者顺序表指针ps为空则返回-1
int SeqListFind1(SeqList* ps, SLDateType x);

// 函数名称:SeqListFind
// 函数功能:从begin下标位置开始查找顺序表里面的数据,和x相同的第一个数据的下标将被返回。
//			 配合while和Erase可以把所有与x值相同的数据删除
// 函数参数:顺序表指针ps,比对数据x,开始查找下标begin
// 返回类型及内容:和x相同的第一个数据的下标,如果没找到或者顺序表指针ps为空则返回-1
int SeqListFind2(SeqList* ps, SLDateType x, int begin);

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);




void SeqListPushBack(SeqList* ps, SLDateType x);//尾插,笔试可用Insert函数实现。
void SeqListPushFront(SeqList* ps, SLDateType x);//头插,笔试可用Insert函数实现。
void SeqListPopFront(SeqList* ps);//头删,笔试可用Erase函数实现。
void SeqListPopBack(SeqList* ps);//尾删,笔试可用Erase函数实现。

SeqList.cpp

//*********************************
// 作者:suwu
// 环境: vs2019 win10
// 最后修改时间:2023/7/23 15:51
//*********************************
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"

void SeqListBoost(SeqList* ps)//扩容
{
	if (ps == NULL)
	{
		return;
	}
	int newcapacity = (ps->capacity == 0) ? 4 : 2 * ps->capacity;
	SLDateType* arr = (SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType));
	if(arr != NULL)
	{
		ps->a = arr;
		ps->capacity = newcapacity;
	}
	else
	{
		perror("SeqListBoost()");
	}
}

void SeqListInit(SeqList* ps)//初始化
{
	if (ps == NULL)
	{
		perror("SeqListInit()");
	}
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

void SeqListDestroy(SeqList* ps)//空间释放
{
	if (ps == NULL)
	{
		perror("SeqListDestroy()");
	}
	if (ps->a)
	{
		free(ps->a);
		ps->a = NULL;
	}
	ps->size = 0;
	ps->capacity = 0;
}

void SeqListPrint(SeqList* ps)//遍历打印
{
	if (ps == NULL)
	{
		return;
	}
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

int SeqListFind1(SeqList* ps, SLDateType x)//查找函数
{
	if (ps == NULL)
	{
		return -1;
	}
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

int SeqListFind2(SeqList* ps, SLDateType x, int begin)
{
	if (ps == NULL)
	{
		return -1;
	}
	for (int i = begin; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

void SeqListInsert(SeqList* ps, int pos, SLDateType x)//任意位置插入
{
	if (ps == NULL)
	{
		perror("SeqListInsert");
	}
	if (pos < 0 || pos > ps->size)//pos下标位置必须合理
	{
		return;
	}
	if (ps->size + 1 > ps->capacity)//空间不够就扩容
	{
		SeqListBoost(ps);
	}
	int i = 0;
	for (i = ps->size; i > pos; i--)//从后往前,挪位置,腾出空间插入
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;//pos下标位置处插入x
	ps->size++;//现有数据个数+1
}

void SeqListErase(SeqList* ps, int pos)//指定位置删除
{
	if (ps == NULL)
	{
		return;
	}
	if (pos < 0 || pos >= ps->size)//pos位置必须合理
	{
		return;
	}
	int i = 0;
	for (i = pos; i < ps->size - 1; i++)//从前往后,挪位置,覆盖掉删除的数据
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

void SeqListPushBack(SeqList* ps, SLDateType x)//尾插,笔试可用Insert函数实现。
{
	SeqListInsert(ps, ps->size, x);
}

void SeqListPushFront(SeqList* ps, SLDateType x)//头插,笔试可用Insert函数实现。
{
	SeqListInsert(ps, 0, x);
}

void SeqListPopFront(SeqList* ps)//头删,笔试可用Erase函数实现。
{
	SeqListErase(ps, 0);
}

void SeqListPopBack(SeqList* ps)//尾删,笔试可用Erase函数实现。
{
	SeqListErase(ps, ps->size - 1);
}

代码说明

接下来是对源码的一些细节的解释说明,方便初学者看明白,学过的可自行看源码中的注释。

SeqList.h

这个头文件是一些结构定义和函数声明。

typedef int SLDateType;//顺序表里一个存储单元的数据类型,如需修改,只需在此把int修改为其他数据类型

这个typedef的话,给原有的/自定义的数据类型另起别名具体格式是如下:

typedef  原有的/自定义的数据类型   别名

这句语句是非常必要的,特别是以后顺序表里存储的数据不是int类型,而是其他的什么double,char,或者是自定义结构体类型等。那么你整个程序很多处地方都需要修改!但你加了这一句语句你只需要把中间的int改成你想要就可以了。适用性和便捷性会提高不少。 

// 自定义顺序表的数据结构
typedef struct SeqList
{
	SLDateType* a;//顺序表数据
	int size;//现有存储数据个数
	int capacity;//顺序表容量
}SeqList;

这段代码首先定义了顺序表的结构,

数据类型为SLDateType*的储存数据的动态数组a,以及现存数据个数顺序表容量。

其次,用 typedefstruct SeqList 另起别名为 SeqList。

接下来就是一些函数声明,某个函数不太懂可以去看注释。

SeqList.cpp

这个文件里面主要是一些函数实现。

#include "SeqList.h"

首先这句声明是非常必须的,连接好函数声明和函数实现。

SeqListBoost扩容

这个函数的作用是扩充顺序表的容量。

void SeqListBoost(SeqList* ps)//扩容
{
	if (ps == NULL)
	{
		return;
	}
	int newcapacity = (ps->capacity == 0) ? 4 : 2 * ps->capacity;
	SLDateType* arr = (SLDateType*)realloc(ps->a, newcapacity * sizeof(SLDateType));
	if(arr != NULL)
	{
		ps->a = arr;
		ps->capacity = newcapacity;
	}
	else
	{
		perror("SeqListBoost()");
	}
}

realloc是一个扩容函数,不懂可自行搜索。

realloc有两种扩容方式,一种是原地扩容,一种是异地扩容。区别在于重新分配后的内存首地址是否改变

这句语句在此的大概作用是把ps->a所拥有的空间扩大两倍,并把分配的数组首地址赋值给arr。

如果arr为空则说明扩容失败,

不为空,则扩容成功,可把arr重新赋值给ps->a,再把容量*2即可。

这里需要注意的是不可以把realloc后的地址赋值自身。

即不可以写成这样!!!!

ps->a = (SLDateType*)realloc(ps->a, 2 * ps->capacity* sizeof(SLDateType))

这样的话你无法得知是否扩容成功。realloc函数扩容失败会返回空指针。 

初始化和空间释放

void SeqListInit(SeqList* ps)//初始化
{
	if (ps == NULL)
	{
		perror("SeqListInit()");
	}
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

SeqListInit这个函数是初始化函数,如果传入顺序表指针为空则报错。

初始化时申请空间个数可以少一点,后续可以扩容,提高空间利用率。

把顺序表结构每个数据都初始化好就行。

void SeqListDestroy(SeqList* ps)//空间释放
{
	if (ps == NULL)
	{
		perror("SeqListDestroy()");
	}
	if (ps->a)
	{
		free(ps->a);
		ps->a = NULL;
	}
	ps->size = 0;
	ps->capacity = 0;
}

SeqListDestroy这个函数释放空间的函数,整个程序将要执行完可以执行这个函数,避免数据泄露。

释放申请空间后,记得把指针置空。

遍历打印

void SeqListPrint(SeqList* ps)//遍历打印
{
	if (ps == NULL)
	{
		return;
	}
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

SeqListPrint这个函数我想应该没什么好说的,就是把顺序表现有的数据打印一遍。

注意考虑到 接受的函数实参不能是空指针的情况 就可以了。

数据查找

int SeqListFind1(SeqList* ps, SLDateType x)//查找函数
{
	if (ps == NULL)
	{
		return -1;
	}
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

//配合while和Erase能把所有和x相同的数据都删除了
int SeqListFind2(SeqList* ps, SLDateType x, int begin)
{
	if (ps == NULL)
	{
		return -1;
	}
	for (int i = begin; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

SeqListFind1这个函数的话就找跟x相同的第一个数据并返回索引值(下标)。

顺序表指针为空或者找不到就返回-1.

SeqListFind2这个函数是一个在Find1上扩展的函数,旨在配合while找到所有与x相同的数据的下标。

数据插入

任意位置插入 Insert
void SeqListInsert(SeqList* ps, int pos, SLDateType x)//任意位置插入
{
	if (ps == NULL)
	{
		perror("SeqListInsert");
	}
	if (pos < 0 || pos > ps->size)//pos下标位置必须合理
	{
		return;
	}
	if (ps->size + 1 > ps->capacity)//空间不够就扩容
	{
		SeqListBoost(ps);
	}
	int i = 0;
	for (i = ps->size; i > pos; i--)//从后往前,挪位置,腾出空间插入
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;//pos下标位置处插入x
	ps->size++;//现有数据个数+1
}

SeqListInsert这个函数就是任意位置插入,这里需要注意的是pos下标位置必须合理。

因为后续头插尾插要用到Insert这个函数,所以得注意pos的范围不能小于0也不能大于ps->size。

这里注意挪位置那个循环插入需要把数据都往后挪,空出一个位置方便插入

头插

在顺序表最前面插入。可用Insert实现。

void SeqListPushFront(SeqList* ps, SLDateType x)//头插,笔试可用Insert函数实现。
{
	SeqListInsert(ps, 0, x);
}
尾插

在顺序表最后面插入。可用Insert实现。

void SeqListPushBack(SeqList* ps, SLDateType x)//尾插,笔试可用Insert函数实现。
{
	SeqListInsert(ps, ps->size, x);
}

数据删除

指定位置删除 Erase
void SeqListErase(SeqList* ps, int pos)//指定位置删除
{
	if (ps == NULL)
	{
		return;
	}
	if (pos < 0 || pos >= ps->size)//pos位置必须合理
	{
		return;
	}
	int i = 0;
	for (i = pos; i < ps->size - 1; i++)//从前往后,挪位置,覆盖掉删除的数据
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

pos这个函数参数接受的是要删除数据的索引值(下标),ps接受的是顺序表的指针

如果pos大于或等于ps->size或者pos小于0,也就是说要删除的位置比现有数据量个数大,不用对数据做任何操作。

如果小于,则做覆盖操作。

头删

在顺序表最前面删除一个数据。可用Erase实现。

void SeqListPopFront(SeqList* ps)//头删,笔试可用Erase函数实现。
{
	SeqListErase(ps, 0);
}
尾删

在顺序表最后面删除一个数据。可用Erase实现。

void SeqListPopBack(SeqList* ps)//尾删,笔试可用Erase函数实现。
{
	SeqListErase(ps, ps->size - 1);
}

最后 

写作不易,点点赞、关注和收藏。有任何疑问请在评论提出!一起进步!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值