成为编程大佬!!----->数据结构与算法(2)——顺序表!!

前言:线性表是数据结构与算法的重中之重所有具有线性逻辑结构的数据结构,都能称为线性表。这篇文章我们先来讨论线性表中的顺序表,顺序表和线性表都是后续实现栈,树,串和图等等结构的重要基础。

目录

简单介绍线性表

顺序表

顺序表的存储

动态存储

静态存储

静态存储与动态存储的优缺点

顺序表操作

1.初始化顺序表

2.销毁顺序表

3.插入数据

插入数据之判断已满否

插入操作之尾插

插入操作之头插

插入数据之插入指定位置

4.删除数据

删除数据之尾删

删除数据之头删

删除数据之删除指定位置

5.查找顺序表单个数据

⭐6.打印顺序表中所有数据

结语


简单介绍线性表

对于某种数据结构的存储特点可以从两个方面来看:逻辑结构物理结构

逻辑结构由人想像出来的抽象结构

物理结构在内存上的存储结构

下面要介绍的顺序表——逻辑结构上,是一段依次排列的、连续存储的数据;在物理结构上,也是连续存储在内存上的。也就是说,顺序表在逻辑结构和物理结构上都是线性的而只要在逻辑结构上是线性的,它就是线性表)。

顺序表

因为顺序表在物理结构上是连续存储的,所以顺序表的本质就是数组。但是数据结构与算法需要对数据进行相关的管理(如增删查改),因此,我们需要对存储数据的数组进行封装。

顺序表的存储

我们用结构体来封装顺序表,结构体内除了包括存储数据的数组之外,我们再定义两个成员,用来保存顺序表的最大存储容量(capacity)顺序表的实际存储的有效数据(size)

动态存储

//顺序表中数据类型
typedef int SLdatatype;
//顺序表存储结构
typedef struct SeqList
{
	SLdatatype* arr;
	int capacity;
	int size;
}SL;

i.动态存储需要申请动态数组,通过调用动态内存管理函数来申请空间,然后通过指针arr保存申请空间的地址来形成动态数组。

ii.动态存储的顺序表,使用的是堆上的空间

静态存储

#define M 100

//顺序表中数据类型
typedef int SLdatatype;
//顺序表存储结构
typedef struct SeqList{
    SLdatatype arr[M];
    int capacity;
    int size;
}SL;

i.静态存储,只需直接创建数组即可。数组大小是定死的。

ii.静态存储的顺序表,使用的是栈上的空间

静态存储与动态存储的优缺点

然而在实际编码项目中,出现过量的空间浪费或者空间不足都有可能导致严重的问题。空间浪费会消耗不必要的存储成本; 空间不足,会导致数据的泄露或者丢失。这些情况都会对管理者和用户造成严重损失。

因此,我们优先使用申请空间自由度高的动态存储来实现顺序表(以下操作均以动态存储为基础来实现)。

顺序表操作

下方非重点操作打了星星。

1.初始化顺序表

//初始化
void SLInit(SL* ps)
{
	//全部置空置零
	ps->arr = NULL;
	ps->capacity = 0;
	ps->size = 0;
}

将顺序表的成员全部置空置零。

2.销毁顺序表

//销毁顺序表
void SLDestroy(SL* ps)
{
	if (!ps)
	{
		printf("empty ptr!\n");
		return;
	}
	free(ps->arr);//释放动态数组
	SLInit(ps);//初始化顺序表
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)

ii.释放动态数组

iii.初始化顺序表

3.插入数据

插入新的数据就会占用顺序表容量,因此我们要确保顺序表有充足的容量如果不够,我们得进行扩容

