Article Directory
4. Linked list
1 Introduction
A linked list is an ordered list, but it is stored in memory as follows
- The linked list is stored in the form of nodes, which is a chain storage
- Each node contains data field, next field: points to the next node.
- As shown in the figure: It is found that each node of the linked list is not necessarily stored continuously
- The linked list is divided into a linked list with a head node and a linked list without a head node, which are determined according to actual needs
Singly linked list (leading node) logical structure diagram:
2. Application examples of singly linked list
Use a one-way linked list with a head to implement – Water Margin Hero Ranking Management to complete the operation of adding, deleting, modifying and checking heroes
1) Add the tail of the linked list
Analysis:
First create a Head node, which is to represent the head of the singly linked
list. Every time a node is added later, it is added to the last position of the linked list.
- Define HeroNodes
@Data
@ToString
class HeroNode{
private int no;
private String name;
private String nickName;
private HeroNode next;
public HeroNode(int no,String name,String nickName){
this.no = no;
this.name = name;
this.nickName = nickName;
}
}
- Define SingleLinkedList¶
class SingleLinkedList {
// 初始化头结点 不存放具体的数据
private final HeroNode head = new HeroNode(0, "", "");
// 遍历链表
public void list() {
// 头结点不能动,因此需要辅助变量遍历
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
// temp 后移
temp = temp.next;
}
}
// 第一种:不考虑排序
public void add(HeroNode heroNode) {
HeroNode temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = heroNode;
}
}
- test
public class Test_1_水浒传排行 {
public static void main(String[] args) {
// 1. 测试
HeroNode h1 = new HeroNode(1, "A", "a");
HeroNode h2 = new HeroNode(2, "B", "b");
HeroNode h3 = new HeroNode(3, "C", "c");
HeroNode h4 = new HeroNode(4, "D", "d");
HeroNode h5 = new HeroNode(5, "E", "e");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(h1);
singleLinkedList.add(h2);
singleLinkedList.add(h3);
singleLinkedList.add(h4);
singleLinkedList.add(h5);
singleLinkedList.list();
}
}
2) Specify the location to add
analyze:
- Find the position of the newly added node, through the auxiliary variable (pointer)
- new node next = temp.next
- set temp.next = new node
// 第二种:添加时排序
public void addByOrder(HeroNode heroNode) {
// 头节点不动,仍然通过指针辅助
// 单链表,所以我们查找的是添加位置的前一个节点
HeroNode temp = head;
boolean flag = false; // 标志添加的编号是否存在
while (true) {
if (temp.next == null) {
// 到达尾部.直接添加
break;
}
if (temp.next.no > heroNode.no) {
// 位置找到 添加到temp上
break;
}
if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.println("准备插入的英雄" + heroNode.no + "已经存在");
return;
}
// 插入链表
heroNode.next = temp.next;
temp.next = heroNode;
}
3) Update the node
// 修改节点信息
public void update(HeroNode heroNode) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
// 找到节点
// 1. 定义复制节点
HeroNode temp = head.next;
// 2. 定义标识 --> 是否找到
boolean flag = false;
while (true) {
// 链表尾部
if (temp == null) {
break;
}
// 找到
if (temp.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
// 找到
if (flag) {
temp.nickName = heroNode.nickName;
temp.name = heroNode.name;
} else {
System.out.println("未找到数据");
}
}
4) Delete the node
// 删除节点
public void delete(int delNo){
// 1. 定义辅助变量
HeroNode temp = head;
// 2. 定义flag
boolean flag = false;
while (true){
if(temp.next == null){
break;
}
// 3. 找到删除节点
if(temp.next.no == delNo){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next = temp.next.next;
}else {
System.out.println("未找到节点");
}
}
3. Single chain surface test questions
1) Find the number of nodes in the singly linked list
// 获取单链表长度【头节点需要去掉】
public int getSize() {
HeroNode curNode = head.next;
int count = 0;
while (curNode != null) {
count++;
curNode = curNode.next;
}
return count;
}
2) Find the last K node in the singly linked list
Ideas:
- Accept the head node, and accept an index at the same time
- index identifies the last index node
- First traverse the linked list from the beginning to the end, get the total length and traverse from the first one of the linked list, traverse (size-index) [10 get the penultimate one, traverse 8 times]
public HeroNode getLastIndex(int index){
if(head.next == null)return null;
int size = getSize();
// index 合法性
if(index <=0 || index > size)return null;
HeroNode temp = head.next;
for (int i = 0; i < (size - index); i++) {
temp = temp.next;
}
return temp;
}
3) Reversal of single linked list
Ideas:
- define a new node
- Traverse the nodes, adding each value to the head of a new node
// 反转当前链表
public void reverse() {
HeroNode reverseHead = new HeroNode(0, null, null);
// 当前节点
HeroNode cur = head.next;
HeroNode next = null;
// 遍历
while (true) {
if (cur == null) {
break;
}
next = cur.next;
// 反转插入
cur.next = reverseHead.next;
reverseHead.next = cur;
// 后移
cur = next;
}
head.next = reverseHead.next;
}
4) Print the singly linked list in reverse
// 从尾到头打印链表
// 利用栈打印
public void reverseByStack(){
Stack<HeroNode> heroStack = new Stack<>();
HeroNode temp = head.next;
while (true){
if(temp == null){
break;
}
heroStack.push(temp);
temp = temp.next;
}
while (!heroStack.empty()){
System.out.println(heroStack.pop());
}
}
5) Merge two ordered singly linked lists
// 合并两个有序链表
public void concatTwo(HeroNode a, HeroNode b) {
HeroNode tempA = a;
HeroNode tempB = b.next;
if (tempA.next == null) {
tempA.next = tempB;
return;
}
if (tempB == null) {
return;
}
while (true) {
if (tempA.next == null) {
break;
}
if (tempB.next == null) {
break;
}
if (tempA.next.no > tempB.no) {
// b 当头
HeroNode heroNode = new HeroNode(tempB.no, tempB.name, tempB.nickName);
// a 接后面
heroNode.next = tempA.next;
// a 后面变成b 遍历
tempA.next = heroNode;
tempB = tempB.next;
}
tempA = tempA.next;
}
tempA.next = tempB;
// 给当前链表赋值
head.next = tempA;
}
}
4. Application examples of doubly linked list
1 Introduction
Disadvantage analysis of managing singly linked list:
- In a singly linked list, the search direction can only be in one direction, while a doubly linked list can search forward or backward.
- The one-way linked list cannot be deleted by itself, it needs to rely on auxiliary nodes, while the two-way linked list can be deleted by itself, so when we delete the single-linked list, we always find temp, which is the previous node of the node to be deleted
2) case
// 双向链表
class DoubleLinkedList {
private HeroNode2 head = new HeroNode2(0, "", "");
public HeroNode2 getHead() {
return head;
}
// 添加节点
public void add(HeroNode2 heroNode) {
HeroNode2 temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = heroNode;
heroNode.pre = temp;
}
// 顺序添加
public void addByOrder(HeroNode2 heroNode) {
HeroNode2 temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
// 尾部
break;
}
if (temp.next.no > heroNode.no) {
break;
}
if (temp.next.no == heroNode.no) {
flag = true;
break;
}
// 后移
temp = temp.next;
}
if (flag) {
System.out.println("值:" + heroNode.no + "重复");
return;
}
if (temp.pre == null) {
// temp == head
heroNode.next = temp.next;
temp.next = heroNode;
heroNode.pre = head.pre;
} else {
temp.pre.next = heroNode;
heroNode.pre = temp.pre;
temp.pre = heroNode;
heroNode.next = temp;
}
}
// 修改节点
public void update(HeroNode2 heroNode) {
if (heroNode == null) return;
HeroNode2 temp = head;
while (temp.next != null) {
if (temp.no == heroNode.no) {
temp.name = heroNode.name;
temp.nickName = heroNode.nickName;
break;
}
temp = temp.next;
}
}
// 删除节点
public void delete(int delNo) {
HeroNode2 temp = head;
boolean flag = false;
while (temp.next != null) {
if (temp.no == delNo) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
if (temp.pre != null) {
temp.pre.next = temp.next;
} else {
head.next = null;
}
}
}
// 遍历打印
public void list() {
HeroNode2 temp = head.next;
while (temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
}
class HeroNode2 {
public int no;
public String name;
public String nickName;
public HeroNode2 pre;
public HeroNode2 next;
public HeroNode2(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' + "}";
}
}
5. Application of one-way circular linked list
1) Joseph problem
Josephhu’s problem is: Let n people numbered 1, 2, ... n sit in a circle, agree that the person numbered k (1<=k<=n) will start counting from 1, and the person who counts to m will go out , its next person starts counting from 1, and the person who counts to m goes out of the queue again, and so on, until everyone goes out of the queue, thus generating a sequence of queue numbers.
n = 5, that is, there are 5 people
k = 1, start counting from the first person
m = 2, count 2
The order of out circle
2->4->1->5->3
// Copied, the teacher didn't understand what he said, but I can realize it by myself, see the replay for details
//创建一个环形的单向链表
public class CircleSingleLinkedList {
//创建一个first节点 当前没有编号 代表第一个小孩
private Boy first = new Boy(-1);
//添加小孩节点,构建成一个环形的链表
public void addBoy(int nums) {
//nums代表总的小孩个数
if (nums < 1) {
System.out.printf("nums值不正确");
return;
}
//因为first不能动 所以创建一个辅助指针来构建环形链表
Boy curBoy = null;
for (int i = 1; i <=nums; i++) {
//根据编号创建小孩节点
Boy boy = new Boy(i);
//第一个小孩 自己构成环形
if (i == 1) {
first = boy;
boy.setNext(first);
curBoy = first;
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
//显示环形链表
public void showBoy() {
if (first == null) {
System.out.println("链表为空,无小孩");
return;
}
//因为first不能动 仍然需要辅助指针
Boy curBoy = first;
while (true) {
System.out.printf("小孩的编号是:%d\n", curBoy.getNo());
if (curBoy.getNext() == first) {
break;
}
curBoy = curBoy.getNext();//curBoy后移
}
}
//根据用户的输入计算出小孩出圈的顺序
/**
* @param startNo 表示从第几个小孩开始数数
* @param countNum 表示数几下
* @param nums 表示最初有多少小孩
*/
public void countBoy(int startNo, int countNum, int nums) {
if (first == null || startNo < 1 || startNo > nums) {
System.out.printf("参数输入有误 请重新输入");
return;
}
//创建辅助指针 帮助小孩出圈
Boy helper = first;
//辅助指针应该指向环形链表的最后节点
while (true) {
if (helper.getNext() == first) {
break;//说明helper指向最后一个小孩节点
}
helper = helper.getNext();
}
//小孩报数前,先让first和helper移动k-1次
for (int i = 0; i < startNo - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
//小孩报数时,让first和helper移动m-1次 然后出圈
while (true) {
if (helper == first) {
break;//圈中只剩一个节点
}
for (int i = 0; i < countNum - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
//这时first指向的节点就是要出圈的小孩
System.out.printf("小孩%d出圈\n", first.getNo());
//将first指向的节点出圈
first = first.getNext();
helper.setNext(first);
}
System.out.printf("最后留在圈中的小孩编号%d\n", helper.getNo());
}
}
//创建Boy类 代表一个节点
public class Boy {
private int no;
private Boy next;//指向下一个节点 默认null
public Boy(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
6. Replay code
1) One-way linked list
Test Methods
public static void testLinkedList() {
LinkedList linkedList = new LinkedList();
for (int i = 9; i > 1; i--) {
Node1 node1 = new Node1(i, String.valueOf(i));
// linkedList.add(node1);
linkedList.addByOrder(node1);
}
linkedList.list();
System.out.println("------更新节点------");
Node1 n1 = new Node1(5, "张飞");
linkedList.update(n1);
linkedList.list();
System.out.println("------删除节点------");
linkedList.delete(5);
linkedList.list();
System.out.println("------获取长度-------");
System.out.println(linkedList.size());
System.out.println("------获取倒数第 3 个节点------");
linkedList.getLastIndex(3);
System.out.println("-----链表反转-------");
linkedList.reverse();
linkedList.list();
System.out.println("-----反向打印--------");
linkedList.printReverse();
System.out.println("-----合并两个链表-----");
// linkedListA = [2,4,6,8,10]
// linkedListB = [1,3,5,7,9]
LinkedList linkedListA = new LinkedList();
LinkedList linkedListB = new LinkedList();
Random random = new Random();
int num = random.nextInt(10);
for (int i = 2; i < 30; i += num) {
linkedListA.addByOrder(new Node1(i, String.valueOf(i)));
}
for (int i = 1; i < 30; i += num) {
linkedListB.addByOrder(new Node1(i, String.valueOf(i)));
}
System.out.println("-----A------");
linkedListA.list();
System.out.println("-----B------");
linkedListB.list();
System.out.println("----合并------");
linkedList.concat(linkedListA.head.next, linkedListB.head.next);
linkedList.list();
}
Node class
class Node1 {
Node1 next;
int id;
String name;
public Node1(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Node1{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
LinkedList class
Basic CRUD
class LinkedList {
// 链表起始点
Node1 head = new Node1(0, "");
// 添加节点
public void add(Node1 node) {
// 1. 验证参数
if (node == null) {
System.out.println("参数 不能为空");
return;
}
// 2. 遍历链表, 加入链表末尾
// 3. 定义一个辅助指针,帮助遍历
Node1 temp = head;
while (temp.next != null) {
// 4. 指针后移
temp = temp.next;
}
temp.next = node;
}
// 排序添加节点
public void addByOrder(Node1 node) {
// 1. 验证参数
if (node == null) {
System.out.println("参数 不能为空");
return;
}
// 2. 创建临时变量遍历 指向遍历的前一个
Node1 temp = head;
// 3. 创建标识标志添加位置
boolean flag = false;
// 4. 遍历
while (temp.next != null) {
// 5. 找到添加位置
if (temp.next.id > node.id) {
flag = true;
break;
}
// 6.指针后移
temp = temp.next;
}
// 7. 有位置
if (flag) {
node.next = temp.next;
temp.next = node;
} else {
// 8. 直接添加到末尾
temp.next = node;
}
}
// 更新节点
public void update(Node1 node) {
// 1. 验证参数
if (node == null) {
System.out.println("参数 不能为空");
return;
}
// 2. 定义临时变量
Node1 temp = head.next;
boolean flag = false;
while (temp != null) {
if (temp.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = node.name;
}
}
// 删除节点
public void delete(int id) {
if (id > 0) {
// 1. 创建辅助变量
Node1 temp = head;
boolean flag = false;
// 2. 遍历
while (temp.next != null) {
if (temp.next.id == id) {
flag = true;
break;
}
temp = temp.next;
}
// 3. 删除数据
if (flag) {
// temp此时为需要删除的节点的上一个
temp.next = temp.next.next;
}
}
}
// 遍历打印链表
public void list() {
// 1. 辅助指针
Node1 temp = head.next;
// 2. 循环遍历
while (temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
}
Get the number of linked lists
public int size() {
// 1. 定义两个变量
Node1 temp = head.next;
int count = 0;
while (temp != null) {
count++;
temp = temp.next;
}
return count;
}
Find the nth last node
public void getLastIndex(int lastIndex) {
// 1. 创建临时变量
Node1 temp = head.next;
// 2. 获取循环长度
int size = size();
if (lastIndex > size || lastIndex < 1) {
System.out.println("index不合法");
return;
}
for (int i = 0; i < (size - lastIndex); i++) {
temp = temp.next;
}
System.out.println(temp);
}
single table inversion
public void reverse() {
// 1. 定义临时变量
Node1 temp = head.next;
// 2. 定义存储变量
Node1 next = null;
Node1 newNode = new Node1(0, "");
// 3. 循环
while (temp != null) {
// 临时存储剩下循环变量
next = temp.next;
// 进行反转
// 例如 [【2】,3,4,5] = [0,null,]
temp.next = newNode.next;
// temp = [2,null]
// temp = [3,2,null]
newNode.next = temp;
// 后移
temp = next;
}
head = newNode;
}
Reverse print singly linked list, using the concept of stack
public void printReverse() {
// 1. 创建栈
Stack<Node1> stack = new Stack<>();
// 2. 循环链表
Node1 temp = head.next;
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
// 3. 打印stack
while (!stack.empty()) {
System.out.println(stack.pop());
}
}
Merge two sorted singly linked lists
public void concat(Node1 n1, Node1 n2) {
// 1. 创建指针
Node1 tempA = n1;
Node1 tempB = n2;
if (tempA == null) {
head.next = tempB;
return;
}
if (tempB == null) {
head.next = tempA;
return;
}
Node1 newNode = new Node1(0, "");
// head存储 newNode
head = newNode;
// 2. 遍历
while (true) {
if (tempA.next == null) {
break;
}
if (tempB.next == null) {
break;
}
if (tempA.id > tempB.id) {
// 举例
// 保证next最后一位指向A
newNode.next = tempB;
tempB = tempB.next;
newNode = newNode.next;
newNode.next = tempA;
} else {
newNode.next = tempA;
}
newNode = newNode.next;
tempA = tempA.next;
}
// 3. 其中一个遍历完毕
// 拼接B
newNode.next = tempB;
}
2) Doubly linked list
Test Methods
// 双向链表测试
public static void testTwoLinkedList() {
TwoLinkedList linkedList = new TwoLinkedList();
System.out.println("------添加-------");
for (int i = 10; i > 0; i--) {
Node2 node = new Node2(i, String.valueOf(i));
linkedList.addByOrder(node);
}
linkedList.list();
System.out.println("------更新-------");
linkedList.update(new Node2(3, "张飞"));
linkedList.list();
System.out.println("------删除-------");
linkedList.delete(3);
linkedList.list();
System.out.println("------添加-------");
System.out.println("------添加-------");
}
Node class
// 双向
class Node2 {
Node2 pre;
Node2 next;
int id;
String name;
public Node2(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Node1{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Two-LinkedList class
//
class TwoLinkedList {
Node2 head = new Node2(0, "");
// 添加
public void add(Node2 node) {
// 1. 验证参数
if (node == null) {
System.out.println("参数 不能为空");
return;
}
// 2. 临时变量
Node2 temp = head;
// 3. 遍历
while (temp.next != null) {
temp = temp.next;
}
// 赋值
temp.next = node;
node.pre = temp;
}
// 修改
public void update(Node2 node) {
// 1. 验证参数
if (node == null) {
System.out.println("参数 不能为空");
return;
}
Node2 temp = head.next;
while (temp != null) {
if (temp.id == node.id) {
temp.name = node.name;
return;
}
temp = temp.next;
}
throw new RuntimeException("该节点不存在");
}
// 删除
public void delete(int id) {
Node2 temp = head.next;
while (temp != null) {
if (temp.id == id) {
temp.pre.next = temp.next;
return;
}
temp = temp.next;
}
throw new RuntimeException("该节点不存在");
}
// 遍历
public void list() {
Node2 temp = head.next;
while (temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
// 顺序添加
public void addByOrder(Node2 node){
// 1. 验证参数
if (node == null) {
System.out.println("参数 不能为空");
return;
}
Node2 temp = head;
boolean flag = false;
while (temp.next != null){
if(temp.next.id > node.id){
flag = true;
break;
}
temp = temp.next;
}if(flag){
node.next = temp.next;
temp.next = node;
node.pre = temp;
}else {
temp.next = node;
node.pre = temp;
}
}
}
3) One-way circular linked list
Test Methods
public static void testCycleLinkedList() {
CycleLinkedList cycleLinkedList = new CycleLinkedList();
for (int i = 1; i <= 5; i++) {
cycleLinkedList.add(new Node1(i, String.valueOf(i)));
}
System.out.println("---添加-----");
cycleLinkedList.list();
System.out.println("---约瑟夫问题-----");
cycleLinkedList.Joseph(1, 2);
System.out.println("-----2---------");
// cycleLinkedList.calcJosephuOrder(2,5);
}
Joseph method
class CycleLinkedList {
public Node1 head;
// 添加
public void add(Node1 node) {
Node1 temp = head;
if (temp == null) {
head = node;
node.next = head;
return;
}
while (temp.next.id != head.id) {
temp = temp.next;
}
temp.next = node;
node.next = head;
}
// 遍历
public int list() {
int count = 0;
Node1 temp = head;
if (temp == null) {
return 0;
}
while (temp.next.id != head.id) {
temp = temp.next;
// System.out.println(temp);
count++;
}
return count;
}
// 约瑟夫问题
public void Joseph(int k, int m) {
// k = 第几个小孩的id
// m = 数几次
// 1. 这里不验证参数了, 直接省略 head.id = 0
Node1 temp = head;
int size = list();
while (size > 0) {
if (temp.id == k) {
for (int i = 0; i < m - 2; i++) {
temp = temp.next;
}
// temp 为要删除的 前一个
System.out.println(temp.next);
temp.next = temp.next.next;
k = temp.next.id;
size--;
}
temp = temp.next;
}
System.out.println(temp);
}
}