【数据结构初阶】C语言实现动态顺序表

一、顺序表基础知识

1.1 线性表

线性表是具有一类相同特性的数据结构的集合

常见的线性表:顺序表、链表、栈、队列、字符串
线性表的数据呈线性从左往右挨着存放

线性表的两种结构
逻辑结构:人为想象出来的数据组织形式 - - 一定是线性

例如:我们认为排队的时候,人们整体是呈一条直线或曲线在排列

物理结构:数据在内存上的存储形式 - - 不一定是线性
(1)数组 - -> 空间是连续的
(2)链表:数据是一块一块的,用指针链接起来(java中用引用来链接)- -> 空间是不连续的

1.2 顺序表

作用:把数据存起来

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,

一般采用数组存储在数组上完成数据的增删查改

数据结构完成的是对顺序表的增删查改等接口函数

顺序表要求
1.连续的物理空间存储 ,一般采用数组存储
2.数据必须是从头开始,依次存储(不能间隔着存)

顺序表特点
逻辑结构:一定是线性
物理结构:一定是线性
底层结构是数组

顺序表的分类
1)静态顺序表:使用定长数组存储,长度是固定的

缺点:空间给小了不够用,给大了浪费

2)动态顺序表:使用动态开辟的数组存储

二、代码实现动态顺序表

2.1 所需文件

SeqList.c: 完成数据的增删查改等函数的具体实现

SeqList.h:完成完成数据的增删查改等接口函数
Test.c :对 SeqList.c 里写的函数进行测试

Seq 是sequnce的缩写,意思为流畅的,流利的
List 意思为列表

在这里插入图片描述

2.2 SeqList .h文件

顺序表是对数组进行封装,并且由于涉及多个数据,所以我们使用结构体

单个数据定义变量,多个数据定义结构体

一般我们定义顺序表之前,是不知道顺序表具体大小的,所以我们定义的第一个成员数组用一个指针变量 int* arr 来表示,等到程序运行中需要使用的时候再申请空间(动态内存管理)。又因为动态顺序表可以增容,所以我们再定义一个成员变量int capacity用来记录当前顺序表的空间大小。

因为顺序表中不只能存储整型的数据,所以为了方便我们以后修改顺序表里数据的类型,我们使用 typedef 将不确定的数据类型 (比如int* arr中的int类型),重命名为SLDatatype

在这里插入图片描述
由于在顺序表中,我们可以动态地插入数据,所以我们需要定义一个变量 int size,用来记录当前顺序表的有效数据个数
在这里插入图片描述
由于原结构体的名字比较长,不方便书写,所以我们使用 typedef 将其重命名为SL
在这里插入图片描述

(1) 顺序表结构的初始化、销毁、打印 - - 函数声明

返回类型:void
形参:指向结构体的指针变量 ps

形参的部分为什么要使用指针变量来接收呢?原因是函数的传参分为传值调用和传址调用,

传值调用:把实参拷贝给形参,实参和形参指向的是两块不同的空间,对形参的改变不会影响到实参

传址调用:把实参的地址拷贝给形参,形参和实参指向的是同一块空间,对形参的改变会影响到实参

在这里插入图片描述

由于涉及到在屏幕上打印信息,所以要包含头文件 stdio.h

(2) 插入数据(头插,尾插)- - 函数声明

返回类型:void
形参:指向结构体的指针变量 ps 和要插入的数据 x
在这里插入图片描述

需包含的头文件:
插入数据涉及到动态内存管理,要包含头文件 stdlib.h

使用 assert 判断插入的数据是否为空,要包含头文件 assert.h

(3) 删除数据(头删,尾删) - - 函数声明

返回类型:void
形参:指向结构体的指针变量 ps

在这里插入图片描述

(4) 在指定位置插入,删除数据 – 函数声明

1)在指定位置插入数据
返回类型:void
形参:指向结构体的指针变量 ps,指定插入的数据,指定插入数据位置的下标

2)在指定位置删除数据
返回类型:void
形参:指向结构体的指针变量 ps,指定删除数据位置的下标

在这里插入图片描述

(5)查找数据 – 函数声明

返回类型:int
形参:指向结构体的指针变量 SL* ps ,要查找的下标 x

查找数据分两种情况
1)查找成功,我们需要返回该数据在顺序表中对应的下标
2)查找失败,我们只需要返回一个无效的下标,比如-1
所以查找数据的函数需要一个整型的返回值
在这里插入图片描述

2.3 SeqList.c文件

要包含头文件 SeqList.h

(1) 顺序表的初始化,销毁,打印

初始化:
初始情况下数组里没有数据,我们把它置为空,等到要使用的时候再动态地申请空间。
数组为空,所以有效数据个数和空间大小都为0

在这里插入图片描述

