本文目录
1 在 Java 中构造单向链表
1.1 单向链表的结构
单向链表的结构如下所示:
其中,head 是 表头指针 用来指向链表的第一个结点也就是 首结点 ,链表中每个 链表结点 都由 数据部分 和 后继元素的 next 指针 组成,由于链表最后一个结点也就是 尾结点 没有直接后继,所以尾结点的 next 指针为 null 。
1.2 定义单向链表
根据单向链表的结构,在 Java 中定义单向链表的代码如下所示:
package com.lin.level1;
/**
* @author lin
* 单向链表结点
*/
public class LinkedListNode {
/**
* 数据部分
*/
public int val;
/**
* 后继元素的 next 指针
*/
public LinkedListNode next;
/**
* 构造方法,初始化单向链表结点对象时,用来给单向链表结点对象的数据部分赋值
*
* @param val 数据值
*/
public LinkedListNode(int val) {
this.val = val;
this.next = null;
}
}
注:为了能够 简化编码 和便于操作,在定义 LinkedListNode 时没有将属性设置为 private 而是设置为 public,这样能够直接使用 node.val 和 node.next 来操作,而不用再去编写 setter 和 getter 方法来操作。
1.3 通过数组初始化单向链表
使用 Java 编写的通过数组初始化单向链表的方法代码如下所示:
/**
* 通过数组初始化单向链表
*
* @param array 数据数组
* @return 链表首结点
*/
public static LinkedListNode initLinkedList(int[] array) {
LinkedListNode head = null, cur = null;
for (int i = 0; i < array.length; i++) {
LinkedListNode newNode = new LinkedListNode(array[i]);
newNode.next = null;
if (i == 0) {
head = newNode;
cur = newNode;
} else {
cur.next = newNode;
cur = newNode;
}
}
return head;
}
初始化示例单向链表的代码如下:
int[] a = {10, 30, 50, 40, 20};
LinkedListNode head = initLinkedList(a);
通过 Debug 模式可以看到从首结点出发,通过结点的 next 指针依次访问下一个结点,直到尾结点,各个结点就像是链结在一条链上:
1.4 获取链表长度
使用 Java 编写的获取链表长度的方法代码如下所示:
/**
* 获取链表长度
*
* @param head 链表首结点
* @return 链表长度
*/
public static int getListLength(LinkedListNode head) {
int length = 0;
LinkedListNode node = head;
while (node != null) {
length++;
node = node.next;
}
return length;
}
注:获取链表长度其实也是在遍历链表,从链表首结点开始遍历。
2 在单向链表中添加元素
在单向链表中添加元素时,我们需考虑以下三种情况:
- 在头部添加元素
- 在中间添加元素
- 在尾部添加元素
2.1 在头部添加元素
以在原链表的头部添加元素 LinkedListNode(60) 为例,在头部添加元素的过程如下图所示:
要想在原链表的头部添加元素 LinkedListNode(60) ,先要让 LinkedListNode(60) 指向原先的 head ,再让 LinkedListNode(60) 成为新的 head,即 先执行 LinkedListNode(60).next = head 再执行 head = LinkedListNode(60) 。
思考:为什么要先执行 LinkedListNode(60).next = head 再执行 head = LinkedListNode(60) ,如果顺序颠倒会怎么样?
答:如果顺序颠倒先执行了 head = LinkedListNode(60) ,则 LinkedListNode(60) 就成为了新的 head,这会导致丢失原先 head 中 next 指向的下一个结点的地址,进而导致后面结点的丢失。
需要注意的点:在头部添加元素后要记得让 head 重新指向新的首结点。如果忘记让 head 重新指向新的首结点,则会导致遍历时遗漏新添加的首结点。
注:本文中 LinkedListNode(60) 表示数据为 60 的链表结点,在程序中体现为: LinkedListNode newNode = new LinkedListNode(60); 。
2.2 在中间添加元素
以在原链表的中间添加元素 LinkedListNode(60) 为例,在中间添加元素的过程如下图所示:
这里要将 LinkedListNode(60) 插入到原先 LinkedListNode(50) 所在的位置上。首先需要在 LinkedListNode(50) 的 前一个位置停下来 ,也就是先停在 LinkedListNode(30) 的位置上,此时 cur 用来保存当前结点 LinkedListNode(30),然后要 先让 LinkedListNode(60).next = cur.next,再让 cur.next = LinkedListNode(60) ,这样就能将 LinkedListNode(60) 插入到原先 LinkedListNode(50) 所在的位置上。
思考一:为什么要在要插入位置的前一个位置停下来?
答:如果不在要插入位置的前一个位置停下来的话,直接到达要插入位置时会无法再获取要插入位置的前驱元素,这就导致要插入位置的前驱元素的 next 无法指向新结点。
思考二:为什么要先让 LinkedListNode(60).next = cur.next,再让 cur.next = LinkedListNode(60),如果顺序颠倒会怎么样?
答:如果顺序颠倒先让 cur.next = LinkedListNode(60) ,则原先 cur.next 的值就丢失了,这将导致原先链接在 LinkedListNode(30) 后的结点丢失。
2.3 在尾部添加元素
以在原链表的尾部添加元素 LinkedListNode(60) 为例,在尾部添加元素的过程如下图所示:
要想在原链表的尾部添加元素 LinkedListNode(60) ,只需让原链表的尾结点 LinkedListNode(20) 指向 LinkedListNode(60),也就是 LinkedListNode(20).next = LinkedListNode(60)
2.4 链表添加元素代码
用 Java 编写的链表添加元素的代码如下所示:
/**
* 链表插入
*
* @param head 链表首结点
* @param nodeInsert 待插入结点
* @param position 待插入位置,从1开始
* @return 插入后得到的链表首结点
*/
public static LinkedListNode insertNode(LinkedListNode head, LinkedListNode nodeInsert, int position) {
// head == null 时可认为 nodeInsert 为链表首结点
if (head == null) {
return nodeInsert;
}
int size = getListLength(head);
if (position < 1 || position > size + 1) {
System.out.println("位置参数错误");
return head;
}
// 头部添加元素
if (position == 1) {
nodeInsert.next = head;
head = nodeInsert;
return head;
}
// 中间添加元素,当 position == size + 1 时,为尾部添加元素
LinkedListNode pNode = head;
int count = 1;
while (count < position - 1) {
pNode = pNode.next;
count++;
}
nodeInsert.next = pNode.next;
pNode.next = nodeInsert;
return head;
}
3 总结
本文根据单向链表的结构,使用 Java 完成了单向链表结点的定义和构造,并分析了如何在单向链表中插入元素,在插入元素时分为了三种情况来讨论,并通过图例说明了需要注意的地方,最终给出了 Java 代码实现。综上所述,我们要理解单向链表的定义,在单向链表中插入元素时,要能够分情况来分析,在进行结点的链结时需要注意 head 指针的指向和结点间链结顺序的问题。
4 获取本文全部示例代码文件
本文全部示例代码文件可从我的 GitHub 仓库中获取:算法通关村第一关链表青铜挑战笔记代码