链表(Java版本)
关于作者
- 作者介绍
🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名初入职场小白🎓、曾在校期间参加各种省赛、国赛,斩获一系列荣誉🏆
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨💻
简介
链表是有序的列表,但是它在内存中是存储如下
1.链表是以节点的方式来存储,是链式存储
2.每个节点包含 data 域, next 域:指向下一个节点.
3.发现链表的各个节点不一定是连续存储
4.链表分为带头节点的链表和没有头节点的链表,根据实际的需求来确定
单链表(带头节点)
实例
使用带 head 头的单向链表实现—学生信息管理完成对学生信息的增删改查操作
思路
添加
直接添加到链表的尾部
在添加信息时,根据编号将学生信息插入到指定位置(如果存在这个编号,则添加失败,并给出提示
思路的分析示意图:
修改
修改节点的信息, 根据 no 编号来修改,即 no 编号不能改.
删除
代码
package com.cz.LinkedList;
import java.util.Stack;
/**
* @ProjectName: Data_structure
* @Package: com.cz.LinkedList
* @ClassName: SingleLinkedListDemo
* @Author: 张晟睿
* @Date: 2022/2/24 10:32
* @Version: 1.0
*/
public class SingleLinkedListDemo {
public static void main(String []args){
NodeList list = new NodeList(0,"zrq",20,"软工1班");
NodeList list1 = new NodeList(1,"zrq1",21,"软工2班");
// NodeList list2 = new NodeList(2,"zrq2",22,"软工3班");
// NodeList list3 = new NodeList(3,"zrq3",23,"软工4班");
NodeList list4 = new NodeList(2,"zrq4",24,"软工5班");
NodeList list5 = new NodeList(5,"sss",21,"软工1班");
SingleLinkedList singleLinkedList1 = new SingleLinkedList();
SingleLinkedList singleLinkedList2 = new SingleLinkedList();
singleLinkedList1.add(list);
singleLinkedList1.add(list1);
// singleLinkedList1.addByOrder(list2);
// singleLinkedList1.addByOrder(list3);
singleLinkedList2.add(list4);
singleLinkedList2.add(list5);
singleLinkedList2.list();
System.out.println("+++++");
System.out.println(list4.next);
System.out.println("+++++");
// singleLinkedList.reversalNode(singleLinkedList.getHead());
//
// singleLinkedList.list();
// singleLinkedList.reversePrint(singleLinkedList.getHead());
// singleLinkedList.add(list4);
// singleLinkedList.delete(list4.sno);
//
// singleLinkedList.update(list5);
//
// singleLinkedList.list();
//
// System.out.println("链表的有效数据个数为:"+ singleLinkedList.size());
//
// //测试查找单链表中倒数第k个节点
// NodeList lastIndexNode = singleLinkedList.findLastIndexNode(singleLinkedList.getHead(), 4);
// System.out.println(lastIndexNode);
// singleLinkedList.reversalNode(singleLinkedList.getHead());
singleLinkedList1.mergeNode(singleLinkedList1.getHead(), singleLinkedList2.getHead());
}
}
//管理Node节点
class SingleLinkedList{
//初始化头节点
private NodeList head = new NodeList(0,"",0,"");
public void setHead(NodeList head) {
this.head = head;
}
public NodeList getHead() {
return head;
}
//统计有效的节点个数
public int size(){
//链表为空 则返回长度0
if (head.next == null) return 0;
//定义一个辅助来记录当前的节点
NodeList temp = head.next;
int count = 0;//统计节点个数
while(temp != null){
count++;
temp = temp.next;
}
return count;
}
//向节点中添加数据
//思路,当不考虑编号顺序时
//1. 找到当前链表的最后节点
//2. 将最后这个节点的 next 指向 新的节
public void add(NodeList nodeList){
NodeList temp = head;
while(true){
if (temp.next == null){
break;
}
temp = temp.next;
}
//退出循环时,链表指向最后
temp.next = nodeList;
}
//向节点中添加数据并排序
//思路,当考虑编号顺序时
//1. 找到当前链表与当前链表的后一个节点的sno编号进行比较
//2. 将最后这个节点的 next 指向 新的节
public void addByOrder(NodeList nodeList){
boolean flag = false;//判断是否已经存在该排名
NodeList temp = head;
while(true) {
//判断是否到达链表尾部
if (temp.next == null) {
break;
}
//判断插入的节点与后一个节点的sno的大小
if (temp.next.sno > nodeList.sno) {
break;//此时找到插入的位置在temp的后面
} else if (temp.next.sno == nodeList.sno) {
flag = true; //说明编号存在
break;
}
temp = temp.next;
}
if (flag) {
System.out.println("该编号已存在,请重新输入");
} else {
nodeList.next = temp.next;
temp.next = nodeList;
}
}
//显示节点中的所有对象
public void list(){
//判断节点是否为空
if(head.next == null){
return;
}
NodeList temp = head.next;
while(true){
if (temp == null){
break;
}
System.out.println(temp);
temp = temp.next;
}
}
//修改节点的信息, 根据 no 编号来修改,即 no 编号不能改.
//1. 根据 newHeroNode 的 no 来修改即可
//2. 说明我们在比较时,需要查找当前节点是否存在 如果存在才能进行修改
public void update(NodeList nodeList){
//标记是否找到该节点
int flag = 0;
//头节点不能动
if (head.next == null) return ;//链表是否为空
NodeList temp = head.next;
while(true) {
if (temp == null)//判断是否到达链表尾部
{
break;
}
if (nodeList.sno == temp.sno) {
//找到该节点
flag = 1;
break;
}
temp = temp.next;
}
if (flag == 1){
temp.setSno(nodeList.sno);
temp.setName(nodeList.name);
temp.setAge(nodeList.age);
temp.setGrade(nodeList.grade);
}else
System.out.printf("要修改的 %d 节点不存在,无发修改n", nodeList.sno);
}
// 删除节点
// 思路
// 1. head 不能动,因此我们需要一个 temp 辅助节点找到待删除节点的前一个节点
// 2. 说明我们在比较时,是 temp.next.sno 和 需要删除的节点的 sno 比较
public void delete(int sno){
//用来标记是否找到该节点 0是为找到 1为找到
int flag = 0;
//头节点不能动
NodeList temp = head;
//判断节点是否为空
if(head.next == null){
return;
}
while(true){
//判断节点是否到达末尾
if (temp.next == null){
break;
}
//删除的链表号是否被找到
if (temp.next.sno == sno) {
flag = 1;break;
}
temp = temp.next;//当前节点指向后一个节点
}
//找到该节点后 判断该节点的尾指针指向下一个节点
if (flag == 1){
temp.next = temp.next.next;
}else
System.out.printf("要删除的 %d 节点不存在n", sno);
}
}
//定义Node 每个Node都是一个对象
class NodeList{
public int sno;
public String name;
public int age;
public String grade;
public NodeList next;
public NodeList(int sno, String name, int age, String grade) {
this.sno = sno;
this.name = name;
this.age = age;
this.grade = grade;
}
public void setSno(int sno) {
this.sno = sno;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGrade(String grade) {
this.grade = grade;
}
public void setNext(NodeList next) {
this.next = next;
}
@Override
public String toString() {
return "Student{" +
"sno=" + sno +
", name='" + name + ''' +
", age=" + age +
", grade='" + grade + ''' +
'}';
}
}
单链表常见的几种类型的问题
1.求单链表中有效节点的个数
//统计有效的节点个数
public int size(){
//链表为空 则返回长度0
if (head.next == null) return 0;
//定义一个辅助来记录当前的节点
NodeList temp = head.next;
int count = 0;//统计节点个数
while(temp != null){
count++;
temp = temp.next;
}
return count;
}
2.查找单 链表中的倒数第k个结点
//查找单链表中倒数第k个节点
//思路
//先把链表全部遍历一遍得到链表的长度,从链表的第一个节点开始,查找length - k 就可以得到该节点的数据
//例如 length=3 k=1 我们所需要循环2次就可以找到该节点
/**
*
* @param head 链表的头节点
* @param index 倒数第k个节点
* @return 倒数第k个节点的链表内容
*/
public NodeList findLastIndexNode(NodeList head, int index){
//判断链表是否为空
if(head.next == null) return null;
NodeList temp = head.next;
for (int i = 0; i < size() - index; i++) {
temp = temp.next;
}
return temp;
}
3.单链表的反转
//链表反转
public void reversalNode(NodeList head){
if (head.next == null || head.next.next == null){
System.out.println("链表为空!");return ;
}
NodeList cur = head.next;//用cur来记录当前的node节点
NodeList next = null;//用来记录cur的下一个节点
NodeList reversalHead = new NodeList(0,"",0,"");//用来记录reversal节点
while(cur != null){
next = cur.next;
cur.next = reversalHead.next;//将 cur 的下一个节点指向新的链表的最前端
reversalHead.next = cur; //将 cur 连接到新的链表上
cur = next;//让 cur 后移
}
//将 head.next 指向 reverseHead.next , 实现单链表的反转
head.next = reversalHead.next;
}
这里可能有两行代码很难理解
cur.next = reversalHead.next;//将 cur 的下一个节点指向新的链表的最前端
reversalHead.next = cur; //将 cur 连接到新的链表上
4.从尾到头打印单链表 [要求方式1:反向遍历。方式2: Stack栈]
//从尾到头打印单链表 【百度,要求方式 1:反向遍历 。 方式 2:Stack 栈
public void reversePrint(NodeList head){
//判断链表是否为空
if (head.next == null) return ;
//创建要给一个栈,将各个节点压入栈
Stack stack = new Stack();
NodeList cur = head.next;
while(cur != null){
stack.push(cur);
cur = cur.next;
}
while(stack.size()>0){
System.out.println(stack.pop());
}
}
5.合并两个有序的单链表,合并之后的链表依然有序
public void mergeNode(NodeList head1, NodeList head2){
//定义一个心链表
NodeList newnode = new NodeList(0,"",0,"");
//定义新链表的表头
NodeList temp = newnode;
//判断链表的个数如果链表为空返回另一链表内容
if(head1.next == null) newnode = head2;
if(head2.next == null) newnode = head1;
NodeList cur1 = head1.next;
NodeList cur2 = head2.next;
//判断链表的sno大小 ,将链表的下一个节点指向所需要的指向的节点
while(cur1 != null && cur2 != null){
if(cur1.sno > cur2.sno){
newnode.next = cur2;
cur2 = cur2.next;
}else{
newnode.next = cur1;
cur1 = cur1.next;
}
newnode = newnode.next;
}
//判断完成后,如果该cur1节点为空 将剩余的cur2链表的内容通过newnode指向cur2
if (cur1 == null) newnode.next = cur2;
if (cur2 == null) newnode.next = cur1;
while(temp.next!=null){
temp = temp.next;
System.out.println(temp);
}
}
双向链表(带头结点)
管理单向链表的缺点分析
- 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
- 单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除时节点总是找到 temp,temp 是待删除节点的前一个节点。
- 分析了双向链表如何完成遍历,添加,修改和删除的思路。
思路
遍历
和单链表一样,只是可以向前,也可以向后查找
添加 (默认添加到双向链表的最后)
修改
和单向链表一样。
删除
(1) 因为是双向链表,因此,我们可以实现自我删除某个节点
(2) 直接找到要删除的这个节点,比如temp
(3) temp.pre.next = temp.next
(4) temp.next.pre = temp.pre;
代码
package com.cz.LinkedList;
/**
* @ProjectName: Data_structure
* @Package: com.cz.LinkedList
* @ClassName: DoubleLinkedListDemo
* @Author: 张晟睿
* @Date: 2022/2/26 18:33
* @Version: 1.0
*/
public class DoubleLinkedListDemo {
public static void main(String[] args) {
DoubleLinedList doubleLinedList = new DoubleLinedList();
NodePoint nodePoint1 = new NodePoint(1,"孙悟空",19,"西游记");
NodePoint nodePoint2 = new NodePoint(2,"唐僧",17,"西游记");
NodePoint nodePoint3 = new NodePoint(3,"猪八戒",26,"西游记");
NodePoint nodePoint4 = new NodePoint(4,"沙悟净",30,"西游记");
NodePoint nodePoint5 = new NodePoint(4,"白骨精",32,"西游记");
doubleLinedList.add(nodePoint1);
doubleLinedList.add(nodePoint2);
doubleLinedList.add(nodePoint3);
doubleLinedList.add(nodePoint4);
doubleLinedList.update(nodePoint5);
doubleLinedList.delete(nodePoint5.sno);
doubleLinedList.list();
}
}
class DoubleLinedList{
private NodePoint head = new NodePoint(0,"",0,"");
// 遍历双向链表的方法
public void list(){
if (head.next == null) {
System.out.println("链表为空");
return ;
}
NodePoint temp = head.next;
while(temp != null){
System.out.println(temp);
temp = temp.next;
}
}
// 添加一个节点到双向链表的最后.
public void add(NodePoint node){
NodePoint temp = head;
while(temp.next != null){
temp = temp.next;
}
temp.next = node;
node.pre = temp;
}
// 修改一个节点的内容, 可以看到双向链表的节点内容修改和单向链表一样
public void update(NodePoint updatenode){
boolean flag = false;
NodePoint temp = head.next;
while(true){
if(temp == null) break;
if (temp.sno == updatenode.sno){
flag = true;
break;
}
temp = temp.next;
}
if (flag == true){
temp.name = updatenode.name;
temp.age = updatenode.age;
temp.grade = updatenode.grade;
}else
System.out.printf("要修改的 %d 节点不存在,无发修改n", updatenode.sno);
}
// 从双向链表中删除一个节点, // 说明
// 1 对于双向链表,我们可以直接找到要删除的这个节点
// 2 找到后,自我删除即可
public void delete(int sno){
if (head.next == null) {// 空链表
System.out.println("链表为空,无法删除");
return;
}
boolean flag = false;
NodePoint temp = head.next;
while (true){
if (temp == null) return ;
if (temp.sno == sno) {// 找到的待删除节点的前一个节点 temp
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.pre.next = temp.next;
//判断是否为最后一个节点,如果最后一个节点不进行判断会出现nullException
if (temp.next != null) {
temp.next.pre = temp.pre;
}
}else {
System.out.printf("要删除的 %d 节点不存在n", sno);
}
}
}
//定义Node 每个Node都是一个对象
class NodePoint{
public int sno;
public String name;
public int age;
public String grade;
public NodePoint pre;
public NodePoint next;
public NodePoint(int sno, String name, int age, String grade) {
this.sno = sno;
this.name = name;
this.age = age;
this.grade = grade;
}
public void setSno(int sno) {
this.sno = sno;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGrade(String grade) {
this.grade = grade;
}
@Override
public String toString() {
return "Student{" +
"sno=" + sno +
", name='" + name + ''' +
", age=" + age +
", grade='" + grade + ''' +
'}';
}
}
约瑟夫问题
这里我们用单循环链表进行题解。
问题
Josephu 问题为:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(11->5->3
图解
代码
package com.cz.LinkedList;
/**
* @ProjectName: Data_structure
* @Package: com.cz.LinkedList
* @ClassName: Josephu
* @Author: 张晟睿
* @Date: 2022/2/27 18:27
* @Version: 1.0
*/
public class Josephu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addChild(5);
circleSingleLinkedList.showChild();
circleSingleLinkedList.outcircle(1,2,5);
}
}
class CircleSingleLinkedList{
//创建一个first节点 当前没有编号
private Child first = null;
public void addChild(int nums) {
//定义一个临时变量存放头节点
Child curChild = null;
if (nums < 1 ){
System.out.println("nums 的值不正确,请重新输入");
}
for (int i = 1; i nums || countNum < 0 || nums < 0){
System.out.println("输入错误,请输入合适的参数");
}
Child point = first; //辅助指针 用来辅助first来进行指针的指向
while(true){
if (point.getNext() == first){
break;
}
point = point.getNext();
}
//将两个指针从startNo开始进行操作
for (int i = 0; i < startNo - 1; i++) {
first = first.getNext();
point = point.getNext();
}
while(true){
if (first == point){ //说明该环中 只有一个孩子
break;
}
for (int i = 0; i < countNum - 1; i++) {
point = point.getNext();
first = first.getNext();
}
System.out.printf("小孩%d 出圈n", first.getNo());
first = first.getNext();//first 指向下一个孩子节点
point.setNext(first);//将point的指针指向first的节点 此时first的孩子已经出圈
}
System.out.printf("最后留在圈中的小孩编号%d n", first.getNo());
}
}
class Child {
private int no;//该节点的编号
private Child next;//指向下一个节点
public Child(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Child getNext() {
return next;
}
public void setNext(Child next) {
this.next = next;
}
@Override
public String toString() {
return "Child{" +
"no=" + no +
'}';
}
}