线性表的基本概念
从今天开始我们正式进入数据结构的学习,开始之前我们先思考几个问题。
- 生活中遇到哪些线性表的例子?
- 如何通过程序实现线性表?
- 线性表结构有哪些基本操作?
- 线性表结构在软件方面有哪些应用?
看着上面的表情包是不是有种熟悉的味道,没错,这就是我们生活中最常见的线性表结构——糖葫芦。根据它的结构特性我们给出相应的定义,一个线性表,由有限个具有相同性质的元素构成,结构上要求,非表头和表尾元素有且只有一个前驱和后继。对于表头和表尾元素,如果是没有环的线性表,则表头元素没有前驱,表尾元素没有后继。如果有环则均存在前驱和后继元素,如下图所示。
线性表的实现
线性表的实现,根据表元素存储方式的不同,可以分为两种。一种是线性表中的所有元素占用一片连续的存储空间,我们称之为顺序存储,一种是表中的所有元素不需要占据连续的空间,当表中插入新的元素的时候,额外申请元素所需要的空间,插入到线性表中即可,我们称之为链式存储,下面我们通过程序来依次实现以上两种存储方式。
- 顺序表的实现(假设顺序表起始地址为b,每个元素占据内存为l)
根据上图所示,显然我们可以使用数组来表示一个顺序表结构,具体如下所示:
// 使用数组来表示顺序表结构
#define MAXSIZE 100 // 顺序表最大长度
typedef int ElemType
typedef struct {
ElemType data[MAXSIZE];
int len // 顺序表长度
}SqList;
使用数组来表示顺序存储结构有一个问题,就是在程序的执行过程中其能表示的元素的个数最大值是不可修改的,如果顺序表元素较少,则存在存储空间的浪费,如果顺序表元素比较多,则有可能导致顺序表的存储空间不够。基于这个问题,我们可以使用动态分配来实现顺序表结构,当内存空间不够用的时候可以重新分配一块更大的内存,并把当前数据拷贝到新的内存当中,具体操作请看后面的基本操作。
// 通过动态分配内存来实现顺序表结构
#define INIT_SIZE 100
#define INCREMENT 20
typedef int ElemType
typedef struct {
ElemType *data;
int len; // 顺序表长
int cap; // 顺序表最大容量
}SqList;
- 链式表(链表)实现
链式表相比于
顺序表,不要求整个表分配在一块连续的存储空间,这对于占用内存比较大的数据来说比较方便,具体实现如下所示。
// 使用链表实现线性表结构
typedef int ElemType
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode, *LinkList;
假设 L 是 LinkList 类型的变量,为了操作方便我们通常使用该变量的指针域作为链表的第一个节点,L 称之为链表的头节点,对链表判空直接看 L->next == NULL ? empty : nonempty 即可。
线性表的基本操作
- 数组实现的顺序表
// 初始化、插入、更新、删除操作
#define MAXSIZE 100 // 顺序表最大长度
typedef int ElemType;
typedef struct {
ElemType data[MAXSIZE];
int len // 顺序表长度
}SqList;
bool init_sq_1(SqList *sq) {
if (NULL == sq) {
return false;
}
sq->len = 0;
return true;
} // init_sq_1
bool insert_sq_1(SqList *sq, int pos, ElemType data) {
// 判断顺序表是否为空,顺序表是否已满,pos是否合法
if (sq == NULL || pos < 0 || pos > sq->len || sq->len == MAXSIZE) {
return false;
}
int i = sq->len;
for ( ; i > pos; i--) {
sq->data[i] = sq->data[i-1];
}
sq->data[pos] = data;
sq->len++;
return true;
}