Java实现链表

链表介绍:

链表是有序的列表,但是他在内存中是存储如下

Snipaste_2023-07-21_18-25-26

小结:

1.链表是以节点的方式来存储,是链式存储

2.每个节点包含data域:存数据,next域:指向下一个节点

3.如图:发现链表的各个节点不一定是连续存储

4.链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

单链表的创建

添加(当不考虑标号顺序时):

1.先创建一个head头节点,作用就是表示单链表的头

2.后面我们每添加一个节点,就直接加入到链表的最后

遍历:

通过一个辅助变量,帮助遍历整个链表

代码实现:
//定义一个HeroNode,每个HeroNode对象就是一个节点
public class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next;//指向下一个节点

    //构造器
    public HeroNode(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 + '\'' +
                '}';
    }
}
//定义SingleLinkedList管理我们的英雄
public class SingleLinkedList {
    //先初始化一个头节点,头节点不要动,不存放具体的数据
    private HeroNode head = new HeroNode(0, "", "");

    //添加节点到单向链表
    //思路:当不考虑标号顺序时
    //1.找到当前链表的最后节点
    //2.将最后这个节点的next指向新的节点
    public void add(HeroNode heroNode) {
        //因为head节点不能动,因此我们需要一个辅助变量temp
        HeroNode temp = head;
        //遍历链表,找到最后
        while (true) {
            //找到最后
            if (temp.next == null) {
                break;
            }
            //如果没有找到,将temp后移
            temp = temp.next;
        }
        //当退出while循环时,temp就指向了链表的最后
        temp.next = heroNode;

    }

    //显示链表[遍历]
    public void list() {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动,因此需要一个辅助变量来遍历
        HeroNode temp = head.next;
        while (true) {
            //判断是否到最后
            if (temp == null) {
                break;
            }
            //输出节点的信息
            System.out.println(temp);
            //将temp后移
            temp = temp.next;
        }
    }
}
public static void main(String[] args) {
    HeroNode heroNode1 = new HeroNode(1, "aa", "11");
    HeroNode heroNode2 = new HeroNode(2, "bb", "22");
    HeroNode heroNode3 = new HeroNode(3, "cc", "33");
    SingleLinkedList singleLinkedList = new SingleLinkedList();
    singleLinkedList.add(heroNode1);
    singleLinkedList.add(heroNode2);
    singleLinkedList.add(heroNode3);
    singleLinkedList.list();
}

添加(当考虑标号顺序时):

根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)

思路分析

1.首先找到新添加节点的位置,是通过辅助变量,通过遍历来找到

2.新的节点.next=temp.next

3.将temp.next=新的节点

代码实现
//第二种方式在添加英雄时,根据排名将英雄插入指定的位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOrder(HeroNode heroNode) {
    //因为head节点不能动,因此我们需要一个辅助变量temp
    //因为单链表,因此我们找到的temp是位于添加位置的前一个节点,否则插入不了
    HeroNode temp = head;
    boolean flag = false;//flag标志添加的标记是否存在,默认为false
    while (true) {
        if (temp.next == null) {//说明temp已经在链表的最后
            break;
        }
        if (temp.next.no > heroNode.no) {//位置找到,就在temp的后面插入
            break;
        } else if (temp.next.no == heroNode.no) {//说明希望添加的对象已经存在
            flag = true;
            break;
        }
        temp = temp.next;
    }
    //判断flag的值
    if (flag) {
        System.out.println("准备添加的英雄已经存在\n" + heroNode.no);
    }else{
        //插入到链表中,temp的后面
        heroNode.next = temp.next;
        temp.next = heroNode;
    }
}

修改

