一.链表
线性数据结构:
- 动态数组
- 栈
- 队列
以上三种数据结构的底层均是依托于静态数组,解决自动变容问题靠的是reSize操作。
链表的认识:
- 是线性数据结构
- 是一种真正的动态数据结构,同时也是最简单的动态数据结构
- 更深入的理解引用(指针)
- 更深入的理解递归
- 辅助组成其他数据结构
链表(Linked List):
- 数据存储在 “节点”(Node)中
class Node{
E e;
Node next;
}
- 优点:是真正的动态存储,不需要处理固定容量的问题 ,需要存储多少个数据就生成多少个节点
- 缺点:丧失了数组的随机访问的能力
数组和链表的对比:
- 数组最好用于索引有语义的情况,数组最大的优点就是支持快速查询
- 链表不适合用于索引有语义的情况,链表最大的优点就是动态的存储
链表的添加操作:
- 链表不同于数组,在链表中,最容易添加的位置是头部,直接将新节点的指针指向头结点,然后更新头结点为新添加的节点
- 在链表中间添加元素的过程:先找到添加位置的前一个节点prev,然后经过两步操作(不可调换位置)完成,(头部节点是没有前一个节点的,需特殊处理)
为链表设立虚拟头结点:
- 由于链表中的头结点相比于其他节点有些特殊,即头结点没有前缀节点,故在实际操作中头结点经常需要特殊考虑。为了解决头结点的特殊性,链表实现中一个很常用的技巧就是为链表添加一个虚拟头结点(dummyHead)。
- 虚拟头结点不存储数据,但需要指向一个真正的头结点(真正的头结点在初始化时可以为null,即 dummyHead = new Node(null, null) ),换句话说就是即使对于一个空链表来说,它也是存在一个节点的,这个节点就是 dummyHead = new Node(null, null) 。
- 虚拟头结点仅仅是为了程序编写的方便。
链表中的删除操作:
链表的查找和遍历操作:
链表的遍历需要通过新建引用(指针)的方式实现,指针的后移操作为 cur=cur.next
//for循环遍历整个链表
for(Node cur = dummyHead.next; cur != null; cur=cur.next){
//do something
}
//while循环遍历整个链表
Node cur = dummyHead.next;
while(cur != null){
//do something
//循环退出条件
cur = cur.next;
}
//查找链表中索引为index(索引从0开始)的元素
Node cur = dummyHead.next;
for(int i=0; i<index; i++){
cur = cur.next;
}
链表的时间复杂度分析:
由于链表的增删改查操作都需要遍历查找过程,所以它们的时间复杂度均为O(n), 但是,对表头的增删查操作,其时间复杂度为O(1)。
基于以上特点,可以将链表头作为栈顶来实现一个栈。
链表的简单改进:带有尾指针的链表,可以用于实现队列
- 在头结点增加和删除元素都容易,时间复杂度均为O(1)
- 在尾节点添加节点容易,时间复杂度为O(1),但删除节点不容易(因为指针是单向的,删除尾节点需要从头节点遍历找到尾节点的前一个元素)
- 基于以上特点,带有尾指针的链表,可以用于实现队列
链表的进一步改进:双链表
在头结点和尾节点的增加和删除元素时,时间复杂度均为O(1)
链表的进一步改进:循环双向链表
将双链表添加虚拟头结点,并将指向null的尾节点改为指向虚拟头结点
数组链表:
在数组中不仅仅只存储一个值,还要存储一个指向下一个空间的索引(数组下标),在明确知道要处理的元素有多少个时,可以考虑使用这种链表。
二.链表与递归
递归:
- 本质上,是将原来的问题转化为更小的同一问题
//递归求和 计算arr[l, n)这个区间内所有数字的和
private static int sum(int[] arr, int rangeL){
if(rangeL == arr.length){
return 0;
}
return arr[rangeL]+sum(arr, rangeL+1);
}
编写递归程序的基本原则:
所有的递归程序都可以分为两部分:
- 求解最基本问题,这个最基本问题是不能自动解决的,需要通过编写程序解决,但这个最基本问题往往十分简单;
- 把原问题转化为更小的问题,这部分是递归的核心,即需要根据最小问题的解构造出原问题的解
编写递归程序的经验 (宏观解读) :
- 注意递归函数的“宏观”语义;(如:计算一个数组内一段区间的和)
- 递归函数就是一个函数,用来完成完成一个功能,函数A里调用函数B与函数A里调用函数A本质上是一样的,可以把递归函数想成一个普通的子函数,这个子函数会完成一定的功能(宏观语义)。
链表天然的递归性质:
我们可以把链表看作是一个一个连接的节点,换个思路,也可以看作是一个节点后面挂载了一个更短的链表,直到最后null也是一个链表。对于链表中的许多操作都可以利用递归的思路来完成。
利用递归解决链表中删除元素的问题(leetcode 203) https://blog.csdn.net/jt102605/article/details/83926604
递归函数的“微观解读”:
- 递归函数的调用本质就是函数的调用,只不过调用的函数是自己而已。
- 递归调用是有代价的:函数调用+系统栈空间
1>. 数组求和的微观过程:
2>. 链表删除元素的微观过程: