1、链表(Linked List)介绍
链表是有序的列表,但是它在内存中是存储如下
小结上图:
- 链表是以节点的方式来存储, 是链式存储
- 每个节点包含 data 域, next 域:指向下一个节点.
- 如图:发现 链表的各个节点不一定是连续存储.
- 链表分 带头节点的链表和 没有头节点的链表,根据实际的需求来确定
-单链表(带头结点) 逻辑结构示意图如下
2、单链表的应用实例
使用带 head 头的单向链表实现
- 第一种方法在添加英雄时,直接添加到链表的尾部 思路分析示意图:
- 第二种方式在添加英雄时, 根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示) 思路的分析示意图:
- 修改节点功能
思路(1) 先找到该节点,通过遍历
-
删除节点
思路分析的示意图:
-
求单链表中有效节点的个数
-
查找单链表中的倒数第 k 个结点
-
单链表的反转
思路分析图解
-
从尾到头打印单链表
-
实现代码:
SingleLinkedListNode.java
package com.yg.datastructures.linkedlist;
/**
* @Description: 单链表的模型
* @Author yg
* @Date 2021-03-24 9:42
*/
public class SingleLinkedListNode {
private int id;
private String name;
private int age;
private String phone;
private SingleLinkedListNode next;
public SingleLinkedListNode(int id, String name, int age, String phone) {
this.id = id;
this.name = name;
this.age = age;
this.phone = phone;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public SingleLinkedListNode getNext() {
return next;
}
public void setNext(SingleLinkedListNode next) {
this.next = next;
}
@Override
public String toString() {
return "SingleLinkedListNode{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
'}';
}
}
SingleLinkedList .java
package com.yg.datastructures.linkedlist;
import java.util.Stack;
/**
* @Description: 单链表操作
* @Author yg
* @Date 2021-03-24 9:42
*/
public class SingleLinkedList {
SingleLinkedListNode head = new SingleLinkedListNode(0, "", 0, "");
/**
* 获取头节点,空的头节点
*
* @return
*/
public SingleLinkedListNode getHead() {
return head;
}
/**
* 获取链表 的长度
*
* @return
*/
public int getLength() {
if (getHead().getNext() == null) {
return 0;
}
int sum = 0;
SingleLinkedListNode temp = getHead().getNext();
while (true) {
if (temp != null) {
sum++;
} else {
break;
}
temp = temp.getNext();
}
return sum;
}
/**
* 添加节点
*
* @param singleLinkedListNode
*/
public void addNode(SingleLinkedListNode singleLinkedListNode) {
SingleLinkedListNode temp = getHead();
while (true) {
if (temp.getId() == singleLinkedListNode.getId()) {
throw new RuntimeException("添加的节点id已经存在:" + singleLinkedListNode);
}
if (temp.getNext() == null) {
//找到了最后的节点,将添加的节点添加到尾部
break;
}
temp = temp.getNext();
}
temp.setNext(singleLinkedListNode);
}
/**
* 按照id顺序添加节点
*
* @param singleLinkedListNode
*/
public void addOrderById(SingleLinkedListNode singleLinkedListNode) {
SingleLinkedListNode temp = getHead();
boolean flag = false;
while (true) {
if (temp.getNext() == null) {
break;
}
//找到要添加的前一个id singleLinkedListNode.getId()<= 当前对比的id
//需要找到添加的位置的前一个节点,
if (singleLinkedListNode.getId() < temp.getNext().getId()) {
break;
} else if (singleLinkedListNode.getId() == temp.getNext().getId()) {
flag = true;
break;
}
temp = temp.getNext();
}
//temp 就是那个节点
if (flag) {
System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", singleLinkedListNode.getId());
} else {
//这里添加注意顺序,如果颠倒则会链表无限循环了
singleLinkedListNode.setNext(temp.getNext());
temp.setNext(singleLinkedListNode);
}
}
/**
* 更新节点
*
* @param singleLinkedListNode
* @return
*/
public SingleLinkedListNode updateNode(SingleLinkedListNode singleLinkedListNode) {
if (head.getNext() == null) {
throw new RuntimeException("链表为空,无有效数据更新!");
}
//从第一关元素遍历,不能给更新头节点
SingleLinkedListNode temp = head.getNext();
//标志位判断是否找到更新节点
boolean flag = false;
while (true) {
if (temp.getId() == singleLinkedListNode.getId()) {
//说明找到了,
flag = true;
break;
}
if (temp.getNext() == null) {
//说明已经到最后了,也没有找到。
System.out.println("没有找到要更新的节点:" + singleLinkedListNode);
break;
}
temp = temp.getNext();
}
if (flag) {
//进来表示找到了
temp.setAge(singleLinkedListNode.getAge());
temp.setName(singleLinkedListNode.getName());
temp.setPhone(singleLinkedListNode.getPhone());
return temp;
}
return null;
}
/**
* 删除节点
*
* @param delNo
*/
public void delNode(int delNo) {
if (isNull()) {
//删除从第一个节点开始遍历
SingleLinkedListNode temp = getHead().getNext();
boolean flag = false;
while (true) {
if (temp.getNext() == null) {
//表示已经找到了最后一个节点,还没有找到删除的元素
break;
}
if (temp.getNext().getId() == delNo) {
//找到要删除的前一个节点
flag = true;
break;
}
temp = temp.getNext();
}
if (flag) {
//进来表示找到了,此时的temp 是指向要删除的前一个节点的
if (temp.getNext().getNext() != null) {
//表示删除的后一个节点不为空,即删除的不是最后一个节点
temp.setNext(temp.getNext().getNext());
} else {
//如果是最后一个节点
temp.setNext(null);
}
}
}
}
/**
* 打印链表
*/
public void showList() {
if (isNull()) {
SingleLinkedListNode temp = getHead().getNext();
System.out.println("----------准备打印列表------------");
while (true) {
if (temp == null) {
//表示已经到头了
break;
}
System.out.println(temp);
temp = temp.getNext();
}
System.out.println("----------打印列表结束------------");
}
}
/**
* 查找单链表中的倒数第 k 个结点
* //思路
* //1. 编写一个方法,接收 head 节点,同时接收一个 index
* //2. index 表示是倒数第 index 个节点
* //3. 先把链表从头到尾遍历,得到链表的总的长度 getLength
* //4. 得到 size 后,我们从链表的第一个开始遍历 (size-index)个,就可以得到
* //5. 如果找到了,则返回该节点,否则返回 nulll
*
* @param singleLinkedListNode 链表
* @param index 倒数个数
* @return
*/
public SingleLinkedListNode findLastIndexNode(SingleLinkedListNode singleLinkedListNode, int index) {
int length = getLength();
SingleLinkedListNode temp = singleLinkedListNode;
if (isNull()) {
//有效性判断
if (index <= 0 || index > length) {
return null;
}
for (int i = 0; i < length - index + 1; i++) {
temp = temp.getNext();
}
return temp;
}
return null;
}
/**
* 判断是否为空
*
* @return
*/
public boolean isNull() {
if (head.getNext() == null) {
System.out.println("链表为空,无法操作!");
return false;
}
return true;
}
/**
* 将单链表反转
*
* @param head 链表的头
* @return
*/
public SingleLinkedListNode reverseLinkedList(SingleLinkedListNode head) {
if (head.getNext() == null || head.getNext().getNext() == null) {
//链表为空或者只有一个元素,无需反转
return head;
}
//定义一个辅助的指针(变量),帮助我们遍历原来的链表
//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表 newNode 的最前端
//头插法
SingleLinkedListNode newNode = new SingleLinkedListNode(0, "", 0, "");
SingleLinkedListNode temp = head.getNext();
SingleLinkedListNode next = null;
while (true) {
if (temp != null) {
//临时保存下一个节点
next = temp.getNext();
//设置当前节点的下一个节点为新链表的第一个节点
temp.setNext(newNode.getNext());
//设置新链表的第一个节点为当前插入的节点。
newNode.setNext(temp);
//temp 右移动
temp = next;
}else{
break;
}
}
//将 head.next 指向 newNode.next , 实现单链表的反转
head.setNext(newNode.getNext());
return head;
}
/**
* 单链表的逆序打印代码
* 利用栈 实现
* @param head
*/
public void reversePrint(SingleLinkedListNode head){
Stack<SingleLinkedListNode> stack = new Stack<SingleLinkedListNode>();
SingleLinkedListNode temp = head.getNext();
while(true){
if(temp == null){
break;
}
stack.push(temp);
temp = temp.getNext();
}
while(stack.size()>0){
System.out.println("出栈顺序:"+stack.pop());
}
}
}
SingleLinkedListDemo.java
package com.yg.datastructures.linkedlist;
/**
* @Description: 测试类
* @Author yg
* @Date 2021-03-24 9:40
*/
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedListNode node1 = new SingleLinkedListNode(1, "李白", 18, "18609287231");
SingleLinkedListNode node2 = new SingleLinkedListNode(2, "杜甫", 20, "18709873321");
SingleLinkedListNode node3 = new SingleLinkedListNode(3, "白居易", 22, "1818873321");
SingleLinkedListNode node4 = new SingleLinkedListNode(4, "李清照", 25, "17638779333");
SingleLinkedListNode node5 = new SingleLinkedListNode(5, "王安石", 33, "1887788772");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addNode(node1);
singleLinkedList.addNode(node2);
singleLinkedList.addNode(node4);
singleLinkedList.showList();
singleLinkedList.delNode(2);
System.out.println("-------------删除后链表--------------");
singleLinkedList.showList();
System.out.println("-------------添加5后链表--------------");
singleLinkedList.addNode(node5);
singleLinkedList.showList();
System.out.println("-------------顺序添加2,3后链表--------------");
singleLinkedList.addOrderById(node2);
singleLinkedList.addOrderById(node3);
singleLinkedList.showList();
System.out.println("-------------修改后链表--------------");
singleLinkedList.updateNode(new SingleLinkedListNode(1, "茅以升", 33, "1879983332"));
singleLinkedList.showList();
System.out.println("链表长度length = " + singleLinkedList.getLength());
System.out.println("链表的倒数第3个节点=" + singleLinkedList.findLastIndexNode(singleLinkedList.getHead(), 3));
SingleLinkedListNode reverseLinkedListNode = singleLinkedList.reverseLinkedList(singleLinkedList.getHead());
System.out.println("---------------反转后的链表为--------------");
singleLinkedList.showList();
System.out.println("----------------逆序打印链表---------------");
singleLinkedList.reversePrint(singleLinkedList.getHead());
}
}
运行结果:
D:\jdk1.8.0_251\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:1360,suspend=y,server=n -javaagent:C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2020.3\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "D:\jdk1.8.0_251\jre\lib\charsets.jar;D:\jdk1.8.0_251\jre\lib\deploy.jar;D:\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;D:\jdk1.8.0_251\jre\lib\ext\dnsns.jar;D:\jdk1.8.0_251\jre\lib\ext\jaccess.jar;D:\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;D:\jdk1.8.0_251\jre\lib\ext\localedata.jar;D:\jdk1.8.0_251\jre\lib\ext\nashorn.jar;D:\jdk1.8.0_251\jre\lib\ext\sunec.jar;D:\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;D:\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8.0_251\jre\lib\ext\zipfs.jar;D:\jdk1.8.0_251\jre\lib\javaws.jar;D:\jdk1.8.0_251\jre\lib\jce.jar;D:\jdk1.8.0_251\jre\lib\jfr.jar;D:\jdk1.8.0_251\jre\lib\jfxswt.jar;D:\jdk1.8.0_251\jre\lib\jsse.jar;D:\jdk1.8.0_251\jre\lib\management-agent.jar;D:\jdk1.8.0_251\jre\lib\plugin.jar;D:\jdk1.8.0_251\jre\lib\resources.jar;D:\jdk1.8.0_251\jre\lib\rt.jar;D:\IdeaProjects\datastructures\target\classes;D:\RepMaven\org\springframework\boot\spring-boot-starter-web\2.4.4\spring-boot-starter-web-2.4.4.jar;D:\RepMaven\org\springframework\boot\spring-boot-starter\2.4.4\spring-boot-starter-2.4.4.jar;D:\RepMaven\org\springframework\boot\spring-boot\2.4.4\spring-boot-2.4.4.jar;D:\RepMaven\org\springframework\boot\spring-boot-autoconfigure\2.4.4\spring-boot-autoconfigure-2.4.4.jar;D:\RepMaven\org\springframework\boot\spring-boot-starter-logging\2.4.4\spring-boot-starter-logging-2.4.4.jar;D:\RepMaven\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\RepMaven\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\RepMaven\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;D:\RepMaven\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;D:\RepMaven\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;D:\RepMaven\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\RepMaven\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;D:\RepMaven\org\springframework\boot\spring-boot-starter-json\2.4.4\spring-boot-starter-json-2.4.4.jar;D:\RepMaven\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;D:\RepMaven\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;D:\RepMaven\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;D:\RepMaven\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;D:\RepMaven\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;D:\RepMaven\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;D:\RepMaven\org\springframework\boot\spring-boot-starter-tomcat\2.4.4\spring-boot-starter-tomcat-2.4.4.jar;D:\RepMaven\org\apache\tomcat\embed\tomcat-embed-core\9.0.44\tomcat-embed-core-9.0.44.jar;D:\RepMaven\org\glassfish\jakarta.el\3.0.3\jakarta.el-3.0.3.jar;D:\RepMaven\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.44\tomcat-embed-websocket-9.0.44.jar;D:\RepMaven\org\springframework\spring-web\5.3.5\spring-web-5.3.5.jar;D:\RepMaven\org\springframework\spring-beans\5.3.5\spring-beans-5.3.5.jar;D:\RepMaven\org\springframework\spring-webmvc\5.3.5\spring-webmvc-5.3.5.jar;D:\RepMaven\org\springframework\spring-aop\5.3.5\spring-aop-5.3.5.jar;D:\RepMaven\org\springframework\spring-context\5.3.5\spring-context-5.3.5.jar;D:\RepMaven\org\springframework\spring-expression\5.3.5\spring-expression-5.3.5.jar;D:\RepMaven\mysql\mysql-connector-java\8.0.23\mysql-connector-java-8.0.23.jar;D:\RepMaven\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\RepMaven\org\springframework\spring-core\5.3.5\spring-core-5.3.5.jar;D:\RepMaven\org\springframework\spring-jcl\5.3.5\spring-jcl-5.3.5.jar;D:\JetBrains\IntelliJ IDEA 2020.3\lib\idea_rt.jar" com.yg.datastructures.linkedlist.SingleLinkedListDemo
Connected to the target VM, address: '127.0.0.1:1360', transport: 'socket'
----------准备打印列表------------
SingleLinkedListNode{id=1, name='李白', age=18, phone='18609287231'}
SingleLinkedListNode{id=2, name='杜甫', age=20, phone='18709873321'}
SingleLinkedListNode{id=4, name='李清照', age=25, phone='17638779333'}
----------打印列表结束------------
-------------删除后链表--------------
----------准备打印列表------------
SingleLinkedListNode{id=1, name='李白', age=18, phone='18609287231'}
SingleLinkedListNode{id=4, name='李清照', age=25, phone='17638779333'}
----------打印列表结束------------
-------------添加5后链表--------------
----------准备打印列表------------
SingleLinkedListNode{id=1, name='李白', age=18, phone='18609287231'}
SingleLinkedListNode{id=4, name='李清照', age=25, phone='17638779333'}
SingleLinkedListNode{id=5, name='王安石', age=33, phone='1887788772'}
----------打印列表结束------------
-------------顺序添加2,3后链表--------------
----------准备打印列表------------
SingleLinkedListNode{id=1, name='李白', age=18, phone='18609287231'}
SingleLinkedListNode{id=2, name='杜甫', age=20, phone='18709873321'}
SingleLinkedListNode{id=3, name='白居易', age=22, phone='1818873321'}
SingleLinkedListNode{id=4, name='李清照', age=25, phone='17638779333'}
SingleLinkedListNode{id=5, name='王安石', age=33, phone='1887788772'}
----------打印列表结束------------
-------------修改后链表--------------
----------准备打印列表------------
SingleLinkedListNode{id=1, name='茅以升', age=33, phone='1879983332'}
SingleLinkedListNode{id=2, name='杜甫', age=20, phone='18709873321'}
SingleLinkedListNode{id=3, name='白居易', age=22, phone='1818873321'}
SingleLinkedListNode{id=4, name='李清照', age=25, phone='17638779333'}
SingleLinkedListNode{id=5, name='王安石', age=33, phone='1887788772'}
----------打印列表结束------------
链表长度length = 5
链表的倒数第3个节点=SingleLinkedListNode{id=3, name='白居易', age=22, phone='1818873321'}
---------------反转后的链表为--------------
----------准备打印列表------------
SingleLinkedListNode{id=5, name='王安石', age=33, phone='1887788772'}
SingleLinkedListNode{id=4, name='李清照', age=25, phone='17638779333'}
SingleLinkedListNode{id=3, name='白居易', age=22, phone='1818873321'}
SingleLinkedListNode{id=2, name='杜甫', age=20, phone='18709873321'}
SingleLinkedListNode{id=1, name='茅以升', age=33, phone='1879983332'}
----------打印列表结束------------
----------------逆序打印链表---------------
出栈顺序:SingleLinkedListNode{id=1, name='茅以升', age=33, phone='1879983332'}
出栈顺序:SingleLinkedListNode{id=2, name='杜甫', age=20, phone='18709873321'}
出栈顺序:SingleLinkedListNode{id=3, name='白居易', age=22, phone='1818873321'}
出栈顺序:SingleLinkedListNode{id=4, name='李清照', age=25, phone='17638779333'}
出栈顺序:SingleLinkedListNode{id=5, name='王安石', age=33, phone='1887788772'}
Disconnected from the target VM, address: '127.0.0.1:1360', transport: 'socket'
Process finished with exit code 0