链表

1、链表(Linked List)介绍

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

image.png

  1. 链表是以接地那的方式来存储的
  2. 每个结点包含 data域(存放数据),next域(指向下一个节点)
  3. 链表的各个结点不一定是连续存储
  4. 链表分带头节点的链表和不带头节点的链表
  • 单链表(带头节点)逻辑结构示意图如下

image.png

2、单链表的创建

  • 直接添加节点到链表尾部
package linkedlist;

public class SingleLinkedListDemo{
    public static void main(String[] args) {
        //测试

        //创建节点

        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(1, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(1, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(1, "林冲", "豹子头");

        //添加节点到链表

        SingleLinkedList singleLinkedList = new SingleLinkedList();
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);

        singleLinkedList.showList();
    }
}

//定义SingleLinkedList 管理英雄
class SingleLinkedList {
    //先初始化一个头结点
    private HeroNode head = new HeroNode(0,"","");

    //添加节点到单向链表
    public void add(HeroNode heroNode){
        //1.找到当前链表的最后节点
        //2.将最后这个节点的next指向新的节点

        //因为head节点不能动,因此需要一个辅助变量 temp

        //辅助变量指向指针
        HeroNode temp = head;
        //遍历链表,找到最后
        while(true){
            //找到链表的最后
            if(temp.next == null){
                break;
            }
            //如果没有找到最后,将temp后移
            temp = temp.next;
        }

        //将最后这个节点指向新的节点
        temp.next = heroNode;

    }


    //显示链表(遍历链表)
    public void showList(){
        //判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动,所以需要一个辅助变量
        HeroNode temp = head.next;

        while(true){
            //判断是否到链表的最后
            if(temp == null){
                break;
            }
            //输出节点信息
            System.out.println(temp.toString());
            //将temp后移
            temp = temp.next;
        }
    }



}

//定义HeroNode
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;
    }

    //重写toString()
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }
}

3、按照编号的顺序添加

  1. 首先找到新添加的节点的位置,通过辅助变量(指针)
  2. 新的节点.next = temp.next
  3. temp.next = 新的节点
    //按编号顺序添加节点
    public void addByOrder(HeroNode heroNode){
        //头结点不能动,需要辅助变量
        //找的temp是位于添加位置的前一个节点否则添加不了

        HeroNode temp = head;

        boolean flag = false;   //flag标志添加的编号是否存在,默认为false

        while (true){
            if (temp.next == null){   //temp已经在链表的最后
                break;
            }
            if (temp.next.no > heroNode.no){   //新结点插入位置temp 和 temp.next 之间
                break;
            }else if(temp.next.no == heroNode.no){   //添加的新节点的编号已经存在
                flag = true;
                break;
            }

            temp = temp.next;   //后移
        }

        if (flag){   //flog == true 编号已经存在 不能添加
            System.out.println("准备插入英雄的编号已经存在");
        }else{
            //新节点插入到链表中
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

4、修改指定节点信息

    //修改节点信息
    public void update(HeroNode newHeroNode){
        //判断链表是否为空
        if (head.next ==null){
            System.out.println("链表为空");
            return;
        }

        //找到需要修改的结点,根据no编号
        //定义一个辅助变量

        HeroNode temp = head.next;
        boolean flag = false;   //是否找到该节点
        while (true){
            if (temp == null){
                break;   //已经遍历完链表
            }
            if(temp.no == newHeroNode.no){   //找到需要修改的节点
                flag = true;
                break;
            }
            temp = temp.next;   //指向下一个节点
        }

        //根据flag判断是否找到结点
        if (flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }else{
            //没有找到
            System.out.println("没有找到编号为"+newHeroNode.no+"的结点,不能修改");
        }
    }

5、删除单链表一个节点

  1. 先找到需要删除的前一个结点
  2. temp.next = temp.next.next
  3. 被删除的节点,将不会有其他引用指向,会被垃圾回收机制回收
    //删除节点
    public void delete(int no){
        //判断链表是否为空
        if(head.next == null ){
            System.out.println("链表为空");
            return;
        }

        //辅助变量
        HeroNode temp = head;
        boolean flag = false;  //是否找到该节点

        while (true){
            if (temp.next == null){   //已经遍历完链表
                break;
            }
            if (temp.next.no == no){   //找到节点
                flag = true;
                break;
            }
            temp = temp.next;   //temp后移
        }

        //根据flag判断是否找到结点
        if (flag){
            temp.next = temp.next.next;  //删除temp.next结点
        }else{
            //没有找到
            System.out.println("没有找到编号为"+no+"的结点,不能删除");
        }
    }

6、单链表的反转

    //单链表的反转
    public static void reversal(HeroNode head){
        //如果当前单链表为空,或者只有一个节点,无需反转,直接返回
        if (head.next == null ||head.next.next == null){
            return;
        }

        //定义一个辅助变量
        HeroNode temp = head.next;
        HeroNode next = null;   //指向当前节点temp的下一个节点
        HeroNode reversalHead = new HeroNode(0,"","");
        //遍历原来的节点,每遍历一个节点,就将其取出,并放在新的链表reversalHead的最前端

        while (temp != null){
            next = temp.next;
            temp.next = reversalHead.next;
            reversalHead.next = temp;
            temp = next;
        }

        //将head.next指向reversalHead.next,实现单链
        head.next = reversalHead.next;
    }

7、双向链表

  • 单向链表查找的方向只能是一个方向,而双向链表可以向前或者向后查找
  • 单向链表不能自我删除,需要靠辅助节点,而双向链表可以自我删除

  1. 遍历和单向链表一样,只是可以向前,也可以向后查找
  2. 添加(添加到双向链表的最后)
    1. 先找到双向链表的最后一个节点
    2. temp.next = newNode
    3. newNode.pre = temp
  3. 修改和单向链表一样
  4. 删除
    1. 找到需要删除的节点temp
    2. temp.pre.next = temp.next
    3. temp.next.pre = temp.pre
package linkedlist;

public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        //测试

        System.out.println("********双向链表的测试********");

        //创建节点
        Node node01 = new Node(1, "cml01", "youmo01");
        Node node02 = new Node(2, "cml02", "youmo02");
        Node node03 = new Node(3, "cml03", "youmo03");
        Node node04 = new Node(4, "cml04", "youmo04");

        //创建双向链表并添加节点
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(node01);
        doubleLinkedList.add(node02);
        doubleLinkedList.add(node03);
        doubleLinkedList.add(node04);

        //遍历链表
        System.out.println("********遍历双向链表********");
        doubleLinkedList.list();

        //修改节点
        System.out.println("********修改节点********");
        Node updateNode = new Node(4,"updateNode","updateNode");
        doubleLinkedList.update(updateNode);
        doubleLinkedList.list();

        //删除节点
        System.out.println("********删除节点********");
        doubleLinkedList.delete(4);
        doubleLinkedList.list();



    }
}

