数据结构~01.线性表的基本实现和概念
线性表的定义:
线性表是具有相同特征性数据元素的一个有限序列。该序列中所含元素的个数叫做线性表的长度。一般用n(n >= 0)来表示。当n =
0时,线性表为空表。
线性表的存储结构:
线性表的存储结构有顺序存储结构和链式存储结构,顺序存储结构被称为顺序表。链式存储结构被称为链表。
顺序表和链表的比较
a为顺序表,b为链表
顺序表
在顺序表中7个元素是紧挨着的,即连续地占用了一片空间。如果要对已有的元素进行新增或者修改,并不会使之变大,也不会减小。
顺序表要求占用连续的存储空间,存储分配只能预先进行。一旦分配好了,以后不管怎么操作都始终不变。
在图中我们看到,顺序表中最右边有一个表结点空间是没有被利用的。假如说我要在中间插入元素,比如说B和C之间要插入一个新元素,那么C和后面的元素就要集体往后移一个位置,而链表的话呢,就不需要这么麻烦。
顺序表的结构体定义
#define MAXSIZE 10 //顺序表的初始大小 typedef struct { int data[MAXSIZE]; //存放顺序表元素的数组
int length; //存放顺序表的长度 }List; //顺序表类型的定义
链表
链表的结点可以散落在内存中的任意位置,且不需要一次性划分所有结点所需的空间给链表,而是根据需求临时划分。因此,链表支持存储空间的动态分配。
链表中的每一个结点需要划出一部分空间来存储指向下一个结点位置的指针。链表当前结点的位置,是由前驱节点中的地址所指示的,而不是由其相对于初始位置的偏移量来确定。
链表的插入操作无需移动多个元素,只需要更改箭头指示即可。
单链表
每个结点中除了包含数据域外,还包含一个指针域。用于指向后续结点。
图为带头结点的单链表
带头结点的单链表中,头指针head指向头节点。头节点的值域不含任何信息。从头节点的后继结点开始存储。头指针head始终不等于NULL。当head->next等于NULL的时候,链表为空。
单链表结点的定义
typedef struct node { int data; //data中存放数据域 struct node* next; //指向后继结点的指针 }
Node;
双链表
在单链表中只能从头走到尾,而不能从尾反向走到头。如果要求从终端结点走到开始结点的数据序列,则对于单链表的操作就非常麻烦。因此,我们就有了双链表。
双链表结点的定义
typedef struct node { int data; //data中存放数据域 struct node* pre; //指向前驱结点的指针
struct node* next; //指向后继结点的指针 }Node;
单链表和双链表结点结构图
a为单链表,b为双链表
最后总结
存储方式
顺序表采用一组地址连续的存储单元依次存放数据元素,通过元素之间的先后顺序来确定元素 之间的位置,因此存储空间的利用率较高。
单链表采用一组地址任意的存储单元来存放数据元素,通过存储下一个节点的地址值来确定节 点之间的位置,因此存储空间的利用率较低。
时间性能比较
顺序表查找的时间复杂度为 O(1),插入和删除需要移动元素,因此时间复杂度为 O(n)。若是需
要频繁的执行查找操作,但是很少进行插入和删除操作,那么建议使用顺序表。
链表查找的时间复杂度为 O(n),插入和删除无需移动元素,因此时间复杂度为 O(1)。若是需
要频繁的执行插入和删除操作,但是很少进行查找操作,那么建议使用链表。
根据序号来插入和删除节点,需要通过序号来找到插入和删除节点的位置,那么整体的 时间复杂度为
O(n)。因此,链表适合数据量较小时的插入和删除操作,如果存储的数据量较大, 那么就建议使用二叉树或者其他数据结构实现。
空间性能比较
顺序表需要预先分配一定长度的存储空间,如果事先不知道需要存储元素的个数,分配空间过
大就会造成存储空间的浪费,分配空间过小则需要执行耗时的扩容操作。
单链表不需要固定长度的存储空间,可根据需求来进行临时分配,只要有内存足够就可以分配, 在链表中存储元素的个数是没有限制的,无需考虑扩容操作。