插入数据之判断已满否
void SLCheckSpace(SL* ps)
{
    if (!ps)
    {
        printf("empty ptr\n");
        return;
    }
    if (ps->capacity == ps->size)//空间已满
    {
        int newCapacity = 1;
        if (ps->capacity)
        {
            newCapacity = ps->capacity;
        }
        int* temp = (int*)realloc(ps->arr, sizeof(SLdatatype) * 2 * newCapacity);//扩容
        if (temp)//扩容成功
        {
            ps->arr = temp;//接收扩容后地址
            ps->capacity = 2 * newCapacity;//修改顺序表容量
        }
    }
    return;
}

i.首先要判断传入的顺序表指针是否为空一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)

ii.如果空间已满,我们就进行扩容。扩容方式将容量扩大到原来的两倍

iii.修改顺序表扩容后的容量ps->capcity。

!!注意!!如果顺序表刚进行初始化操作容量是零,此时容量乘2之后还是零

因此,我们要设置默认值newcapacity为1。当容量为0的时候,使newcapacity不变;当容量不为零时,使newcapacity等于原容量.

PS:为什么是要扩大到原来的两倍呢?

因为,如果我们在容量不够的时候,只扩容多一个数据单位的容量,那么在下次插入时,又要进行一次扩容。虽然没有空间浪费,但是次数繁多的扩容操作,会让程序的时间效率大大的降低。所以,我们采用这种扩容方式,这样就能大大减少扩容的次数(因为每次都使得顺序表的容量呈指数级增长),同时这样的操作所产生的浪费也是可以控制的。

再补充一点,我们也可以让顺序表的容量每次扩大到原来的3倍,4倍,5倍都可以,按照实际情况来选择即可。

插入操作之尾插
//尾插
void SLPushBack(SL* ps, SLdatatype x)
{
	if (!ps)//检查顺序表指针
	{
		printf("empty ptr!\n");
		return;
	}
	SLCheckSpace(ps);//检查扩容
	ps->arr[ps->size++] = x;//尾插后,size++
	return;
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。

ii.进行检查扩容

iii.直接在顺序表最后的位置插入新数据(索引为ps->size)。

iiii.顺序表有效数据数ps->size++

插入操作之头插
//头插
void SLPushFront(SL* ps, SLdatatype x)
{
    if (!ps)
    {
        printf("empty ptr!\n");
        return;
    }
    SLCheckSpace(ps);
    int i = ps->size;
    while (i >= 1)
    {
        ps->arr[i] = ps->arr[i - 1];
        i--;
    }
    ps->arr[0] = x;
    ps->size++;
    return;
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。

ii.检查扩容

iii.将所有的数据往后挪一个数据单位。从后面的数据开始依次往后挪,否则较后面的数据会被覆盖导致丢失。

iiii.将新数据插入顺序表第一个位置

iiiii.顺序表有效数据ps->size++

插入数据之插入指定位置
//插入指定位置数据
void SeqListInsert(SL* ps, SLdatatype x, int pos)
{
	if (!ps)
	{
		printf("empty ptr!\n");
		return;
	}
	if (pos < 0 || pos >= ps->size)
	{
		printf("wrong pos!\n");
		return;
	}
	SLCheckSpace(ps);
	for (int i = ps->size; i > pos; i--)//pos后数据往后挪1位
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;//插入数据
	ps->size++;//size加一
	return 0;
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。

ii.判断插入位置pos是否合法

iii.检查扩容

iiii.将pos往后的数据都向后挪动一个数据单位。要从后面的数据开始依次往后挪,否则较后面的数据会被覆盖导致丢失。

iiiii.插入新数据到pos位置

iiiiii.顺序表有效数据容量ps->size++

4.删除数据

删除数据之尾删
//尾删
void SLPopBack(SL* ps, SLdatatype* x)
{
	if (!ps)
	{
		printf("empty ptr!\n");
		return;
	}
	if (ps->size == 0)
	{
		printf("无数据可删!\n");
		return;
	}
	*x = ps->arr[--ps->size];//x接收删除的数据
	//最终,size会被减1,删除的数据虽然仍保存在数组中,但不影响后续操作。
	return;
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。

ii.判断顺序表是否还有数据可删除

iii.用x接收删除的数据

iiii.顺序表有效数据ps->size--

!!注意!!虽然顺序表中,实际上还存储着删除了的数据,但是这个数据由于ps->size的减小,已经无法访问或者使用,并且该残留数据也不会影响后续的操作。

删除数据之头删
//头删
void SLPopFront(SL* ps, SLdatatype* x)
{
	if (!ps)
	{
		printf("empty ptr!\n");
		return;
	}
	if (ps->size == 0)
	{
		printf("无数据可删!\n");
		return;
	}
	*x = ps->arr[0];//接收删除的数据
	int i = 1;
	while (i < ps->size)//数据整体往前挪
	{
		ps->arr[i - 1] = ps->arr[i];
		i++;
	}
	ps->size--;//size减1
	return;
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。

ii.判断顺序表是否还有数据可删除

iii.用x接收删除的数据

iiii.将第一位往后的所有数据都往前挪一位,从而覆盖第一位数据。要从前面的数据开始依次往前挪,否则较前面的数据会被覆盖导致丢失。

iiii.顺序表有效数据数ps->size--

!!注意!!虽然顺序表经过该操作之后,ps->arr[size]上还保存着数据,但是这个数据由于ps->size的减小,已经无法访问或者使用,并且该残留数据也不会影响后续的操作。

删除数据之删除指定位置
//删除指定位置数据
void SeqListErase(SL* ps, int pos)
{
	if (!ps)
	{
		printf("empty ptr!\n");
		return;
	}
	if (pos < 0 || pos >= ps->size)
	{
		printf("wrong pos!\n");
		return;
	}
	if (ps->size == 0)
	{
		printf("无数据可删!\n");
		return;
	}
	for (int i = pos + 1; i < ps->size; i++)
	{
		ps->arr[i - 1] = ps->arr[i];
	}
	ps->size--;
	return;
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。

ii.判断插入位置pos是否合法

iii.判断顺序表是否还有数据可删除

iiii.将pos往后的数据都向前挪动一个数据单位要从前面的数据开始依次往前挪,否则较前面的数据会被覆盖导致丢失。

iiiii.顺序表有效数据ps->size--

5.查找顺序表单个数据

//查找顺序表单个数据
int SeqListFind(SL* ps, SLdatatype x)
{
	if (!ps)
	{
		printf("empty ptr!\n");
		return -1;
	}
	if (ps->size == 0)
	{
		printf("顺序表为空!\n");
		return -1;
	}
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == x)
		{
			printf("数据x在顺序表的 %d 索引处\n", i);
			return i;
		}
	}
	printf("不存在这样的数据x\n");
	return -1;
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。

ii.判断顺序表是否有数据。没有数据就没必要查找了。

iii.循环遍历顺序表来查找。找到了就返回其索引;没找到就打印数据不存在的信息,返回-1。

⭐6.打印顺序表中所有数据

(实际打印方式不只下方代码一种,可根据实际情况设计打印操作)

//输出顺序表数据
void SLPrint(SL* ps)
{
	if (!ps)
	{
		printf("empty ptr!\n");
		return;
	}
	if (ps->size == 0)
	{
		printf("顺序表为空!\n");
		return;
	}
	printf("当前顺序表数据为:");
	for (int i = 0; i < ps->size; ++i)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
	return;
}

i.首先要判断传入的顺序表指针是否为空。一来可以保证程序合理合法运行,二来也可以消除VS的警告(ps可能为空指针)。

ii.判断顺序表是否有数据。没有数据就没必要打印了。

iii.循环遍历顺序表来打印

结语

看完这篇博客,相信你已经对算法复杂度有了深刻认识了。有什么疑问和困惑欢迎来评论区留言!!🤩我一定尽力及时解答!!制作不易,求关注!!求点赞!!之后还会有更多有用的干货博客会发出哦!!欢迎做客我的主页!!❤❤Elnaij-CSDN博客❤❤

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值