//定义双链表
class DoubleLinkedList{
    //初始化头结点
    private Node head = new Node(0,"","");

    //遍历双向链表
    public void list(){
        //判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }

        //定义辅助变量temp
        Node temp = head.next;

        while(true){
            //判断是否到链表最后
            if(temp == null){
                break;
            }
            //输出节点信息
            System.out.println(temp.toString());
            //temp 后移
            temp = temp.next;
        }
    }

    //添加节点到链表最后
    public void add(Node node){
        //定义辅助变量
        Node temp = head;

        //遍历链表
        while (true){
            if (temp.next == null){   //遍历到链表最后
                break;
            }

            //temp向后移
            temp = temp.next;
        }

        //添加新的节点
        temp.next = node;
        node.pre = temp;
    }

    //修改节点的内容
    public void update(Node node){

        //判断链表是否为空
        if (head.next == null){
            return;
        }

        //定义辅助变量temp
        Node temp = head.next;
        boolean flag = false;  //表示是否找到需要修改的节点

        while (true){
            if (temp == null){
                return;
            }
            if (temp.no == node.no){
                flag=true;
                break;
            }

            //temp后移
            temp = temp.next;
        }

        //根据flag判断是否找到需要修改的节点
        if (flag){   //找到
            temp.name = node.name;
            temp.nickname  = node.name;
        }else{   //没有找到
            System.out.println("没有找到需要修改的结点"+node.no);
        }
    }

    //删除节点
    public void delete(int no){

        //判断链表是否为空
        if (head.next ==null) {
            System.out.println("链表为空");
            return;
        }

        //定义辅助变量
        Node temp = head.next;

        boolean flag = false;  //表示是否找到需要删除的节点

        while (true){
            if (temp == null){   //链表遍历到最后
                break;
            }

            if(temp.no == no){   //找到需要删除的节点
                flag = true;
                break;
            }

            temp = temp.next;
        }

        //根据flag判断是否找到需要删除的节点
        if (flag){   //找到
            //删除节点temp
            temp.pre.next = temp.next;

            //如果是最后一个节点,不需要执行下面一步操作
            if (temp.next != null){
                temp.next.pre = temp.pre;
            }

        }else{
            System.out.println("没有找到节点"+no);
        }
    }
}

