算法通关村第一关——链表青铜挑战笔记
1. 单链表
1.1 创建链表
在JVM中,栈区存放的是引用(这里我理解为指针),堆区存放的才是创建的对象。
使用Java创建链表结点的方式有两种,一种是根据面向对象思想去创建链表结点,还有一种则是违背面向对象思想,采用一种更精简的方式去创建链表结点。
根据面向对象思想去创建链表结点:
public class LinkNode {
private int data;
private LinkNode next;
// 构造方法
public LinkNode(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public LinkNode getNext() {
return next;
}
public void setNext(LinkNode next) {
this.next = next;
}
}
违背面向对象思想,采用一种更精简的方式去创建链表结点:
public class LinkNode {
public int val;
public LinkNode next;
LinkNode(int x) {
val = x;
next = null; // 可以不写,写上更规范
}
}
1.2 遍历链表
对于单链表,一定是从头开始逐个向后访问,所以操作之后一定要确保还能找到表头。
/**
* 获取链表长度
* @param head 头结点
* @return 链表长度
*/
public static int getListLength(Node head) {
int length = 0;
Node node = head;
while (node != null) {
length++;
node = head.next;
}
return length;
}
1.3 链表插入
单链表的插入需要考虑到三种情况:首部、中部、尾部。
(1)在链表的首部插入
需要注意从首部插入后要将head指向新插入的结点。
(2)在链表的中部插入
需要注意的是遍历时要在插入的前一个位置停下。
(3)在链表的尾部插入
只需要将尾结点指向新的结点就可以了。
(4)编码
/**
* 链表插入
* @param head 头结点
* @param nodeInsert 要插入的结点
* @param position 要插入的位置
* @return 新链表的头结点
*/
public static Node insertNode(Node head, Node nodeInsert, int position) {
// 1. 判断当前链表是否是一个空链表
if (head == null) {
return nodeInsert;
}
// 2. 判断要插入的位置是否越界
int size = getListLength(head);
if (position > size + 1 || position < 1) {
System.out.println("位置参数越界");
return head;
}
// 3. 插入结点
// 当插入位置是头部的情况时
if (position == 1) {
nodeInsert.next = head;
// 这里可以直接写 return nodeInsert; 也可以像下面这么写
head = nodeInsert;
return head;
}
Node pNode = head;
int count = 1;
while (count < position - 1) {
pNode = pNode.next;
count++;
}
nodeInsert.next = pNode.next;
pNode.next = nodeInsert;
return head;
}
1.4 链表删除
同样要考虑到三种情况:首部、中部、尾部。
(1)删除表头结点
只要将head向前移动一个就行,JVM会自动回收到不可达的结点。
(2)删除最后一个结点
(3)删除中间的结点
(4)编码
/**
* 链表删除
* @param head 头结点
* @param position 要删除结点的位置
* @return 新链表的头结点
*/
public static Node deleteNode(Node head, int position) {
// 1. 判断当前链表是否是一个空链表
if (head == null) {
return null;
}
// 2. 判断要删除的位置是否越界
int size = getListLength(head);
if (position > size || position < 1) {
System.out.println("位置越界");
return head;
}
// 3. 删除结点
if (position == 1) {
return head.next;
} else {
Node pNode = head;
int count = 1;
while (count < position - 1) {
pNode = pNode.next;
count++;
}
Node curNode = pNode.next;
pNode.next = curNode.next;
}
return head;
}
2. 双向链表
2.1 基本概念
2.2 创建、遍历链表
public class DoubleLink {
// 双向链表的结构
private DoubleNode first;
private DoubleNode last;
public DoubleLink() {
first = null;
last = first;
}
/**
* 定义结点类
*/
public static class DoubleNode {
public int data;
public DoubleNode next;
public DoubleNode prev;
public DoubleNode(int data) {
this.data = data;
}
// 打印结点的数据域
public void displayNode() {
System.out.println("{" + data + "}");
}
}
/**
* 从头部遍历打印
*/
public void displayForward() {
System.out.println("List(first---->last): ");
DoubleNode current = first;
while (current != null) {
current.displayNode();
current = current.next;
}
System.out.println();
}
/**
* 从尾部遍历打印
*/
public void displayBackward() {
System.out.println("List(last---->first): ");
DoubleNode current = last;
while (current != null) {
current.displayNode();
current = current.prev;
}
System.out.println();
}
}
2.3 链表插入
首尾插入要比中间插入简单,中间插入的图像为:
public class DoubleLink {
// 双向链表的结构
private DoubleNode first;
private DoubleNode last;
public DoubleLink() {
first = null;
last = first;
}
/**
* 定义结点类
*/
public static class DoubleNode {
public int data;
public DoubleNode next;
public DoubleNode prev;
public DoubleNode(int data) {
this.data = data;
}
// 打印结点的数据域
public void displayNode() {
System.out.println("{" + data + "}");
}
}
/**
* 从头部遍历打印
*/
public void displayForward() {
System.out.println("List(first---->last): ");
DoubleNode current = first;
while (current != null) {
current.displayNode();
current = current.next;
}
System.out.println();
}
/**
* 从尾部遍历打印
*/
public void displayBackward() {
System.out.println("List(last---->first): ");
DoubleNode current = last;
while (current != null) {
current.displayNode();
current = current.prev;
}
System.out.println();
}
/**
* 判断链表是否为空
* @return 布尔值
*/
public boolean isEmpty() {
return first == null || last == null;
}
/**
* 在头部插入结点
* @param data 结点数据
*/
public void insertFirst(int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
if (isEmpty()) {
last = newDoubleNode;
} else {
first.prev = newDoubleNode;
}
newDoubleNode.next = first;
first = newDoubleNode;
}
/**
* 在尾部插入结点
* @param data 插入的结点数据
*/
public void insertLast(int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
if (isEmpty()) {
first = newDoubleNode;
} else {
newDoubleNode.prev = last;
last.next = newDoubleNode;
}
last = newDoubleNode;
}
/**
* 在中间插入结点
* @param key 在对应数据为key的结点后面插入
* @param data 要插入的结点的数据
*/
public void insertAfter(int key, int data) {
DoubleNode newDoubleNode = new DoubleNode(data);
DoubleNode current = first;
while (current != null && current.data != key) {
current = current.next;
}
// 如果当前结点为null
if (current == null) {
// 判断是不是空链表
if (isEmpty()) {
first = newDoubleNode;
last = newDoubleNode;
} else {
// 找不到key的值,就在最后插入一个结点
newDoubleNode.prev = last;
last.next = newDoubleNode;
last = newDoubleNode;
}
} else { // 找到了key的值,分情况讨论
if (current == last) {
// key的值和最后结点的data相等
newDoubleNode.next = null;
last = newDoubleNode;
} else {
// 在两个结点中间插入
newDoubleNode.next = current.next;
current.next.prev = newDoubleNode;
}
newDoubleNode.prev = current;
current.next = newDoubleNode;
}
}
}
2.4 链表删除
同样的,首尾删除要比中间删除简单,中间删除的图像为:
/**
* 删除首结点
* @return 删除的结点
*/
public DoubleNode deleteFirst() {
DoubleNode temp = first;
// 如果链表只有一个结点
if (first.next == null) {
last = null;
} else {
// 两个以上的结点
first.next.prev = null;
}
first = first.next;
return temp;
}
/**
* 删除尾结点
* @return 删除的结点
*/
public DoubleNode deleteLast() {
DoubleNode temp = last;
if (last.prev == null) {
first = null;
} else {
last.prev.next = null;
}
last = last.prev;
return temp;
}
/**
* 删除中间结点
* @param key 对应删除结点的key
* @return 删除的结点
*/
public DoubleNode deleteKey(int key) {
DoubleNode current = first;
while (current != null && current.data != key) {
current = current.next;
}
// 如果current为null
if (current == null) {
return null;
} else {
// 如果current是第一个结点
if (current == first) {
first = current.next;
current.next.prev = null;
} else if (current == last) {
// 如果current是最后一个结点
last = current.prev;
current.prev.next = null;
} else {
// current在中间
current.prev.next = current.next;
current.next.prev = current.prev;
}
}
return current;
}