前面一篇文章主要介绍了数据结构和算法的一些基本概念, 从这篇文章开始进行对数据结构的展开分析。从前面我们了解到数据结构主要研究的是数据的
逻辑结构
和物理结构
以及他们之间的关系, 接下来我们就展开来研究一下不同的结构和关系下产生的各种数据结构。
线性表
定义
线性表是最基本、最简单、也是最常用的一种数据结构, 一个线性表是 n 个具有相同特性的数据元素的有限序列。
举个例子来说明一下:
假如我们这里有一张学生表, 表里面存储的每一个学生都是一个数据元素, 每一个数据元素都包含诸如 姓名、年龄、性别等数据项。
这张表里的各个学生就是不同的数据元素, 但是组成学生的各个数据项是相同的(也就是学生具有相同的特性), 像这样由 n(n >= 0) 个特性相同的数据元素组成的有限序列就叫做 线性表。
逻辑结构
说到 线性
, 首先肯定可以想到 逻辑结构 中的 线性结构
, 这里我们说的 线性
和 非线性
都是在 逻辑层次 上的讨论, 而没有去考虑 存储结构 。所以后面将要说的 链表
也属于 线性表 。
如果在逻辑结构上细分的话, 线性表又可以分为 一般线性表
和 受限线性表
。一般线性表也就是我们通常说的线性表, 他可以自由的 删除或者添加结点。至于受限线性表主要包括 栈
和 队列
, 所谓受限就是对结点的操作受限制 ( 栈
为 "先进后出", 队列
为 "先进先出") 。
存储结构
根据存储结构的不同, 线性表主要有 顺序表示
和 链式表示
两种, 在实际使用中, 常以 栈、队列、字符串等形式使用。
顺序表示
是指用一组地址连续的存储单元依次存储数据元素 (称为线性表的 顺序存储结构
)。通过 "物理位置相邻" 来表示线性表中数据元素之间的逻辑关系, 可以随机存取表中的任一元素。
链式表示
是指用一组任意的存储单元存储数据元素 (称为线性表的 链式存储结构
)。他们可以是连续的, 也可以是不连续的, 在表示数据元素之间的逻辑关系时, 除了存储自身的信息之外, 还需要存储其直接后继的信息, 这两部分信息组成了数据元素的存储映像, 称为结点(node) 。他包括两个部分, 存储数据的部分为 数据域
, 存储直接后继位置的部分为 指针域
。指针域存储的信息称为 指针或链
。
注: 本篇我们主要研究 顺序表示 存储结构下的线性表。
特点
回到线性表, 线性表中元素的个数 n
定义为线性表的 长度 , 如果 n = 0
的话, 我们称为 空表 。
对于非空的 线性表 或 线性结构 有以下特点:
- 存在唯一的一个被称作
第一个
的数据元素 - 存在唯一的一个被称作
最后一个
的数据元素 - 除了第一个数据元素之外, 结构中的每个数据元素均有一个前驱
- 除了最后一个元素之外, 结构中的每个数据元素均有一个后继
顺序表