销毁:
销毁顺序表之前,我们需要先判断顺序表底层的数组是否为空,如果不为空,我们就将申请的空间释放
空间释放后,将数组置为空,有效数据个数和空间大小的值为0,让顺序表回到初始状态
在这里插入图片描述
打印:
使用循环来打印顺序表里的有效数据(size)
在这里插入图片描述

(2) 插入数据(头插,尾插)

插入数据分为头插(从头部插入数据)和尾插(从末尾插入数据)
在这里插入图片描述

但无论是哪种方法插入数据,我们都需要先判断空间是否充足

以尾插为例:
空间充足:直接往size指向的位置插入一个数据,再size++
空间不够:需要先增容 (一般两倍) ,然后在size指向的位置插入一个数据,再size++
在这里插入图片描述

所以我们先封装一个判断空间是否充足的函数,后续只要是涉及到插入数据,我们就先调用这个函数,用来检查空间是否充足

判断空间是否充足的函数实现思路:

  1. 首先,判断空间是否充足
    当capacity==size时,说明顺序表满了,需要增容(一般两倍)
    增容前需要判断capacity的值,如果capacity为0,就给默认值,否则就capacity*2
if (ps->size == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
	}	
  1. 然后,动态申请空间
    动态申请空间有两种情况,申请成功和申请失败。
    1)如果申请失败,会返回空,导致顺序表里的所有数据都被清空,所以我们用一个临时变量tmp来接收返回值,然后判断tmp里的值是否为空,如果为空,就不继续插入数据,打印完提示信息后就退出
SLDatatype* tmp = (SLDatatype*)realloc(ps->arr, newCapacity * sizeof(SLDatatype));

	if (tmp == NULL)//动态申请空间失败
		{
			perror("realloc fail!");//打印信息
			exit(1);//不继续插入数据,退出
		}

realloc的两个参数分别表示:对哪块地址增加空间, 增加多少字节的空间(size_t类型)

2)如果申请成功,就将tmp的值赋给arr,将增容后的空间赋给capacity

    //动态申请空间成功
	ps->arr = tmp;
	ps->capacity = newCapacity;//容量增大为2倍

在这里插入图片描述

尾插:

  1. 判断传过来的是否是一个有效的顺序表结构 assert(ps)
  2. 判断空间是否充足(调用SLCcapacity函数)
    空间充足:直接往size指向的位置插入一个数据,再size++
    空间不够:先增容 (一般两倍) ,然后在size指向的位置插入一个数据,再size++
    在这里插入图片描述

头插:

  1. 判断传过来的是否是一个有效的顺序表结构 assert(ps)
  2. 判断空间是否充足(调用SLCcapacity函数)
  3. 原数组里的数据整体向后挪动一位 ,将要插入的数据放到数组的第一个位置

在这里插入图片描述
相应代码:
在这里插入图片描述

(3) 删除数据(头删,尾删)

尾删:

  1. 判断传过来的是否是一个有效的顺序表结构 assert(ps)
  2. 判断顺序表结构是否为空,为空(size = 0),则不可删除
  3. 有效数据个数-1
    在这里插入图片描述
    相应代码:
    在这里插入图片描述

头删:

  1. 判断传过来的是否是一个有效的顺序表结构 assert(ps)
  2. 判断顺序表结构是否为空,为空(size = 0),则不可删除
  3. 将数组中第一个数据之后的数据整体向前挪动一位
    1)把下标为0的数据覆盖
    2)把后一个位置的数据赋给前一个位置的数据
    在这里插入图片描述

相应代码:
在这里插入图片描述

(4) 在指定位置插入,删除数据

在指定位置插入数据:

  1. 判断传过来的是否是一个有效的顺序表结构 assert(ps)
  2. 限制指定位置的下标pos的范围,[0,size] 之间
    1)pos = 0,相当于头插
    2)pos = size,相当于尾插
  3. 判断空间是否足够(CapaCity(ps) )
  4. 让pos及pos之后的数据整体后移一位
  5. 将要插入的数据赋给arr[pos],有效数据个数+1
    在这里插入图片描述
    相应代码:
    在这里插入图片描述

删除指定位置的数据:

  1. 判断传过来的是否是一个有效的顺序表结构 assert(ps)
  2. 判断顺序表结构是否为空,为空(size = 0),则不可删除
  3. 限制指定位置的下标pos的范围,[0,size) 之间
    1)pos = 0,相当于头删
    2)pos不能等于size,原因:size指向的是最后一个有效数据的下一个位置,是没有数据的
  4. 把pos之后的数据整体向前挪动一位
  5. 有效数据个数-1
    在这里插入图片描述

在这里插入图片描述

(5)查找数据
  1. 判断传过来的是否是一个有效的顺序表结构 assert(ps)
  2. for循环查找数据
    1)如果查找到,就返回数据对应的下标
    2)如果没有查找到,就返回无效数据,比如 -1
    在这里插入图片描述

