数组结构与算法-024-029 双向链表、环形链表、约瑟夫问题

024 双向链表增删改查

使用带 head 头的双向链表实现-水浒英雄排行榜,

单向链表的缺点分析

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

在这里插入图片描述
分析 双向链表的遍历,添加,修改,删除的操作思路

  1. 遍历方和单链表,只是可以向前,也可以向后查找
  2. 添加(默认添加到双向链表的最后)
    1. 先找到双向链表的最后这个节点
    2. temp.next = newHeroNode
    3. newHeroNode.pre = temp
  3. 修改思路和原来的单向链表一样
  4. 删除
    1. 因为是双向链表,因此,可以实现自我删除某个节点
    2. 直接找到要删除的这个节点,比如是 temp,
    3. temp.pre.next = temp.next
    4. temp.next.pre = temp.pre

在这里插入图片描述

package com.old.linkedList_016_023;

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

    }
}

/**
 * 创建一个双向链表的类
 */
class DoubleLinkedList {
    /**
     * 初始化头节点,头节点不要动
     * 不放具体的数据
     */
    private HeroNode2 head = new HeroNode2(0, "", "");

    /**
     * 返回头节点
     *
     * @return
     */
    public HeroNode2 getHead() {
        return head;
    }

    /**
     * 显示链表
     */
    public void list() {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为头结点不能动,因此需要一个辅助变量来遍历
        HeroNode2 temp = head.next;
        while (true) {

            System.out.println(temp);
            //判断是否到链表最后了
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
    }

    /**
     * 添加到双向链表的最后
     */
    public void add(HeroNode2 heroNode) {
        //因为 head 节点不能动,因此需要一个辅助记功 temp
        HeroNode2 temp = head;
        //遍历链表,找到最后
        while (true) {
            //找到最后一个链表
            if (temp.next == null) {
                break;
            }
            //如果没有找到,就将 temp 后移
            temp = temp.next;
        }
        /**
         * 当退出while 循环时,temp 就指向了链表的最后
         * temp.next 是没有数据,这里就将 temp 后面衔接了起来,
         * 再将要添加的节点的前一个指向 temp ,形成了双向链表
         * 因为要添加的节点的 pre 是空,他应该指向双向链表中的最后一个
         * 而要添加的节点的 next 本来是空,他应该指向下一个要添加的节点
         * 所以可以为空
         */
        temp.next = heroNode;
        temp.next.pre = temp;
    }

    /**
     * 修改节点的信息,根据编号来修改,即 no 编号不能修改,
     * no编号修改相添加
     * <p>
     * 根据 new HeroNode 的 no来修改
     * <p>
     * 视频代码
     * <p>
     * 修改一个节点的内容,可以看到双向节点内容修改和单身链表一样
     * 只是节点类型进行了修改
     */
    public void update(HeroNode2 newHeroNode) {
        //判断是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //找到需要修改的节点,根据 no 编号
        //定义一个辅助变量
        HeroNode2 temp = head;
        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.printf("没有找到这个节点 %d, 还在修改", newHeroNode.no);
        }
    }

    /**
     * 从双向链表删除一个节点,
     * 1. 对于双向链表,我们可以直接找到要删除的这个节点
     * 2. 找到后自我删除即可
     */
    public void del(int no) {
        if (head.next == null) {
            System.out.println("链表为空,不能删除");
            return;
        }
        //辅助变量/指针
        HeroNode2 temp = head.next;
        //标志是否找到待删除的节点
        boolean flag = false;
        while (true) {
            //已经找到链表的最后了, 已经找到了链表节点最后的 next
            if (temp == null) {
                break;
            }
            if (temp.no == no) {
                //找到待删除节点的前一个节点
                flag = true;
                break;
            }
            temp = temp.next;//temp 后移,遍历
        }
        if (flag) {
            //找到,temp 就是要删除的节点
            //temp.next = temp.next.next; 这是单向链表的删除方法
            temp.pre.next = temp.next;
            /**
             * 这里代码有问题,假如要删除的节点是最后的节点
             * 最后一个节点的 next 是 null,而 null.pre 会出现 空指针异常
             * 如果是最后一个节点就不需要执行下面这行代码
             * 否则会出现空指针异常
             */
            if (temp.next != null) {
                temp.next.pre = temp.pre;
            }
        } else {
            System.out.println("要删除的节点不存在:" + no);
        }
    }
}