线性表的顺序表示
我们知道, 顺序表示指的是一组地址连续的存储单元依次存储线性表的数据元素, 这种表示也称为 线性表的顺序存储结构 或 顺序映像 , 通常称这种结构的线性表为 顺序表 。他的特点在于, 逻辑上相邻的数据元素, 物理次序也是相邻的。
顺序表的存储结构
/* 宏定义顺序表的长度 */
#define MAXSIZE 100
/* ElemType类型根据实际情况而定,这里假设为int */
typedef int ElementType;
// 顺序表结构
typedef struct {
ElementType *data;
int length;
}seqList;
顺序表的操作实现
初始化
表的结构完成了, 在使用表之前首先需要进行表的初始化, 也就是生成一个空的顺序表:
int initList(SeqList *L) {
// 为顺序表开辟一个大小为 100 的空间
L->data = malloc(sizeof(ElemType *) * 100);
// 非空判断, 如果开辟失败直接退出程序, 注意这里用的是 exit()
if (!L->data) {
exit(0);
}
// 空表赋值长度为 0
L->length = 0;
return YES;
}
关于 exit( ) 和 return 的区别 ?
插入
插入时需要注意顺序表是插入的位置判断, 是否超过顺序表存储长度, 以及插入完成以后对顺序表长度进行 +1
操作。
算法步骤如下:
- 首先声明函数需要三个参数: 待插入数据的顺序表, 插入位置, 插入的数据元素
- 判断插入位置是否合法, 即 位置是否是从 1(或者0) 开始
- 判断顺序表是否已满
- 判断插入的位置是否在表尾, 如果不在表尾需要对之后的数据做移位操作(所以我们这里是采用的倒序遍历, 将插入数据后面的数据做后移一位)
- 将需要插入的元素放到想要插入的位置
- 顺序表长度
+1
// 插入函数
int ListInsert(SeqList *L, int i, ElementType a) {
// 判断 i 值是否合法 (当为 length + 1 时说明是插在表尾)
if (i < 1 || i > L->length + 1) return ERROR;
// 判断存储空间是否已满
if (L->length > MAXSIZE) return ERROR;
// 判断插入位置是否在表尾
if (i <= L->length) {
for (int j = L->length; j >= i; j--) {
// 插入位置之后的位置做后移
L->data[j+1] = L->data[j];
}
}
// 插入数据
L->data[i] = a;
// 长度 +1
++L->length;
return OK;
}
取值
取值操作与插入操作一样需要3个参数, 操作的顺序表, 要取值的位置 和 要取值的接收者
算法步骤如下:
- 首先判断所要取的位置是否合法
- 根据传入的位置取值, 并将取出的值交给赋给接收者
// 取值
int getElem (SeqList *L, int i, ElementType *e) {
// 判断 i 值是否有效
if (i < 1 || i > L->length) return ERROR;
// 取值, 然后赋给接收者
*e = L->data[i];
return OK;
}
// 循环打印
int printSeqList (SeqList *L) {
for (int i = 1; i <= L->length; i++) {
printf("%d\n", L->data[i]);
}
return OK;
}
删除
删除操作因为是要做减操作, 所以首先需要判断顺序表是否为空, 然后判断传入的位置是否合法, 最后在进行完删除以后还要讲 length
做 -1
操作
算法步骤如下:
- 首先判断顺序表的长度是否为 0
- 判断想要删除的位置是否有效
- 循环遍历, 将想要删除的位置之后的元素向前移动 (注意: 这里可以看到如果删除的位置是最后一个是不会进循环的, 只需要进行第4步就可以了)
- 顺序表长度
-1
// 删除
int deletElem (SeqList *L, int i) {
// 判断长度是否为0
if (!L->length) return ERROR;
// 判断删除位置是否有效
if (i < 1 || i > L->length) return ERROR;
// 将删除位置 i 之后的元素都往前移动
for (int j = i; j < L->length; j++) {
L->data[j] = L->data[j+1];
}
// 表的长度 -1
--L->length;
return OK;
}
调用
int main(int argc, const char * argv[]) {
// insert code here...
SeqList L;
// 初始化
initList(&L);
// 插入
ListInsert(&L, 1, 10);
ListInsert(&L, 2, 15);
ListInsert(&L, 3, 20);
ListInsert(&L, 4, 35);
ListInsert(&L, 5, 50);
ListInsert(&L, 6, 15);
printSeqList(&L);
printf("----------------------\n");
// 查找
ElementType e;
getElem(&L, 4, &e);
printf("e: %d\n", e);
printf("----------------------\n");
// 删除
deletElem(&L, 4);
printSeqList(&L);
printf("end!!!\n");
return 0;
}
// 打印结果:
10
15
20
35
50
15
----------------------
e: 35
----------------------
10
15
20
50
15
end!!!
Program ended with exit code: 0
总结
以上就是本次的全部内容, 主要是是研究 顺序表 的定义和一些基本操作的实现。然后对 逻辑结构 和 物理结构 也有了进一步的认识, 后续内容将继续研究不同结构下的各种数据结构。感谢大家, 希望本次内容能让你有所收获。
和谐学习, 不急不躁!