线性表 :: 顺序存储结构的实现
说明:本文属于读书笔记。笔者将以讲述的方式表达全片文章。故文中提到的某些字词是非正式术语,只是笔者本人的理解性词语。
线性表简介:想要了解点击此处
目录
- 顺序存储结构简介
- 顺序存储结构设计思路
- 顺序存储结构设计的实现
- 顺序存储结构功能的实现
-
- 获取数据元素
-
- 插入数据元素
-
- 删除数据元素
- 顺序存储结构的优缺点
- 结语
-
- 笔者对线性表顺序存储的观点
-
- 关于性能
-
- 其他
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. 顺序存储结构功能的实现
函数 / 功能 实现思路(注意事项):
- 函数的是否需要返回值?如果需要放回置类型是什么?
- 是否需要传递参数?如果需要应该传递说明参数?
- 操作元素有哪些特殊情况?
4.1 获取数据元素
- 获取元素时,应考虑 LinkList 是否为空;
- 获取指定位置的元素时,应传入参数,且注意指定位置不能在数据索引范围之外。
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,则原数组从第三位到最后一位都得给这“新人”腾地方。如下图所示。腾地方也就算了,在实际过程中我们还受其他因素的影响。例如,如果没地儿腾怎么办?
- LinkList 是否已经满了,满则跳出。
- 插入位置是否合理,不合理则跳出;
- 如不存在上述情况,则执行腾地儿操作;
注意实现方式:从末尾开始,指定位置后的元素均向后移动一位。由于输入的时位置标号,不是索引标号,故出现易错点。(自行推导,加深记忆和理解,如果只是想用代码,那就当个CV战士吧。)- 将“新人”请到新位置;
- 计数增加。
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 删除数据元素
删除和插入的情形差不多,一个挪位置,一个填坑。
- LinkList 是否为空了,如果是空,则跳出。
- 删除位置是否合理,不合理则跳出;
- 如不存在上述情况,则执行填坑操作;
注意实现方式:从末尾开始,指定位置后的元素均向前移动一位。- 计数减少。
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 优点
- 设定数组大小后(足够用),无需因为元素之间的逻辑关系而增加额外的存储空间。
- 可以快速的存取表中的元素。
5.2 缺点
- 插入和删除时,需要大量移动元素。
6. 结语
6.1 笔者对线性表顺序存储的观点
- 由于是基于数组实现的,线性表的顺序存储结构性能与数组性能大同小异。
- 由于设计的方式多种多样的,大家可以模仿着取实现其他形式的设计方案。如文中提到的用数组首元素存储当前线性表的大小的方案。
- 想必大家也能想到,文中没有说关于元素的访问修改等。为什么呢?因为基于数组。增删改查中,我们相对麻烦的就是增删,改和查十分简单就没必要实现。
- 在不同的编程语言中实现的底层方式不同。比如python中用序列实现等。
- 在此,还想给大家说明一点。学习数据结构重在理解设计思路,实现思路,遇到难以理解的,可以画图分析,这是一个很好的方法。在实际应用中,不要为了体现能力而使用(虽然本篇文章没有什么可体现能力的地方,大家懂意思就行)。能明白吗?因地制宜才是最佳选择。
6.2 关于性能
增:O(n)
删:O(n)
改:O(1)
查:O(1)
6.3 其他
有头单链表的设计与实现
无头单链表(待更新)
循环链表(待更新)
双向链表(待更新)