/**
 * 定义 HeroNode , 每个 HeroNode,对象就是一个节点
 */
class HeroNode2 {
    public int no;
    public String name;
    public String nickname;
    /**
     * 指向下一个节点
     * 默认为 null
     */
    public HeroNode2 next;

    /**
     * 指向前一个节点
     * 默认为 null
     */
    public HeroNode2 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 + '\'' +
                '}';
    }
}

026 双向链表功能测试和小结

package com.old.linkedList_016_023;

/**
 * 按照编号顺序添加
 */
public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        System.out.println("双向链表测试");
        HeroNode2 node1 = new HeroNode2(1, "宋江", "及时雨");
        HeroNode2 node2 = new HeroNode2(2, "卢俊义", "玉麒麟");
        HeroNode2 node3 = new HeroNode2(3, "吴用", "智多星");
        HeroNode2 node4 = new HeroNode2(4, "林冲", "豹子头");
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(node1);
        doubleLinkedList.add(node2);
        doubleLinkedList.add(node3);
        doubleLinkedList.add(node4);
        doubleLinkedList.list();
        node3.name = "测试名";
        doubleLinkedList.update(node3);
        System.out.println("修改后");
        doubleLinkedList.list();
        doubleLinkedList.del(3);
        System.out.println("删除后:");
        doubleLinkedList.list();
    }
}

/**
 * 创建一个双向链表的类
 */
class DoubleLinkedList {
    /**
     * 初始化头节点,头节点不要动
     * 不放具体的数据
     */
    private HeroNode2 head = new HeroNode2(0, "", "");

    /**
     * 返回头节点
     *
     * @return
     */
    public HeroNode2 getHead() {
        return head;
    }

    /**
     * 显示链表
     */
    public void list() {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为头结点不能动,因此需要一个辅助变量来遍历
        HeroNode2 temp = head.next;
        while (true) {

            System.out.println(temp);
            //判断是否到链表最后了
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
    }

    /**
     * 添加到双向链表的最后
     */
    public void add(HeroNode2 heroNode) {
        //因为 head 节点不能动,因此需要一个辅助变量 temp
        HeroNode2 temp = head;
        //遍历链表,找到最后
        while (true) {
            //找到最后一个链表
            if (temp.next == null) {
                break;
            }
            //如果没有找到,就将 temp 后移
            temp = temp.next;
        }
        /**
         * 当退出while 循环时,temp 就指向了链表的最后
         * temp.next 是没有数据,这里就将 temp 后面衔接了起来,
         * 再将要添加的节点的前一个指向 temp ,形成了双向链表
         * 因为要添加的节点的 pre 是空,他应该指向双向链表中的最后一个
         * 而要添加的节点的 next 本来是空,他应该指向下一个要添加的节点
         * 所以可以为空
         *
         * 上面看不懂了
         *
         * temp 是最后一个节点,这个节点的 next 就应该是要添加的子节点
         * 而要添加的节点的 pre 就应该是这个最后的节点
         */
        temp.next = heroNode;
//        heroNode.pre = temp;两种写法,这种也可以
        temp.next.pre = temp;
    }

    /**
     * 修改节点的信息,根据编号来修改,即 no 编号不能修改,
     * no编号修改相添加
     * <p>
     * 根据 new HeroNode 的 no来修改
     * <p>
     * 视频代码
     * <p>
     * 修改一个节点的内容,可以看到双向节点内容修改和单身链表一样
     * 只是节点类型进行了修改
     */
    public void update(HeroNode2 newHeroNode) {
        //判断是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //找到需要修改的节点,根据 no 编号
        //定义一个辅助变量
        HeroNode2 temp = head;
        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.printf("没有找到这个节点 %d, 还在修改", newHeroNode.no);
        }
    }

