数据结构“动态顺序表”实现方法及其原理

本文详细介绍了动态顺序表的实现,包括结构体定义、初始化函数、销毁函数以及关键操作如检查并扩容、尾插、头插、头删、尾删和指定位置插入/删除数据的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        顺序表是基于数组来实现的,它属于线性表的一种,而顺序表又分为“静态顺序表”和“动态顺序表”。今天我们讲解的是动态顺序表。

一、顺序表实现

        实现顺序表第一步是创建顺序表的结构,它是基于一个数组,我们要管理和修改这个数组,就必须添加一些管理数组的变量,这里用结构体来管理,结构体的内容如下:

//设置顺序表内容相关数据类型名重定向
typedef int TypeofSeqList;


//定义顺序表结构体类型,并类型重定向为 SeqList
typedef struct SeqList
{
	TypeofSeqList* Arr;
	int Size;
	int DataCap;

}SeqList;

        这里第一个内容是一个指针后期可以用动态内存管理来扩容,我们对变量类型名字进行了重定义,方便后期做一些改动;第二个变量用来储存顺序表的可用数据;第三个变量用来储存已经开辟的,可以储存的最大数据的数量,DataCap就像一口缸,而Size就是缸的实际水位。

有了这个基础,让我们来编写顺序表的函数

        1,顺序表初始化函数

//初始化顺序表
void SeqListInit(SeqList* SL)
{
	SL->Arr = NULL;
	SL->Size = 0;
	SL->DataCap = 0;
}

        函数的形参是一个SeqList类型的指针,用来接收创建的顺序表,下面三步的作用就是将顺序表里面的指针指向一个有效的地址,然后将其他变量初始化为0。

      2,顺序表的销毁函数

//销毁顺序表
void SLDst(SeqList* SL)
{
	SL->DataCap = 0;
	SL->Size = 0;
	free(SL->Arr);
	SL->Arr = NULL;
}

顺序表的销毁和初始化有着相似之处,不同的是需要将动态申请的内存归还给操作系统,并且将指针指向NULL用来防止野指针。

        3,检查是否需要扩容

//检查是否需要扩容
void CheckLen(SeqList* SL)
{
	if (SL->DataCap == SL->Size)
	{
		
		int NewDataCap = SL->DataCap == 0 ? 10 : SL->DataCap * 2;
		TypeofSeqList* newsl = (TypeofSeqList*)realloc(SL->Arr, NewDataCap * sizeof(TypeofSeqList));
		if (newsl != NULL)
		{
			SL->Arr = newsl;
			newsl = NULL;
			SL->DataCap = NewDataCap;
		}
		else
		{
			perror("realloc");
			exit(1);
		}
	}
}

在这里,这个函数十分重要,在进行数据写入操作时,我们必须保证可用空间足够,也就是DataCap所保存的数据,如果空间不够,将会引起意想不到的意外。

上边提到过Size就是实际水位,DataCap就是水缸的大小,当DataCap等于Size的时候就证明水缸已经满了,需要我们进行扩容。

        int NewDataCap = SL->DataCap == 0 ? 10 : SL->DataCap * 2;
        TypeofSeqList* newsl = (TypeofSeqList*)realloc(SL->Arr, NewDataCap * sizeof(TypeofSeqList));

在初始化时候,DataCap是等于0的,如果直接写

TypeofSeqList* newsl = (TypeofSeqList*)realloc(SL->Arr, SL->DataCap * sizeof(TypeofSeqList));

那么式子“SL->DataCap * sizeof(TypeofSeqList)”将会等于0,在以后便不再等于0,所以我们在此之前需要判断DataCap是否为0,于是便用语句”int NewDataCap = SL->DataCap == 0 ? 10 : SL->DataCap * 2;“来实现DataCap的初值。由于realloc函数有开辟内存失败的可能,如果用SL->Arr去直接接收,就可能导致已有数据丢失,所以需要创建临时变量来储存realloc返回的地址,如果返回的地址不为NULL,那么再讲返回的地址赋值给SL->Arr即可。如果开辟失败,就直接退出程序。

        4,顺序表的尾插

//数据尾插
void SLPushBack(SeqList* SL, TypeofSeqList Data)
{
	assert(SL);
	CheckLen(SL);
	SL->Arr[SL->Size++] = Data;
}

这里为尾部插入数据函数,因为是插入数据,所以先检查是否需要扩容,检查以后便可以写入数据,从尾部写入数据后,Size应该加1,这里方便起见写成“SL->Size++”。

        5,顺序表的头插

//数据头插
void SLPushHead(SeqList* SL, TypeofSeqList Data)
{
	assert(SL);
	CheckLen(SL);
	for (int i = SL->Size;i > 0; i--)
	{
		SL->Arr[i] = SL->Arr[i - 1];
	}
	SL->Arr[0] = Data;
	SL->Size++;
}

头插同尾插原理一样,只不过需要将数据整体后移一个单位,再插入数据

后移原理如下:

从后向前移动可以防止有效数据的覆盖,这里如果想为方便的话可以使用memmove函数来移动。

 同样的,插入数据以后Size需要加1.

        6,顺序表头删

//数据头删
void SLDeleHead(SeqList* SL)
{
	assert(SL);
	if (SL->Size == 0)
	{
		return;
	}
	for (int i = 0; i < SL->Size-1; i++)
	{
		SL->Arr[i] = SL->Arr[i+1];
	}
	SL->Size--;
}

对于删除数据来说,涉及到Size的减1,因为数组元素是没有负数的,所以我们要先判断Size的有效性,然后再进行头删操作,这个原理和头插一样,原理如下

删除之后Size需要减1.

        7,顺序表尾删

void SLDeleBack(SeqList* SL)
{
	assert(SL);
	if (SL->Size == 0)
	{
		return;
	}
	SL->Size--;
}

这里依然是判断Size的有效性,然后删除尾部数据,由于数组一定是有值的,所以只需更改Size的大小,因为我们读取数据只能从0到Size的范围读取。

        8,顺序表指定位置插入数据

//指定位置插入数据
void SLAssiWrite(SeqList* SL, TypeofSeqList Data, int POS)
{
	assert(SL);
	for (int i = SL->Size;i>= POS; i--)
	{
		SL->Arr[i] = SL->Arr[i - 1];
	}
	SL->Arr[POS - 1] = Data;
	SL->Size++;
}

指定位置插入数据需要接收指定的位置,把这个位置及其以后得数据后移,然后再在这个位置插入数据,比如我要在第二个位置插入数据,原理如下:

最后的Size依然需要加1。

        9,顺序表指定位置删除数据

//指定位置删除数据
void SLAssiDele(SeqList* SL, size_t POS)
{
	assert(SL);

	for (int i = POS;i< SL->Size;i++ )
	{
		SL->Arr[i - 1] = SL->Arr[i];
	}
	SL->Size--;
}

我们在指定位置插入数据的基础上,可以更容易理解指定位置删除数据,它的方法是将指定位置之后的数据整体前移,覆盖掉删除位置的数据,达到删除的目的,我以删除第二个数据为例,原理如下:

最后Size需要减1。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值