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的数据往前挪一位,通过arr[pos+1]把arr[pos]覆盖掉来实现定位删除。

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;
}

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

相关推荐
晨非辰13 分钟前
#C语言——学习攻略:深挖指针路线(三)--数组与指针的结合、冒泡排序
c语言·开发语言·数据结构·学习·算法·排序算法·visual studio
小码哥学习中13 分钟前
centos7 安装mysql5.7.36和mysql8.0.32(同时存在)
数据结构
一只小风华~3 小时前
JavaScript 函数
开发语言·前端·javascript·ecmascript·web
苕皮蓝牙土豆4 小时前
Qt 分裂布局:QSplitter 使用指南
开发语言·qt
神经兮兮的小饼6 小时前
字符串是数据结构还是数据类型?
数据结构·字符串
Brookty6 小时前
Java线程安全与中断机制详解
java·开发语言·后端·学习·java-ee
aiprtem7 小时前
LVGL + ESP-Brookesia 嵌入式模拟桌面应用开发
linux·c语言·物联网
從南走到北7 小时前
JAVA东郊到家按摩服务同款同城家政服务按摩私教茶艺师服务系统小程序+公众号+APP+H5
android·java·开发语言·微信小程序·小程序
遇见尚硅谷8 小时前
C语言:20250728学习(指针)
c语言·开发语言·数据结构·c++·笔记·学习·算法
☆璇8 小时前
【C++】C/C++内存管理
c语言·开发语言·c++