//修改节点的信息
public void update(HeroNode newHeroNode) {
    //判断是否为空
    if (head.next == null) {
        System.out.println("链表为空");
    }
    //找到需要修改的节点,根据no编号
    HeroNode temp = head;
    boolean flag = false;
    while (true) {
        if (temp == null) {
            break;
        }
        if (temp.no == newHeroNode.no) {
            flag = true;
            break;
        }
        temp = temp.next;
    }
    if (flag) {
        temp.name = newHeroNode.name;
        temp.nickname = newHeroNode.nickname;
    } else {
        System.out.println("没有找到编号" + newHeroNode.no);
    }
}

删除

1.我们先找到需要删除的这个节点的前一个节点temp

2.temp.next=temp.next.next

3.被删除的节点,将不会有其他引用指向,会被垃圾回收机制回收

//删除
public void deleteDate(int no) {
    if (head.next == null) {
        System.out.println("链表为空");
        return;
    }
    HeroNode temp = head;
    Boolean flag = false;
    while (true) {
        if (temp == null) {
            break;
        }
        if (temp.next.no == no) {
            flag = true;
            break;
        }
        temp = temp.next;
    }
    if (flag) {
        temp.next = temp.next.next;
    }else{
        System.out.println("没有这个数据" + no);
    }
}

例题

1.求单链表中有效节点的个数

获取单链表的节点的个数(如果是带头节点的链表,需要不统计头节点)

/**
 *
 * @param head 链表的头节点
 * @return 返回的就是有效节点的个数
 */
public static int getLength(HeroNode head) {
    int num = 0;
    if (head.next == null) {
        return 0;
    }
    HeroNode temp = head.next;
    while (temp != null) {
        num++;
        temp = temp.next;
    }
    return num;
}
2.查找单链表中倒数第K个节点
  1. 编写一个方法,接收head节点,同时接收一个index
  2. index表示倒数第index个节点
  3. 先求出链表的总长度
  4. 得到size后,我们从链表的第一个开始遍历(size-index)个,就可以得到
  5. 如果找到了,则返回该节点,否则返回null
public static HeroNode findLastIndexNode(HeroNode head, int index) {
    if (head.next == null) {
        return null;
    }
    int size = getLength(head);
    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.将链表反转
思路:

1.先定义一个节点reversHead=new HeroNode();

2.从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表最前端

3.原来的链表head.next=reverseHead.next

Snipaste_2023-07-25_17-43-04

代码:
//将链表反转 
public void reverseList(HeroNode head) {
    //如果链表为空,或者只有一个节点,无需反转,直接返回
    if (head.next == null || head.next.next == null) {
        return;
    }
    HeroNode reverseHead = new HeroNode(0, "", "");
    //指向当前链表的下一个节点
    HeroNode next = null;
    //定义一个辅助变量,帮助我们遍历原来的链表
    HeroNode temp = head.next;
    while (temp != null) {
        next = temp.next;//暂时保存当前节点的下一个节点,因为后面需要
        temp.next = reverseHead.next;//将temp的下一个节点指向新的链表的最前端
        reverseHead.next = temp;//将temp连接到新的链表上
        temp = next;//让temp后移
    }
    //将head.next指向reverseHead.next,实现单链表的反转
    head.next = reverseHead.next;
}
4.从尾到头打印单链表

思路:

  1. 要求逆序打印单链表
  2. 方式1:先将单链表进行反转操作,然后再遍历即可,这样的做的问题是会破坏原来的单链表的结构(不建议)
  3. 方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进先出的特点,就实现了逆序打印的效果
栈的演示:
import java.util.Stack;

public class TestStack {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        //入栈
        stack.add("jack");
        stack.add("tom");
        stack.add("smith");
        //出栈
        while (stack.size() > 0) {
            System.out.println(stack.pop());//pop就是将栈顶的数据取出
        }
    }
}
代码
public void reverseShow(HeroNode head) {
    if (head.next == null ) {
        return;//空链表,不能打印
    }
    //创建一个栈,将各个节点压入栈
    Stack<HeroNode> stack = new Stack<>();
    HeroNode temp = head.next;
    //将链表的所有节点压入栈
    while (temp != null) {
        stack.add(temp);
        temp = temp.next;
    }
    //将栈中的节点进行打印
    while (stack.size() > 0) {
        System.out.println(stack.pop());
    }
}
5.合并两个有序的单链表,合并之后的链表依然有序
public static HeroNode merge(HeroNode head1, HeroNode head2) {
    //若链表1为空,返回链表2的头结点
    if (head1.next == null && head2.next != null) {
        return head2;
    }
    //若链表2为空,返回链表1的头结点
    if (head1.next != null && head2.next == null) {
        return head1;
    }
    //创建一个新的头结点
    HeroNode newHeroNode = new HeroNode(0, "", "");
    HeroNode temp1 = head1.next;
    HeroNode temp2 = head2.next;
    HeroNode temp3 = newHeroNode;
    //从第一个数据节点比较no值,小的放到新链表的后面,no即为每个节点的顺序值
    while (temp1 != null && temp2 != null) {
        if (temp1.no < temp2.no) {
            temp3.next = temp1;
            temp1 = temp1.next;
        } else {
            temp3.next = temp2;
            temp2 = temp2.next;
        }
        temp3 = temp3.next;
    }
    //如果链表2已经合并完,将链表1其余的元素进行合并
    if (temp2 == null) {
        while (temp1 != null) {
            temp3.next = temp1;
            temp1 = temp1.next;
        }
    }
    //如果链表1已经合并完,将链表2其余的元素进行合并
    if (temp1 == null ) {
        while (temp2 != null) {
            temp3.next = temp2;
            temp2 = temp2.next;
        }
    }
    return newHeroNode;
}

单向链表的缺点分析

  1. 单向链表,查找的方向只能是一个方向,而双向链表可以向前后者向后查找
  2. 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除节点时,总是找到temp的下一个节点来删除的

双链表

image-20231013222852525

创建HeroNode类

public class HeroNode2 {
    int no;
    String name;
    String nickname;
    HeroNode2 next;
    HeroNode pre;//指向前一个节点


    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 + '\'' +
                '}';
    }

}

遍历

遍历方法和单链表一样,只是可以向前,也可以向后查找

//显示链表[遍历]
public void list() {
    if (head.next == null) {
        System.out.println("链表为空");
        return;
    }
    HeroNode2 temp = head.next;
    while (true) {
        if (temp == null) {
            break;
        }
        System.out.println(temp);
        temp = temp.next;
    }
}

添加(默认添加到双向链表的最后)

1.先找到双向链表的最后这个节点

2.temp.next=newHeroNode;

3.newHeroNode.pre=temp;

//添加一个节点到双向链表到最后
public void add(HeroNode2 heroNode) {
    HeroNode2 temp = head;
    while (true) {
        if (temp.next == null) {
            break;
        }
        temp = temp.next;
    }
    //当退出while循环时,temp就指向了链表的最后
    //形成一个双向链表
    temp.next = heroNode;
    heroNode.pre = temp;
}

添加(按照编号顺序)

public void addByOrder(HeroNode2 heroNode) {
    HeroNode2 temp = head;
    boolean flag = false;
    while (true) {
        if (temp.next == null) {
            //到链表最后,直接添加
            temp.next = heroNode;
            heroNode.pre = temp;
            return;
        }
        if (temp.next.no > heroNode.no) {
            break;
        } else if (temp.next.no == heroNode.no) {
            flag = true;
            break;
        }
        temp = temp.next;
    }
    if (flag) {
        System.out.println("准备添加的英雄已经存在\n" + heroNode.no);
    } else {
        heroNode.next = temp.next;
        temp.next.pre = heroNode;
        temp.next = heroNode;
        heroNode.pre = temp;
    }
}

修改

思路和原理与单向链表一样

public void update(HeroNode heroNode) {
    //判断是否为空
    if (head.next == null) {
        System.out.println("链表为空");
    }
    HeroNode temp = head;
    boolean flag = false;
    while (true) {
        if (temp.next == null) {
            break;
        }
        if (temp.next.no == heroNode.no) {
            flag = true;
            break;
        }
        temp = temp.next;
    }
    if (flag) {
        heroNode.next = temp.next.next;
        temp.next = heroNode;
    } else {
        System.out.println("没有找到该号码");
    }

}

