逻辑结构
相同数据类型的n个数据元素的有限序列。
n为表长,n=0时是空表。
除第一个元素外,每个元素都有一个直接前驱。
除最后一个元素外,每个元素都有一个直接后继。
线性表特点
- 元素个数有限
- 逻辑上有序
- 元素都是数据元素(即单个元素)
- 元素数据类型相同(意味着每个元素占有的存储空间相同)
顺序表和链表是存储结构。
线性表是逻辑结构。
两者是不同层面。
物理结构
顺序存储结构
顺序表
顺序表,用一组地址连续的存储单元依次存储线性表中的数据元素。
通常使用数组来描述顺序表。
- 静态分配:指定长度,超出就会溢出。
- 动态分配:超出长度后,自动开辟一块新的区域,复制过去。
动态分配不是链式存储,仍是顺序存储。
特点
顺序表的特点就是逻辑顺序与物理顺序相同、任一数据元素都可以随机存取。
存储密度高,结点只存储数据元素。
增删都需要移动大量结点。
操作复杂度
- 查找
- 按值:O(n)
- 按下标:O(1)
- 插入:O(n)
- 删除:O(n)
链式存储结构
单链表
不需要连续的地址空间。
通常使用头指针来标识一个单链表,为了操作上的方便,有时头指针指向的是头结点。
引入头结点后带来的好处
- 链表第一个元素的操作与其他元素的操作别无二致
- 空表和非空表的处理也得到了统一
操作复杂度
- 头插法建立单链表:O(n)
- 尾插法建立单链表:O(n)
头插法读入数据的顺序与单链表中顺序是反的
- 查找
- 按值:O(n)
- 按下标:O(n)
- 插入
- 给定结点后插:O(1)
- 给定节点前插:如果不是尾结点,O(1)
- 删除
- 按下标删除:O(n)
- 删除给定结点:如果不是尾结点,O(1)
- 求表长:O(n)
双链表
对于单链表,访问给定结点的前驱和后继分别需要O(n)和O(1)
对于双链表则都是O(1)
操作复杂度
- 插入
- 给定结点:O(1)
- 删除
- 给定结点:O(1)
循环链表
循环链表与单链表的差别:尾结点的指针域指向的不是NULL,而是头结点。
若要遍历整个链表,单链表只能从表头开始遍历,而循环链表可以从任意结点开始。
若常在表头和表尾进行操作,则不设头指针,仅设尾指针效率更高。因为如果在表尾操作,则需要遍历整个链表;而通过尾指针可以直接操作表尾,当然也可以直接操作表头。
循环双链表
除了尾结点的后继指针指向头结点;
头结点的前驱指针也要指向尾结点;
当循环链表为空表时,头结点的前驱和后继指针都指向自己。
静态链表
借助数组来实现,结点同样由data域和指针域构成,不过指针域的值不是地址,而是后继结点的数组下表(游标)。
与顺序表一样,静态链表也需要预先分配一块连续的存储空间(数组用)。
next == -1 意为尾结点。
顺序存储 vs 链式存储
- 存取方式
顺序表可以随机存取;只需一次访问;
链表只能遍历存取;需要n次访问;
- 与逻辑结构的对应关系
顺序表逻辑上相邻的元素,物理上也相邻;
链表逻辑上相邻的元素,物理上不一定相邻;
- 插入、删除与查找
- 按值查找
- 链表与顺序表都是_O(n)(顺序表有序时,可以优化到_O(logn))
- 按下标查找
- 链表_O(n),顺序表_O(1)
- 插入删除
- 按下标,都是_O(n)_;链表是因为要遍历得到目的结点,顺序表是因为移动结点
- 给定结点,链表是_O(1),顺序表是_O(n)
- 按值查找
- 空间分配
静态存储分配时(顺序表——数组,链表——数组),需要预先了解元素个数,否则会浪费或溢出。
动态存储分配时,虽然灵活,但是溢出时需要移动拷贝所有元素。
使用时的考虑
- 基于存储:顺序存储肯定存储密度大,链式存储密度一定小于1。
- 基于运算:查找如果按下标顺序表优,如果按值都一样;插入删除时,虽然都是_O(n),但链表是为了找目的结点,比顺序表移动元素要好一些。_
- 基于环境:顺序表易实现。