2.4 Test.c文件中

要包含头文件 SeqList.h

(1) 测试顺序表的初始化和销毁

在这里插入图片描述

(2) 测试插入数据(头插,尾插)

尾插:
在这里插入图片描述
运行结果:
在这里插入图片描述

头插:

在这里插入图片描述
运行结果:
在这里插入图片描述

(3) 测试删除数据(头删,尾删)

尾删:
在这里插入图片描述
运行结果:
在这里插入图片描述

我们再测试一下把顺序表里的数据删完后,还能否再删除数据
在这里插入图片描述
出现断言为假的报错,说明代码正确
在这里插入图片描述

头删:
在这里插入图片描述
在这里插入图片描述

同样,测试一下把顺序表里的数据删完后,还能否再删除数据
在这里插入图片描述
出现断言报错的弹窗,说明代码正确
在这里插入图片描述

(4) 测试在指定位置插入、删除数据

1. 测试指定位置插入数据:

测试在数据头部、中间,尾部插入数据,
在这里插入图片描述

在这里插入图片描述

测试,当顺序表为空时插入数据
在这里插入图片描述
在这里插入图片描述

2. 测试删除指定位置数据:

在这里插入图片描述
在这里插入图片描述

(5)测试查找数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、动态顺序表完整代码

SeqList .h文件

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//增强程序的可维护性
typedef int SLDatatype;//将int类型重命名

 typedef struct SeqList {
	SLDatatype* arr;

	//只能为整型
	int capacity;//空间大小
	int size;    //当前顺序表中有效数据个数
}SL;

//typedef struct SeqList SL;

//初始化
void SLInit(SL* ps);
//销毁
void SLDestroy(SL* ps);
//打印
void SLPrint(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, SLDatatype x, int pos);
//删除指定位置的数据
void SLErase(SL* ps, int pos);

//查找数据
int SLFind(SL* ps, SLDatatype x);

SeqList.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"

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

//销毁
void SLDestroy(SL* ps)
{
	if (ps->arr)//相当于ps->arr != NULL
	{
		free(ps->arr);//申请的空间释放
	}
	//让其回到最初始的状态
	ps->arr = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

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

//判断空间是否充足
void SLCheckCapacity(SL* ps)
{
	if (ps->size == ps->capacity)
	{
		//判断capacity, 0*2 =0,如果为0,给默认值,否则*2倍
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

		//动态申请空间
		SLDatatype* tmp = (SLDatatype*)realloc(ps->arr, newCapacity * sizeof(SLDatatype));

		//动态申请空间失败
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1); //不继续插入数据,退出
		}

		//动态申请空间成功
		ps->arr = tmp;
		ps->capacity = newCapacity;//空间大小增加2倍
	}
}

//插入数据(头插,尾插)
void SLPushBack(SL* ps, SLDatatype x)//尾插
{
	//先判断,传过来的顺序表结构不能为空
	assert(ps);//等价于assert(ps != null)
	SLCheckCapacity(ps);//判断空间是否充足
	ps->arr[ps->size++] = x;
}

void SLPushFront(SL* ps, SLDatatype x)//头插
{
	assert(ps);
	SLCheckCapacity(ps);

	//数据整体后移一位
	int i = 0;
	for (i = ps->size; i > 0; i--)
	{
       ps->arr[i] = ps->arr[i - 1];
	}
	//下标为0的位置空出来
	ps->arr[0] = x;
	ps->size++;//插入数据,有效数据+1
}

//删除数据(头删,尾删)
void SLPopBack(SL* ps)//尾删
{
	assert(ps);//判断是否为有效的顺序表结构
	assert(ps->size);//判断顺序表结构是否为空

	ps->size--;//有效数据个数-1
}

void SLPopFront(SL* ps)//头删
{
	assert(ps);
	assert(ps->size);
	
	//数据整体向前挪动一位
	int i = 0;
	for (i = 0; i < ps->size - 1; i++)
	{
      ps->arr[i] = ps->arr[i + 1];
	}
	//有效数据个数-1
	ps->size--;
}

//在指定位置之前插入数据(空间足够才能插入数据)
//pos指的是下标的位置
void SLInsert(SL* ps, SLDatatype x, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);

	//先判断空间大小是否足够
	SLCheckCapacity(ps);

	//pos及向后的数据整体向后移动一位
	int i = 0;
	for (i = ps->size; i > pos; i--)
	{
       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);
	assert(ps->size);//若顺序表结构为空,则不可删除数据

	//pos之后的数据整体向前挪动一位
	int i = 0;
	for (i = pos; i < ps->size - 1 ; i++)
	{
		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 (x == ps->arr[i])
		{
			return i;
		}
	}
	return -1;
}
  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值