删除

1.因为是双向链表,因此,我们可以实现自我删除某个节点

2.直接找到要删除的这个节点,比如temp

3.temp.pre.next=temp.next

4.temp.next.pre=temp.pre

public void delete(int no) {
    if (head.next == null) {
        System.out.println("链表为空");
        return;
    }
    HeroNode2 temp = head.next;
    while (true) {
        if (temp == null) {
            break;
        }
        if (temp.no == no) {
            //找到待删除的节点
            temp.pre.next = temp.next;
            //如果是最后一个节点,就不需要执行下面这句话,否则出现空指针异常
            if (temp.next != null) {
                temp.next.pre = temp.pre;
            }
            return;
        }
        temp = temp.next;
    }
    System.out.println("没有找到");
}

单向环形链表

image-20231014151607241

约瑟夫问题

设编号为1.2…n的n个人围坐一圈,约定编号为看(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为此产生一个出队列的序号

创建Boy节点

package com.链表;

public class Boy {
    private int no;
    private Boy next;

    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;
    }
}

构建一个单向的环形链表思路

1.先创建第一个节点,让first指向该节点,并形成环形

2.后面当我们每创建一个新节点,就把该节点加入到已有的环形链表中即可

package com.链表;

public class CircleSingleLinkedList {
    //创建一个first节点,当前没有编号
    private Boy first = null;

    //添加节点,构成一个环形的链表
    public void addBoy(int nums) {
        //nums做一个数据校验
        if (nums < 1) {
            System.out.println("nums数据不正确");
        }
        Boy lastBoy = null;//辅助指针,帮助构建环形链表
        //使用for来创建我们的环形链表
        for (int i = 1; i <= nums; i++) {
            Boy boy = new Boy(i);
            //根据编号,创建节点
            if (i == 1) {
                first = boy;
                first.setNext(first);//构成环
                lastBoy = first;//让curBoy指向第一个节点
            }else{
                lastBoy.setNext(boy);
                boy.setNext(first);
                lastBoy = boy;
            }
        }
    }
}

遍历

1.先让一个辅助指针(变量)lastBoy,指向first节点

2.然后通过一个while(循环)遍历该环形链表即可,lastBoy.next ==first结束

public void showBoy() {
    //判断链表是否为空
    if (first == null) {
        System.out.println("链表为空");
        return;
    }
    //辅助指针,帮助构建环形链表
    Boy lastBoy = first;
    while (true) {
        System.out.println(lastBoy);
        if (lastBoy.getNext() == first) {
            ///说明遍历成功
            break;
        }
        lastBoy = lastBoy.getNext();
    }
}

约瑟夫问题解答

出队

1.创建一个helper辅助指针,事先应该指向环形链表的最后这个节点。

补充:小孩报数前,先让first和helper移动k-1次(移动到第一个开始报数的人)

2.当开始报数时,让first和helper指针同时移动m-1次

3.这啥就可以将first指向的节点出队

first=first.next

helper.next=first

4.原来first指向的节点就会被回收

代码实现
/**
 *
 * @param startNo 表示第几个小孩开始数数
 * @param countNum 表示数几下
 * @param nums 表示最初有几个小孩
 */
public void countBoy(int startNo, int countNum, int nums) {
    //先对数据进行校验
    if (first == null ||  startNo > nums) {
        System.out.println("参数有误");
        return;
    }
    //创建辅助指针
    Boy helper = first;
    //创建一个helper辅助指针,事先应该指向环形链表的最后这个节点
    while (helper.getNext() != first) {
        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();
        }
        System.out.println(first);//将出队的人打印
        first = first.getNext();
        helper.setNext(first);

    }
    System.out.println("最后的是" + first);

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ww0110.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值