一、线性结构
线性结构是一种逻辑结构。
特点:一个出度 + 一个入度(一个接一个排列)
1-1、线性表
最简单,最基本的线性结构。
定义:n(n >= 0)个元素的有限序列。
特点:
线性表的存储结构:
- 顺序存储
- 链式存储
基本操作:插入、删除、查找。
1-1-1、线性表的顺序存储
一组地址连续的存储单元。如:数组。
逻辑上相邻的两个元素,物理位置上也相邻。
- 优点:随机存取;O(1)
- 缺点:插入、删除需要移动元素。
表长为n的线性表中插入元素,等概率下,插入一个新元素需要移动的元素个数的数学期望:
E = n/2
表长为n的线性表中删除元素,等概率下,删除一个新元素需要移动的元素个数的数学期望:
E = (n-1)/2
1、插入操作的代码实现
在数组的第K个位置,插入元素x
时间复杂度:
2、删除操作的代码实现
删除数组第K个位置的元素
时间复杂度:
3、查找操作的代码实现
查找数组中第K个位置的元素
1-1-2、线性表的链式存储
通过指针链接起来的节点存储元素
优点:
支持动态内存分配,结点空间需要的时候才申请,无需实现分配。
支持高效的插入、删除操作,时间复杂度为O(1)。
缺点:
不支持随机访问,需要从头节点开始遍历整个链表才能访问任意位置的元素。
特点:
数据元素,逻辑上连续,物理上分开。
单链表 = 线性链表
单链表分为:
- 带头结点的单链表;
- 不带头结点的单链表
无论是否有头结点,头指针始终指向链表的第一个结点!!!
1、单链表的定义
或者:
2、单链表的插入操作
在链表list的位置K处,插入一个节点(K = 1,2,......list.length+1)
2-1、不带头结点
①不带头结点的链表,在表头插入结点
x->next = head;
head = x;
②不带头结点的链表,在表头以外的地方插入结点
x->next = p->next;
p->next = x;
P指向k-1的位置
2-2、带头结点(推荐)
头结点:(可以看做第0个节点)
在单链表的第一个结点之前附加一个结点,称为头结点。
头结点的Data域可以不设任何信息,也可以记录表长等相关信息。
①带头结点的链表,在任意位置插入结点(包括第一个位置)
x->next = p->next;
p->next = x;
【对比小结】:
这个时候你会发现,不带头结点的链表的两种情况操作步骤不一样;而带头结点的两种情况操作步骤是一样的。
所以,带头节点的单链表的插入操作更加方便!!!
2-3、时间复杂度
代表头/不带表头的单链表,插入的时间复杂度:
- 最好:O(1)——表头
- 最坏:O(n)——表尾
- 平均:O(n)
3、单链表的删除操作
删除第K个位置的节点
重点:找到k-1位置的节点,将k-1位置的节点指向k+1位置的节点
3-1、不带头结点
①删除第一个节点(k=1)
list = list.next;
②删除除了第一个节点以外的节点
Node s = p.next;
p.next = s.next;
3-2、带头结点
Node s = p.next;
p.next = s.next;
3-3、时间复杂度
带头结点/不带头结点的单链表,删除操作的时间复杂度
4、单链表的查找操作
4-1、不带头结点
代码和带头结点的查询操作一样!!!
4-2、带头结点
4-3、时间复杂度
带头结点/不带头结点的单链表,查询操作的时间复杂度
5、循环单链表
特点:
可以从表中任意节点开始,遍历整个链表。
5-1、初始化
head.next = head;
5-2、插入操作
尾指针,好处:
可以直接通过尾指针,找到头结点。
在末尾(n+1)插入节点的时候,没有尾指针,则时间复杂度为O(n),若是有尾指针,则时间复杂度为O(1)。
但是在第n个位置插入节点时,还是时间复杂度为O(n)
6、双链表
特点:
可以从表中任意的节点出发,从两个方向上,遍历链表。
6-1、插入操作
插入到非第一个和非最后一个位置的节点。
s.front = p.front;
p.front.next = s;
s.next = p;
p.front = s;
6-2、删除节点
删除到非第一个和非最后一个位置的节点。
p.next = s.next;
s.next.front = p;
二、真题
真题1:
单链表的查找时间复杂度(平均):O(n)
真题2:
删除第K个节点,要先找到第k-1个节点。 (前一个节点)
真题3:
真题4:
顺序表:
单链表:
- 查找:O(n)
- 删除:O(n)
- 插入:O(n)
真题5:
真题6:
真题7:
真题8: