线性表
线性表,数据结构中最简单的一种存储结构,专门用于存储逻辑关系为"一对一"的数据。
线性表,基于数据在实际物理空间中的存储状态,又可细分为顺序表(顺序存储结构)和链表(链式存储结构)
通过前面的学习我们知道,具有“一对一”逻辑关系的数据,最佳的存储方式是使用线性表
线性表,全名为线性存储结构。使用线性表存储数据的方式可以这样理解,即“把所有数据用一根线儿串起来,再存储到物理空间中”
如图 1 所示,这是一组具有“一对一”关系的数据,我们接下来采用线性表将其储存到物理空间中
首先,用“一根线儿”把它们按照顺序“串”起来,如图 2 所示:
图 2 中,左侧是“串”起来的数据,右侧是空闲的物理空间。把这“一串儿”数据放置到物理空间,我们可 以选择以下两种方式,如图 3 所示
图 3a) 是多数人想到的存储方式,而图 3b) 却少有人想到。我们知道,数据存储的成功与否,取决于是 否能将数据完整地复原成它本来的样子。如果把图 3a) 和图 3b) 线的一头扯起,你会发现数据的位置依旧没有发生改变(和图 1 一样)。因此可以认定,这两种存储方式都是正确的
将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)
使用线性表存储的数据,如同向数组中存储数据那样,要求数据类型必须一致,也就是说,线性表存储的数据,要么全部都是整形,要么全部都是字符串。一半是整形,另一半是字符串的一组数据无法使用线性表存储
顺序存储结构和链式存储结构
图 3 中我们可以看出,线性表存储数据可细分为以下 2 种:
-
如图 3a) 所示,将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构(简称 顺序表);
-
如图 3b) 所示,数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构(简称链表);
也就是说,线性表存储结构可细分为顺序存储结构和链式存储结构。
前驱和后驱
数据结构中,一组数据中的每个个体被称为“数据元素”(简称“元素”)。例如,图 1 显示的这组数据, 其中 1、2、3、4 和 5 都是这组数据中的一个元素。 另外,对于具有“一对一”逻辑关系的数据,我们一直在用“某一元素的左侧(前边)或右侧(后边)”这样不专业的词,其实线性表中有更准确的术语:
某一元素的左侧相邻元素称为“直接前驱”,位于此元素左侧的所有元素都统称为“前驱元素”;
某一元素的右侧相邻元素称为“直接后继”,位于此元素右侧的所有元素都统称为“后继元素”;
以图 1 数据中的元素 3 来说,它的直接前驱是 2 ,此元素的前驱元素有 2 个,分别是 1 和 2;同理,此 元素的直接后继是 4 ,后继元素也有 2 个,分别是 4 和 5。如图 4 所示:
顺序表
顺序表,全名顺序存储结构,是线性表的一种。线性表用于存储逻辑关系为“一对一”的数据,顺序表自然也不例外
不仅如此,顺序表对数据的物理存储结构也有要求。顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来,存储时做到数据元素之间不留一丝缝隙。
例如,使用顺序表存储集合 {1,2,3,4,5} ,数据最终的存储状态如图 1 所示:
由此我们可以得出,将“具有 '一对一' 逻辑关系的数据按照次序连续存储到一整块物理空间上”的存储结 构就是顺序存储结构
通过观察图 1 中数据的存储状态,我们可以发现,顺序表存储数据同数组非常接近。其实,顺序表存储 数据使用的就是数组
初始化
使用顺序表存储数据之前,除了要申请足够大小的物理空间之外,为了方便后期使用表中的数据,顺序表还需要实时记录以下 2 项数据:
-
顺序表申请的存储容量;
-
顺序表的长度,也就是表中存储数据元素的个数;
提示:正常状态下,顺序表申请的存储容量要大于顺序表的长度
顺序表插入元素
向已有顺序表中插入数据元素,根据插入位置的不同,可分为以下 3 种情况:
-
插入到顺序表的表头;
-
在表的中间位置插入元素;
-
尾随顺序表中已有元素,作为顺序表中的最后一个元素;
虽然数据元素插入顺序表中的位置有所不同,但是都使用的是同一种方式去解决,即:通过遍历,找到数据元素要插入的位置,然后做如下两步工作:将要插入位置元素以及后续的元素整体向后移动一个位置;将元素放到腾出来的位置上;
例如,在 {1,2,3,4,5} 的第 3 个位置上插入元素 6,实现过程如下:
注意
在实现后续元素整体后移的过程,目标位置其实是有数据的,还是 3,只是下一步新插入元素时会把旧元素直接覆盖
顺序表删除元素
从顺序表中删除指定元素,实现起来非常简单,只需找到目标元素,并将其后续所有元素整体前移1个位置即可
后续元素整体前移一个位置,会直接将目标元素删除,可间接实现删除元素的目的
例如,从 {1,2,3,4,5} 中删除元素 3 的过程如图 4 所示:
顺序表更改元素
顺序表更改元素的实现过程是:
-
找到目标元素;
-
直接修改该元素的值;
示例代码
#include "Table.h"
int main()
{
Table t = creatTable(5);
setTable(&t);
displayTable(t);
displayTable(*addNum(&t, 66, 2));
displayTable(*delNum(&t, findWithNum(t, 66)));
changeNum(&t, 2, 99);
displayTable(t);
return 0;
}
#ifndef _TABLE_
#define _TABLE_
/* 一 定义顺序表 */
// 结构体 Table:表类型
typedef struct
{
int * head; // 1 指针 存储申请的内存的首地址
int length; // 2 长度 记录当前顺序表元素个数
int size; // 3 大小 记录当前顺序表最大长度
}Table;
/* 函数声明 */
// 创建顺序表
// 参数: 初始长度
Table creatTable(int SIZE);
// 给所有元素初值
// 参数: 表指针
Table* setTable(Table* ptable);
// 插入元素
// 参数: 表指针 数值 位置(下标)
Table* addNum(Table* ptable, int num, int pos);
// 删除元素 按下标删 返回指针
// 参数: 表指针 位置(下标)
Table* delNum(Table* ptable, int pos);
// 查找元素 按数值查 返回下标
// 参数: 表 数值
int findWithNum(Table table, int num);
// 更改元素 按下标改 返回指针
// 参数: 表指针 下标 值
Table* changeNum(Table* ptable, int pos, int num);
// 输出所有元素的值
// 参数: 表
void displayTable(Table table);
#endif
#include "Table.h"
#include <stdio.h>
#include <stdlib.h>
/* 函数定义 */
// 创建顺序表
// 参数: 初始长度
Table creatTable(int SIZE)
{
Table table;
// 创建一个空的顺序表,动态申请存储空间
table.head = (int*)malloc(sizeof(int)*SIZE);
// 判断: 如果申请失败
if (!table.head)
{
printf("申请失败!\n");
exit(0);
}
table.length = 0; // 长度初始化为 0
table.size = SIZE; // 大小初始化为 SIZE
return table;
}
// 给所有元素初值
// 参数: 表指针
Table* setTable(Table* ptable)
{
for (size_t i = 0; i < ptable->size; i++)
{
// 给值
ptable->head[i] = i+10;
// 改长度
ptable->length++;
}
return ptable;
}
// 插入元素
// 参数: 表指针 数值 位置(下标)
Table* addNum(Table* ptable, int num, int pos)
{
// 判断插入操作是否存在问题
// 插入元素的位置不在插入范围(pos == ptable->length 刚好插在最后)
// pos: 0 --- ptable->length
if (pos > ptable->length || pos < 0)
{
printf("插入位置不对\n");
return ptable;
}
// 1 判断是否有存储空间
// 2 如果不够 那就扩容
if (ptable->length == ptable->size)
{
// 1 记录原来的内存
int* ptemp = ptable->head;
// 2 申请新的内存(2倍)
ptable->head = (int*)calloc(sizeof(int), ptable->size *= 2);
if (!ptable->head)
{
ptable->head = ptemp;
printf("申请内存失败 插入失败 未添加元素\n");
return ptable;
}
// 3 拷贝原来的内存
for (size_t n = 0; n < ptable->length; n++)
{
ptable->head[n] = ptemp[n];
}
// 4 释放原来的内存
free(ptemp);
// 5 置空ptemp
ptemp = NULL;
}
// 插入
// 1 后移(先移动后面的)
for (int i = ptable->length; i >= pos; i--)
{
ptable->head[i + 1] = ptable->head[i];
}
// 2 插入
ptable->head[pos] = num;
// 3 改长度
ptable->length++;
return ptable;
}
// 删除元素 按下标删 返回指针
// 参数: 表指针 位置(下标)
Table* delNum(Table* ptable, int pos)
{
if (pos >= ptable->length || pos < 0)
{
printf("删除位置不对\n");
return ptable;
}
// 删除
for (int i = pos; i < ptable->length; i++)
{
ptable->head[i] = ptable->head[i + 1];
}
ptable->length--;
// 是否缩减大小 (作业)
return ptable;
}
// 查找元素 按数值查 返回下标
// 参数: 表 数值
int findWithNum(Table table, int num)
{
for (int i = 0; i < table.length; i++)
{
if (num == table.head[i])
{
return i;
}
}
return -1;
}
// 更改元素 按下标改 返回指针
// 参数: 表指针 下标 值
Table* changeNum(Table* ptable, int pos, int num)
{
if (pos > ptable->length - 1 || pos < 0)
{
printf("修改失败\n");
return ptable;
}
ptable->head[pos] = num;
return ptable;
}
// 输出所有元素的值
// 参数: 表
void displayTable(Table table)
{
printf("顺序表中存储的元素是:\n");
for (size_t i = 0; i < table.length; i++)
{
printf("%d ", table.head[i]);
}
printf("\ntable.size = %d , table.length = %d:\n\n", table.size,
table.length);
}