//定义Node节点
class Node{
    public int no;
    public String name;
    public String nickname;
    public Node pre;    //指向前一个节点,默认为null
    public Node next;   //指向后一个节点,默认为null

    //构造器
    public Node(int no,String name,String nickname){
        this.no = no;
        this.name =name;
        this.nickname = nickname;
    }

    //重写toString()
    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + '\'' +
                '}';
    }
}

8、单向环形链表(约瑟夫问题)

  • 单向环形链表应用场景
  • Josephu(约瑟夫、约瑟夫环)问题
  • Josephu 问题为:设编号为 1,2,... n 的 n 个人围坐一圈,约定编号为 k(1 <= k <= n)的人从 1 开始报数,数到 m 的那个人出列,他的下一为又从 1 开始报数,数到 m 的那个人又出列,一次类推,直到所有人出列为止,由此产生一个出队编号的序列
  • 用一个不带头节点的循环链表来处理Josephu问题,先构成一个由n个节点的单循环链表,然后由 k 节点开始从 1 开始计数,记到 m 时,对应节点从链表中删除,然后再从被删除节点的下一个节点又从 1 开始计数,直到最后一个节点从链表中删除算法结束

  • 构造单向环形链表思路
    1. 先创建第一个节点,让frist指向该结点
    2. 当我们每创建一个新的节点,就把该结点加入到已有的环形链表中即可
  • 遍历环形链表
    1. 先让一个辅助变量 temp 指向frist结点
    2. 通过while循环遍历该环形链表
    3. temp.next = frist 遍历结束
package linkedlist;

public class Josephu {
    public static void main(String[] args) {
        //测试

        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);  //加入5个小孩节点
        circleSingleLinkedList.list();

        circleSingleLinkedList.countBoy(1,2,5);
    }
}

//创建一个环形的单向链表
class CircleSingleLinkedList{
    //创建一个first节点,为空
    public Boy first = null;

    //添加节点,构成一个环形的链表  num 小孩节点个数
    public void addBoy(int num){
        if (num < 1){
            System.out.println("num < 1 ");
            return;
        }

        //创建赋值变量temp
        Boy temp = null;

        for (int i=1;i<=num;i++){
            //根据编号创建小孩节点
            Boy boy = new Boy(i);
            //如果是第一个小孩
            if(i==1){
                first  = boy;
                first.next = first;  //first next 指向自己构成环状
                temp = first;   //让temp变量指向first
            }else {
                temp.next = boy;   //temp 指向 新节点
                boy.next = first;  //新节点指向first节点 形成环状
                temp = temp.next;   //temp 后移
            }

        }

    }

    //遍历当前环形链表
    public void list(){
        //判断链表是否为空
        if (first == null){
            System.out.println("链表为空");
            return;
        }

        //创建赋值变量temp
        Boy temp = first;

        while (true){
            System.out.println(temp.no);
            if(temp.next == first){  //已经遍历完成
                break;
            }
            temp = temp.next;
        }
    }

    //根据用户的输入,计算出小孩出圈的顺序
    /**
     * @param startNo 表示从第几个小孩可是数数
     * @param countNum 表示数几下
     * @param num 表示最初有多少小孩在圈中
     * */
    public void countBoy(int startNo,int countNum,int num){
        //先对数据进行校验
        if (first == null ||startNo <1 || startNo >num){
            System.out.println("输入有误");
            return;
        }

        //创建辅助变量temp,指向最后一个节点
        Boy temp = first;
        while (true){
            if (temp.next == first){
                break;
            }
            temp = temp.next;
        }

        //小孩报数前,先让first 和 temp 移动 startNo-1 次
        //使first指向开始数数的孩子节点,temp指向开始数数的孩子节点的上一个节点

        for (int i = 0;i<startNo -1;i++){
            first = first.next;
            temp = temp.next;
        }

        while (true){
            if (temp == first){   //说明圈中只有一个孩子节点
                break;
            }

            //让first 和 temp 指针同时移动 countNum - 1
            for (int i= 0;i< countNum -1;i++){
                first = first.next;
                temp = temp.next;
            }

            //这时first指向的节点就是出圈的小孩节点
            System.out.println("小孩"+first.no+"出圈");
            //小孩出圈,删除该小孩节点
            first = first.next;
            temp.next = first;
        }

        System.out.println("最后留在圈中的小孩:"+temp.no);
    }
}

//创建Boy节点类
class Boy{
    public int no;   //编号
    public Boy next;   //指向下一个节点默认null

    public Boy(int no){
        this.no = no;
    }
}

posted @ 2022-11-06 22:36  youmo~  阅读(61)  评论(0编辑  收藏  举报