C语言:顺序表(上)

C语言:顺序表(上)

1.顺序表的介绍
2.顺序表的实现

1.顺序表的介绍

线性表是n个具有相同特性的数据元素的有限序列。

线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以 数组链式结构 的形式存储。

顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。

顺序表分为两类:

  • 静态顺序表:使用定长数组存储元素。容易出现空间不够用、空间浪费的问题。

  • 动态顺序表:空间可以按需申请。

接下来以动态顺序表为例,编程实现顺序表。

2.顺序表的实现

首先,我们打开vs2022,创建一个头文件SeqList.h,用来定义结构体和函数声明,再创建SeqList.c来编写函数,创建test.c文件来进行测试。

实现顺序表的过程中,我们需要realloc函数来进行开辟和调整空间,用assert进行检查,所以我们在头文件SeqList.h中应包含stdio.h、stdlib.h、assert.h。

c 复制代码
//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

定义顺序表SeqList,再用typedef重命名为SL。

c 复制代码
typedef struct SeqList
{
	int* arr;
	int size;    //有效数据个数
	int capacity;//空间大小(个数)
}SL;

指针arr可以指向数组、结构体、字符串等等数据,所以要把int重命名,以便后续更改arr指向的数据。

c 复制代码
//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;//把int重命名,方便后续更改
typedef struct SeqList
{
	SLDataType* arr;
	int size;    //有效数据个数
	int capacity;//空间大小(个数)
}SL;

接下来进行函数声明,我们要编写函数实现顺序表的初始化、销毁、打印、尾插/删、头插/删、定位插入/删除、查找。

c 复制代码
//SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;//把int重命名,方便后续更改
typedef struct SeqList
{
	SLDataType* arr;
	int size;    //有效数据个数
	int capacity;//空间大小(个数)
}SL;
void SLInit(SL* ps);//顺序表初始化

void SLDestroy(SL* ps);//顺序表销毁

void SLPrint(SL s);//顺序表打印

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);//定位插入

void SLErase(SL* ps, int pos);//定位删除

int SLFind(SL* ps, SLDataType x);//查找

顺序表初始化:指针ps指向顺序表,把arr先置为NULL,有效数据的个数size为0,空间大小capacity为0。

c 复制代码
void SLInit(SL* ps)//顺序表初始化
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

顺序表销毁:需要判断顺序表是否为空,若为空则不需要销毁,若不为空则先用free释放arr指向的空间,再把size、capacity置为0。

c 复制代码
void SLDestroy(SL* ps)//顺序表销毁
{
	if (ps->arr)//若ps->arr为NULL,不再free,若不为NULL,则执行free
		free(ps->arr);
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

在创建顺序表之后,我们通过头插、尾插、定位插入的方式往顺序表中插入数据,但在插入之前,我们需要检查arr指向的空间是否足够,不够则用realloc函数增加空间。

我们先判断size与capacity是否相等,若相等,则说明空间不足,如图所示:

size与capacity不相等,则以原空间大小的2倍增加空间。

c 复制代码
void SLCheckCapacity(SL* ps)//检查插入前空间是否足够
{
	if (ps->capacity == ps->size)//空间大小和有效数据个数一致,则空间不足
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* t = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要申请多大空间
		if (t == NULL)//若空间申请失败
		{
			perror("realloc fail !");
			exit(1);//退出程序
		}
		ps->arr = t;//空间申请成功
		ps->capacity = newCapacity;//记录新的空间大小
	}
}

尾插:尾插函数需要参数指向顺序表的指针ps和插入的数据x,先用assert检查ps是否为NULL,再通过SLCheckCapacity函数检查空间是否足够,若不足则增加空间,在插入数据之后,还要让size加1,记录新的有效数据个数。

c 复制代码
void SLPushBack(SL* ps, SLDataType x)//尾插
{
	assert(ps);
	SLCheckCapacity(ps);//检查插入前空间是否足够
	ps->arr[ps->size] = x;
	ps->size++;
}

头插:头插与尾插类似,头插还需要循环把数据整体向后挪动一位。

c 复制代码
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;
	ps->size++;
}

顺序表打印函数:

c 复制代码
void SLPrint(SL s)//打印
{
	for (int i = 0;i < s.size;i++)
		printf("%d ", s.arr[i]);
	printf("\n");
}

尾删:只要size减1,让原本最后一个数据无法被访问,就实现了尾删。

c 复制代码
void SLPopBack(SL* ps)//尾删
{
	assert(ps);
	assert(ps->size);//顺序表不为空
	--ps->size;
}

头删:与尾删类似,头删还要数据整体向前挪一位,且从第二位数据开始挪,通过覆盖第一位数据实现头删。

c 复制代码
void SLPopFront(SL* ps)//头删
{
	assert(ps);
	assert(ps->size);
	for (int i = 0;i < ps->size - 1;i++)//数据整体往前挪一位
	{                                   //从第二位往前挪,覆盖第一位实现删除
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

定位插入:类似头插、尾插,但多了一个参数pos,pos为数据插入后的下标,所以0<=pos<=size,在插入前,下标为pos及大于pos的数据往后挪一位。

c 复制代码
void SLInsert(SL* ps, int pos, SLDataType x)//定位插入,插入后下标为pos
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	for (int i = ps->size;i > pos;i--)//pos及之后的数据整体往后挪一位
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

定位删除:pos为下标,显然0<=pos<size,下标大于pos的数据往前挪一位,通过arrpos+1把arrpos覆盖掉来实现定位删除。

c 复制代码
void SLErase(SL* ps, int pos)//定位删除
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos;i < ps->size-1;i++)//pos之后数据往前挪一位
	{                                   //arr[pos+1]覆盖掉arr[pos]
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

查找:SLFind函数的参数x为要查找的数据,遍历整个顺序表,找到返回下标,找不到返回-1。

c 复制代码
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;//未找到
}

在SeqList.c文件中的代码如下:

c 复制代码
//SeqList.c
#include"SeqList.h"
void SLInit(SL* ps)//顺序表初始化
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
void SLDestroy(SL* ps)//顺序表销毁
{
	if (ps->arr)//若ps->arr为NULL,不再free,若不为NULL,则执行free
		free(ps->arr);
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)//检查插入前空间是否足够
{
	if (ps->capacity == ps->size)//空间大小和有效数据个数一致,则空间不足
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* t = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要申请多大空间
		if (t == NULL)//若空间申请失败
		{
			perror("realloc fail !");
			exit(1);//退出程序
		}
		ps->arr = t;//空间申请成功
		ps->capacity = newCapacity;
	}
}
void SLPushBack(SL* ps, SLDataType x)//尾插
{
	assert(ps);
	SLCheckCapacity(ps);//检查插入前空间是否足够
	ps->arr[ps->size] = x;
	ps->size++;
}
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;
	ps->size++;
}
void SLPrint(SL s)//打印
{
	for (int i = 0;i < s.size;i++)
		printf("%d ", s.arr[i]);
	printf("\n");
}
void SLPopBack(SL* ps)//尾删
{
	assert(ps);
	assert(ps->size);//顺序表不为空
	--ps->size;
}
void SLPopFront(SL* ps)//头删
{
	assert(ps);
	assert(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)//定位插入,插入后下标为pos
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps);
	for (int i = ps->size;i > pos;i--)//pos及之后的数据整体往后挪一位
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}
void SLErase(SL* ps, int pos)//定位删除
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos;i < ps->size-1;i++)//pos之后数据往前挪一位
	{                                   //arr[pos+1]覆盖掉arr[pos]
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
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;//未找到
}

最后,我们就可以在test.c文件中做测试了,例如测试头插和尾插:

c 复制代码
#include"SeqList.h"
void test1()
{
	SL s;
	SL* ps = &s;
	SLInit(ps);
	SLPushFront(ps, 1);
	SLPushBack(ps,2);
	SLPushBack(ps, 3);
	SLPushBack(ps, 4);
	SLPrint(s);
	SLDestroy(ps);
}
int main()
{
	test1();
	return 0;
}

拙作一篇,望诸位同道不吝斧正。

相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨8 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜10 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa10 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio