链表基础
我画了20张图,终于让女朋友学会了翻转链表-码海
该文章介绍了链表的基础知识,
- 包括链表的定义和表示
- 为何要设置哨兵节点(即头结点)
- 头插法和尾插法
- 需要注意的是,头插法插入完毕之后数据的顺序和输入的顺序是相反的
- 程序局部性原理(重点关注)
//尾插法
public class LinkedList {
int length = 0; // 链表长度,非必须,可不加
Node head = new Node(0); // 哨兵结点
public void addNode(int val) {
Node tmp = head;
while (tmp.next != null) {
tmp = tmp.next;//从头节点开始顺序向后寻找,直到找到尾结点
}
tmp.next = new Node(val);//尾部插入
}
//头插法,公众号链接有动图
public class LinkedList {
int length = 0; // 链表长度,非必须,可不加
Node head = new Node(0); // 哨兵节点
// 头插法
public void headInsert(int val) {
// 1.构造新结点
Node newNode = new Node(val);
// 2.新结点指向头结点之后的结点
newNode.next = head.next;
// 3.头结点指向新结点
head.next = newNode;
}
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
int[] arr = {1,2,3,4};
// 头插法构造链表
for (int i = 0; i < arr.length; i++) {
linkedList.headInsert(arr[i]);
}
// 打印链表,将打印 4-->3-->2-->1
linkedList.printList();
}
}
链表翻转
递归方式翻转链表
/**
* 递归翻转结点 node 开始的链表
*/
public Node invertLinkedList(Node node) {
if (node.next == null) {
return node;
}
// 步骤 1: 先翻转 node 之后的链表
Node newHead = invertLinkedList(node.next);
// 步骤 2: 翻转该节点,node 的后继节点设置为空的目的是防止形成环
//e.g.对于当前node为倒数第二个节点时,将下一个节点(即最后一个节点)的后继改为当前节点,但此时有环,所以还要将当前节点的后继
node.next.next = node;
node.next = null;
// 步骤 3: 返回翻转后的头结点,在递归结束时返回的是第二个节点
return newHead;
}
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
int[] arr = {4,3,2,1};
for (int i = 0; i < arr.length; i++) {
linkedList.addNode(arr[i]);
}
Node newHead = linkedList.invertLinkedList(linkedList.head.next);
// 翻转后别忘了设置头结点的后继结点!
linkedList.head.next = newHead;
linkedList.printList(); // 打印 1,2,3,4
}
复杂度:由于递归调用了 n 次 invertLinkedList 函数,所以时间复杂度显然是 O(n)
迭代方式翻转链表
复杂度:用迭代的思路来做由于循环了 n 次,显然时间复杂度为 O(n),另外由于没有额外的空间使用,也未像递归那样调用递归函数不断压栈,所以空间复杂度是 O(1),对比递归,显然应该使用迭代的方式来处理
/**
* 迭代翻转
*/
public void iterationInvertLinkedList() {
// 步骤 1
Node pre = head.next;
Node cur = pre.next;
pre.next = null; // pre 是头结点,避免翻转链表后形成环
while (cur != null) {
/**
* 务必注意:在 cur 指向 pre 之前一定要先保留 cur 的后继结点,不然 cur 指向 pre 后就再也找不到后继结点了
* 也就无法对 cur 后继之后的结点进行翻转了
*/
Node next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
// 此时 pre 为头结点的后继结点
head.next = pre;
}