线性表 :: 顺序存储结构的实现

线性表 :: 顺序存储结构的实现

说明:本文属于读书笔记。笔者将以讲述的方式表达全片文章。故文中提到的某些字词是非正式术语,只是笔者本人的理解性词语。

线性表简介:想要了解点击此处

目录

  • 顺序存储结构简介
  • 顺序存储结构设计思路
  • 顺序存储结构设计的实现
  • 顺序存储结构功能的实现
    • 获取数据元素
    • 插入数据元素
    • 删除数据元素
  • 顺序存储结构的优缺点
  • 结语
    • 笔者对线性表顺序存储的观点
    • 关于性能
    • 其他

1. 顺序存储结构简介
1.1定义

用一段 地址连续的存储单元 依次存储线性表的数据元素。


1.2 实现基础

由于线性表中存储的是 同种数据类型 ,且存储单元连续,在 C 语言中,可以使用一维数据来实现顺序存储结构。


2. 顺序存储结构设计思路

当我们采取使用数组的方式来实现顺序存储结构是,势必要思考二者的异同。

首先,对于数组而言,其基本特点是 存储相同数据类型的数据存储单元的地址是连续的 ,最特殊的一点是:数组的起始索引位置为 0 ,这是一个切入点。比如说,如果我们设计的线性表希望他有记录数据个数的基本属性,同时希望存储的数据位置与其索引值相同,那数组中的 0 号位,是不是可以用来记录当前线性表的数据元素个数。(如下图)

在这里插入图片描述

其次,在我们定义了数组之后,数组大小与线性表数据元素个数有什么关系呢?
假设:我们定义了一个数组大小为20,那么由于 0 号位用于记录当前线性表的数据元素个数,故我们所设计的线性表最多可存储19个数据元素。即,线性表的最大存储数据元素应是 不超过 数组大小的。(与设计思路方式有关!)


3. 顺序存储结构设计的实现

如下代码:(设计方式:将顺序存储的数据元素个数作为内置属性,故使用结构体进行构造新结构。阅读者可仿照思路自行实现其他方式的设计。)

#define MAXSIZE 20				/* 为了后续需要改变数组大小而设定 */
typedef int ElementType;		/* 此处以 int 为例,后续可修改成其他任意数据类型 */
typedef struct{
	ElementType arr[MAXSIZE];	/* 数组,存储数据元素 */
	int length;					/* 记录线性表当前元素个数 */
}LinkList;

4. 顺序存储结构功能的实现

函数 / 功能 实现思路(注意事项):

  1. 函数的是否需要返回值?如果需要放回置类型是什么?
  2. 是否需要传递参数?如果需要应该传递说明参数?
  3. 操作元素有哪些特殊情况?

4.1 获取数据元素
  1. 获取元素时,应考虑 LinkList 是否为空;
  2. 获取指定位置的元素时,应传入参数,且注意指定位置不能在数据索引范围之外。
void GetElement(LinkList L,int i,ElementType* val){
	// 情形一:传入的表为空
	if(0 == length)
		return;
	// 情形二:当输入的索引不在数组索引范围内,且小于 0 时,返回第一个元素
	if(i < 0)
		printf("%d\n",L[0]);
	// 情形三:当输入的索引大于当前线性表元素个数时,返回最后一个元素
	if(i > L.length)
		printf("%d\n",L[length]);
	// 排除情形后
	printf("%d\n",L[i-1]);
}

以上代码,笔者认为没必要这么"人性化",情形二和情形三,显然是应用者的输入失误,如果我们返回给他我们认为的值,如上述代码中的 L[0] 和 L[length] ,反而可能误导应用者,故修改上述代码如下:

void GetElement(LinkList L,int i,ElementType* val){
	if(0 == length || i < 0 || i > L.length)
		return printf("线性表为空或输入越界!\n");
	*val = L[i-1];
	printf("获取的目标值为:%d", *val);
}

4.2 插入数据元素

示意图:
由于是基于数组实现,当然避免不了数组本身的一些弊端。即当我们操作元素不在尾部时,数组的增删时间复杂度都在 O(n)。如果在第三个位置处插入一个新元素 111,则原数组从第三位到最后一位都得给这“新人”腾地方。如下图所示。腾地方也就算了,在实际过程中我们还受其他因素的影响。例如,如果没地儿腾怎么办?

在这里插入图片描述

  1. LinkList 是否已经满了,满则跳出。
  2. 插入位置是否合理,不合理则跳出;
  3. 如不存在上述情况,则执行腾地儿操作;
    注意实现方式:从末尾开始,指定位置后的元素均向后移动一位。由于输入的时位置标号,不是索引标号,故出现易错点。(自行推导,加深记忆和理解,如果只是想用代码,那就当个CV战士吧。)
  4. 将“新人”请到新位置;
  5. 计数增加。
void insertElement(LinkList* L,int i,ElemnetType val){
	if(MAXSIZE == L->length) 
	{
		printf("线性表已满,无法插入。\n");
		return;
	}
	if(i < 0 || i > L->length+1)
		return;
	int k;
	if(i <= L->length)
	{	/* 此时实现从末尾开始,指定位置后的元素均向后移动一位 */
		for(k = L->length-1;k >= i-1;k--)
			L->arr[k+1] = L->arr[k];
	}
	L->arr[i-1] = val;
	L->length++;
}

4. 3 删除数据元素

删除和插入的情形差不多,一个挪位置,一个填坑。

  1. LinkList 是否为空了,如果是空,则跳出。
  2. 删除位置是否合理,不合理则跳出;
  3. 如不存在上述情况,则执行填坑操作;
    注意实现方式:从末尾开始,指定位置后的元素均向前移动一位。
  4. 计数减少。
void deleteElement(LinkList* L,int i){
	if(0 == L->length) 
	{
		printf("线性表为空,操作失败。\n");
		return;
	}
	if(i < 0 || i > L->length)
		return;
	int k;
	if(i < L->length)
	{	/* 此时实现从末尾开始,指定位置后的元素均向前移动一位 */
		for(k = i;k < L->length;k++)
			L->arr[k-1] = L->arr[k];
	}
	L->length--;
}

5. 顺序存储结构的优缺点
5.1 优点
  1. 设定数组大小后(足够用),无需因为元素之间的逻辑关系而增加额外的存储空间。
  2. 可以快速的存取表中的元素。
5.2 缺点
  1. 插入和删除时,需要大量移动元素。

6. 结语
6.1 笔者对线性表顺序存储的观点
  1. 由于是基于数组实现的,线性表的顺序存储结构性能与数组性能大同小异。
  2. 由于设计的方式多种多样的,大家可以模仿着取实现其他形式的设计方案。如文中提到的用数组首元素存储当前线性表的大小的方案。
  3. 想必大家也能想到,文中没有说关于元素的访问修改等。为什么呢?因为基于数组。增删改查中,我们相对麻烦的就是增删,改和查十分简单就没必要实现。
  4. 在不同的编程语言中实现的底层方式不同。比如python中用序列实现等。
  5. 在此,还想给大家说明一点。学习数据结构重在理解设计思路,实现思路,遇到难以理解的,可以画图分析,这是一个很好的方法。在实际应用中,不要为了体现能力而使用(虽然本篇文章没有什么可体现能力的地方,大家懂意思就行)。能明白吗?因地制宜才是最佳选择。
6.2 关于性能

增:O(n)
删:O(n)
改:O(1)
查:O(1)

6.3 其他

有头单链表的设计与实现
无头单链表(待更新)
循环链表(待更新)
双向链表(待更新)

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NPC的白话文谈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值