    /**
     * 从双向链表删除一个节点,
     * 1. 对于双向链表,我们可以直接找到要删除的这个节点
     * 2. 找到后自我删除即可
     */
    public void del(int no) {
        if (head.next == null) {
            System.out.println("链表为空,不能删除");
            return;
        }
        //辅助变量/指针
        HeroNode2 temp = head.next;
        //标志是否找到待删除的节点
        boolean flag = false;
        while (true) {
            //已经找到链表的最后了, 已经找到了链表节点最后的 next
            if (temp == null) {
                break;
            }
            if (temp.no == no) {
                //找到待删除节点的前一个节点
                flag = true;
                break;
            }
            temp = temp.next;//temp 后移,遍历
        }
        if (flag) {
            //temp.next = temp.next.next; 这是单向链表的删除方法
            /**
             * temp 就是要删除的节点
             * 既然 temp 是要删除的节点, 那么它的 上一个节点(pre)
             * 中(这里通过 pre 找到了上一个节点,即代表他当前在上一个节点)的 下一个(next,上一个节点的 next)就应该是
             * temp 之后的节点
             */
            temp.pre.next = temp.next;
            /**
             * 这里代码有问题,假如要删除的节点是最后的节点
             * 最后一个节点的 next 是 null,而 null.pre 会出现 空指针异常
             * 如果是最后一个节点就不需要执行下面这行代码
             * 否则会出现空指针异常
             *
             * 这里的意思是:要删除的节点的 下一个节点 (next) 中的 上一个节点(pre)应该是 temp 的 上一个
             */
            if (temp.next != null) {
                temp.next.pre = temp.pre;
            }
        } else {
            System.out.println("要删除的节点不存在:" + no);
        }
    }
}

/**
 * 定义 HeroNode , 每个 HeroNode,对象就是一个节点
 */
class HeroNode2 {
    public int no;
    public String name;
    public String nickname;
    /**
     * 指向下一个节点
     * 默认为 null
     */
    public HeroNode2 next;

    /**
     * 指向前一个节点
     * 默认为 null
     */
    public HeroNode2 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 + '\'' +
                '}';
    }
}

027 环形链表介绍和约瑟夫问题

josephu 问题为:设编号为1,2,…n的n个人围坐一圈,约定编号为 k (1 <= k <= n)的人从1开始报数,数到 m 的那个人出列,它的下一位又从1开始报数,数到 m 的那个人又出列,直到所有人出殡为止,由此产生出一个出队编号的序列

提示:用一个不带头结点的循环链表来处理 josephu 问题,先构成一个有 n 个结点的单循环链表,然后由 k 结点起从1开始计数,计到 m 时,对应结点从链表中删除, 然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除,算法结束

在这里插入图片描述

028-029 约瑟夫问题分析图解和实现1-2

在这里插入图片描述
构建一个单向的环形链表思路

  1. 先创建第一个节点,让 first 指向该节点,并形成环形
  2. 后面每创建一个新的节点,就把该节点,加入到已有的环形链表中即可

遍历环形链表

  1. 先让一个辅助指针(变量) curBoy ,指向first 节点
  2. 然后通过一个 while 循环遍历该环形链表即可 curBoy.next = first 结束

01 写的代码

package com.old.linkedList_016_023;

public class Joseph {
    public static void main(String[] args) {
        //测试
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.showBoy();
    }


}

/**
 * 创建一个环形的单向链表
 */
class CircleSingleLinkedList{
    /**
     * 创建 first 节点,当前没有编号
     */
    private Boy first = null;

