目录
一、线性表的定义、基本操作
1.1 线性表相关定义
(1)基本概念:
线性表是具有相同数据类型的n(n > 0)个数据元素的有限序列,其中n为表长,当n = 0 时线性表是一个空表。
线性表中有唯一的“第一个”数据元素(又称表头元素)以及唯一的“最后一个”数据元素(又称表尾元素)。除第一个元素外,每个元素有且仅有一个直接前驱。除最后一个元素外,每个元素 有且仅有一个直接后继。以上就是线性表的逻辑特性。
图1.1-1 经典线性表实例(顺序表实现)
图1.1-2 经典线性表实例(循环链表实现)
(2)相关特性:
线性表的特点如下:
•表中元素的个数有限。(存在唯一一个被称为第一个和最后一个的元素)
•表中元素具有逻辑上的顺序性。(有先后顺序)
•表中元素都是数据元素,每个元素都是单个元素。
•表中元素的数据类型都相同,这意味着每个元素占有相同大小的存储空间。(同构)
•表中元素具有抽象性,即仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容。
简单来说就是:有限、序列、同构
注意:线性表是一种逻辑结构,表示元素之间一对一的协构,两者属于不同层面的概念。
1.2 线性表的基本操作
线性表的主要操作如下(在最后会有部分相关实现):
InitList( &L ); 初始化线性表,构造空的线性表L
DestroyList( &L ); 销毁已有线性表L。
ListEmpty( L ); 判断线性表L是否为空,返回true/false。
ListLength( L ); 求线性表L中的元素个数。
ClearList( &L ); 将已有线性表L置为空表。
GetElem( L, i, &e ); 用e返回线性表L中第i个元素值。
LocateElem( L, e ); 在线性表L中查找元素e的位置。
PriorElem( L, cur_e, &pre_e ); 用pre_e返回线性表L中元素cur_e的前驱。
NextElem( L, cur_e, &next_e ); 用next_e返回线性表L中元素cur_e的后继。
ListInsert( &L, i, e ); 在线性表L第i个元素之前插入e。
ListDelete(&L, i, &e); 删除线性表L中第i个元素
PutElem( &L, i, &e ); 将线性表L中第i个元素赋值为e
二、线性表的顺序表示
2.1 顺序表相关定义
(1)基本概念
线性表的顺序存储又称顺序表。它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。特点是表中元素的逻辑顺序与其物理顺序相同。(数组就是一种典型的顺序表)
线性表中的任一数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的存储结构。
注意:线性表中元素的位序是从1开始的,而数组中元素的下标是从0开始的。线性表是第1个元素存储在线性表的起始位置, 第i个元素的存储位置后面紧接着存储的是第i+1个元素,第i个元素的位序是i。
2.2 顺序表C语言简单实现
1.相关定义实现
假定线性表的元素类型为ElemType,则线性表的顺序存储类型描述为
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* num;//存放数据
int capacity;//记录容量
int size;//记录存储数据的个数
}SL;
一维数组可以是静态分配的,也可以是动态分配的。在静态分配时,由于数组的大小和空间 事先已经固定,一旦空间占满,再加入新的数据就会产生溢出,进而导致程序崩溃。
而在动态分配时,存储数组的空间是在程序执行过程中通过动态存储分配语句分配的,一旦数据空间占满,就另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储数组空间的目的,而不需要为线性表一次性地划分所有空间。
2.顺序表的初始化、打印、扩容
//顺序表的初始化
//顺序表的初始化 相对简单,我们定义了有一个顺序表的指针、 容量、数据的个数
void SeqListInit(SL* pc)
{
pc->num = NULL;
pc->capacity = pc->size = 0;
}
//顺序表的打印
void SeqListPrint(SL* pc)
{
assert(pc);
for (int i = 0; i < pc->size; ++i)
{
printf("%d ", pc->num[i]);
}
printf("\n");
}
//扩容函数
//顺序表在存储数据时,需要空间的使用,我们这里采用的是动态存储,
//我们要保证每次存放数据时顺序表的容量是足够的,就需要动态开辟空间
void SeqListCheckCapacity(SL* pc)
{
if (pc->size == pc->capacity)
{
//当我们第一次插入数据时,顺序表的容量和数据个数都是0,
//可以采用三目操作符,先给它一块空间
int newcapacity = pc->size == 0 ? 4 : pc->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(pc->num, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
pc->num = tmp;
pc->capacity = newcapacity;
}
}
注意:动态分配并不是链式存储,它同样属于顺序存储结构,物理结构没有变化,依然是随 机存取方式,只是分配的空间大小可以在运行时动态决定。
补充:顺序表为了避免频繁扩容,一般扩容到原本的两倍。
3.顺序表的查找
图2.1-1 顺序表查找相关流程
//查找函数
int SeqListFind(SL* pc, SLDataType x)
{
assert(pc);
for (int i = 0; i < pc->size; ++i)
{
if (pc->num[i] == x)
{
return i;
}
}
return -1;
}
4.顺序表的插入
图2.1-2 顺序表查找相关流程
//顺序表在pos位置插入指定x
void SeqListInsert(SL* pc, int pos, SLDataType x)
{
assert(pc);
assert(pos >= 0 && pos <= pc->size);
SeqListCheckCapacity(pc);//检查是否需要扩容
int end = pc->size - 1;
while (end >= pos)
{
pc->num[end + 1] = pc->num[end];
end--;
}
pc->num[pos] = x;
pc->size++;
}
5.顺序表的删除
图2.1-3 顺序表删除相关流程
//顺序表删除pos位置的值
void SeqListErase(SL* pc, int pos)
{
assert(pc);
assert(pos >= 0 && pos < pc->size);
int begin = pos + 1;
while (begin < pc->size)
{
pc->num[begin - 1] = pc->num[begin];
begin++;
}
pc->size--;
}
2.3 顺序表的优缺点
2.3.1 优点:
1.可随机存取和访问任一元素
顺序表最主要的特点是随机访问,即通过首地址和元素序号可在时间0(1)内找到指定的元素。
2.存储空间使用紧凑
顺序表的存储密度高,每个结点只存储数据元素。
3.逻辑相邻,物理相邻
顺序表逻辑上相邻的元素物理上也相邻。
2.3.2 缺点:
1.要求数据从开始位置储存,插入、删除操作需要移动大量的元素,效率较低。
2.空间不足时需要扩容,异地扩容时代价过高。(不了解异地扩容可以去了解一下malloc函数的工作方式)