    /**
     * 添加小孩节点,构建成一个环形链表
     *
     * nums表示要加多少个小孩
     */
    public void addBoy(int nums){
        if (nums < 1){
            System.out.println("nums不能小于1");
            return;
        }
        /**
         * 辅助指针,帮助构建环形链表
         */
        Boy curBoy = null;
        //使用 for 创建环形链表
        for (int i =1; i <= nums; i++) {

            Boy boy = new Boy(i);
            if (i == 1){
                first = boy;
                //构成环状, 让 curBoy指向第一个小孩
                first.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 = curBoy.getNext();
        }
    }

}

/**
 * 创建一个目标 boy 类,表示一个节点
 */
class Boy{
    /**
     * 编号
     */
    private int no;
    /**
     * 指向下一个节点,默认为 null
     */
    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) {
        Next = next;
    }
}

02

根据用户的输入,生成一个小孩出圈的顺序
n =5 即有5个人
k =1 即从第一个人开始报数
m =2 即数2下

  1. 需求创建一个辅助指针(变量)helper ,事先应该指向环形链表的最后这个节点
    补充: 小孩报数时,先让 first 和 helper 移动 k -1 次
  2. 当小孩报数时,让 first 和 helper 指针同时的移动 m - 1 次
  3. 这里就可以将 first 指向的小孩节点出圈
    first = first.next
    helper.next = first
    原来的 first指向的节点就没有任何引用,就会被回收

在这里插入图片描述

package com.old.linkedList_016_023;

public class Joseph {
    public static void main(String[] args) {
        //测试
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.showBoy();

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


}

/**
 * 创建一个环形的单向链表
 */
class CircleSingleLinkedList{
    /**
     * 创建 first 节点,当前没有编号
     */
    private Boy first = null;

    /**
     * 添加小孩节点,构建成一个环形链表
     *
     * nums表示要加多少个小孩
     */
    public void addBoy(int nums){
        if (nums < 1){
            System.out.println("nums不能小于1");
            return;
        }
        /**
         * 辅助指针,帮助构建环形链表
         */
        Boy curBoy = null;
        //使用 for 创建环形链表
        for (int i =1; i <= nums; i++) {

            Boy boy = new Boy(i);
            if (i == 1){
                first = boy;
                //构成环状, 让 curBoy指向第一个小孩
                first.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 = curBoy.getNext();
        }
    }

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

        //创建辅助指针
        Boy helper = first;
        while (true){
            //说明, helper 指向了最后小孩节点
            if (helper.getNext() == first){
                break;
            }
            helper = helper.getNext();
        }
        //小孩报数前,先让 first 和 helper 移动 k -1次
        for (int j = 0; j < startNo -1 ; j++){
            first = first.getNext();
            helper = helper.getNext();
        }

        /*
        当小孩报数时,让 fist 和 helper 指针同时移动 m -1 次,然后出圈
        这里是一个循环操作,直到圈中只有一个节点
        */
        while (true){
            if (helper == first){
                //说明圈中只有一个节点
                break;
            }
            //让 fist 和 helper 指针同时移动 m -1 次,然后出圈
            for (int j = 0 ; j < countNum -1; j++){
                first = first.getNext();
                helper = helper.getNext();
            }
            //这里 first 指向的节点,就是要出圈的小孩节点
            System.out.printf("小孩%d \n", first.getNo());
            //将 first 指向的小孩节点出圈
            first = first.getNext();
            helper.setNext(first);
        }
        //当退出 while 节点之后只有一个节点了
        System.out.printf("最后留在圈中的小孩编号 %d \n", helper.getNo());

    }

}

/**
 * 创建一个目标 boy 类,表示一个节点
 */
class Boy{
    /**
     * 编号
     */
    private int no;
    /**
     * 指向下一个节点,默认为 null
     */
    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) {
        Next = next;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值