三、Java数据结构与算法---线性表(链表)(讲解与代码)

2.链表

链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
按照面向对象的思想,我们可以设计一个类,来描述结点这个事物,用一个属性描述这个结点存储的元素,用来另外一个属性描述这个结点的下一个结点。

结点API设计:
在这里插入图片描述

2.1 单向链表

单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。
在这里插入图片描述

2.1.1 单向链表API设计

在这里插入图片描述

2.1.2 单向链表代码实现

下面展示一些 单向链表代码实现

import java.util.Iterator;

/**
 * @author shkstart
 * @create 2021-08-10 14:45
 */
public class LinkList<T> implements Iterable<T>{
    //成员变量
    private Node head;//记录首结点
    private int Num;//记录链表的长度


    //Node不需要添加泛型<T>,会导致item的结果与LinkList的泛型结果出现紊乱
    private class Node {
        //存储元素
        T item;
        //指向下一个节点
        Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }

    }

    //添加单向链表的构造器,对首结点和链表长度进行初始化
    public LinkList() {
        this.head = new Node(null,null);
        Num = 0;
    }

    //空置线性表
    public void clear() {
        head.next = null;
        head.item = null;
        Num = 0;
    }

    //判断线性表是否为空,是就返回true,反之false
    public boolean isEmpty() {
        return Num == 0;
    }

    //获取线性表中的元素个数
    public int length() {
        return Num;
    }

    //读取并返回线性表中的第i个元素的值
    public T get(int i) {
        //需要对链表中的元素进行遍历,引入Iterator接口
        if (i < 0 || i > Num){
            throw new IllegalArgumentException("输入索引有误!");
        }

        Node n = head;
        for (int index = 0; index <= i; index++) {
            n = n.next;

        }
        return n.item;
    }

    //往线性表中添加了一个元素
    public void insert(T t){
        Node n = head;
        while (n.next != null){
            n = n.next;

        }

        Node newNode = new Node(t,null);
        n.next = newNode;

        //链表长度加1
        Num++;

    }

    //在线性表的第i个元素之前插入一个值为t的数据元素
    public void insert(int i,T t){
        if (i < 0 || i > Num){
            throw new RuntimeException("插入的数据不合法");
        }

        Node prev = head;
        for (int index = 0; index < i; index++) {
            prev = prev.next;
        }
        //位置i的结点,现i的结点cur指向原有i的下一个结点
        Node cur = prev.next;

        //构建新的结点让新的结点指向位置i的结点
        Node newNode = new Node(t, cur);

        //让之前的结点指向新结点
        prev.next = newNode;

        //元素数量加1
        Num++;

    }


    //删除并返回线性表中第i个元素
    public T remove(int i){
        if (i < 0 || i >= Num) {
            throw new IllegalArgumentException("删除的元素不存在!");
        }

        Node prev = head;
        for (int index = 0; index < i; index++) {
            prev = prev.next;
        }
        //原i位置删除,原i-1指向i+1位置,现在i的位置cur,由原来的i+1替代
        Node cur = prev.next;
        prev.next = cur.next;


        //元素个数减1
        Num--;



        return cur.item;

    }

    //返回线性表中首次出现的指定的数据元素的位序号,若不存在返回-1
    public int indexOf(T t){
        Node prev = head;
        for (int index = 0;prev.next != null; index++) {
            prev = prev.next;
            if (prev.item.equals(t)){
                return index;
            }
        }
        return -1;
    }

    //对接口中的iterator方法进行重写,对集合中的元素进行遍历
    @Override
    public Iterator<T> iterator() {
        return new LinkIterator();
    }

    private class LinkIterator implements Iterator<T> {
        //建立一个节点的成员变量
        private Node n;

        //使用构造器初始化首结点为head
        public LinkIterator() {
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public T next() {
            n = n.next;
            return n.item;
        }
    }
}

下面展示一些 单向链表代码测试

import Algorithm.LinkTable.LinkList;

/**
 * @author shkstart
 * @create 2021-08-10 14:45
 */
public class LinkListTest {
    public static void main(String[] args) throws Exception{
        LinkList<String> linkList = new LinkList<>();
        linkList.insert(0,"张三");
        linkList.insert(1,"李四");
        linkList.insert(2,"王五");
        linkList.insert(3,"小六");
        linkList.insert("七宝");

        //测试length方法
        for (String s:linkList){
            System.out.println(s);
        }


        System.out.println(linkList.length());

        System.out.println("----------");

        //检测get方法
        String s = linkList.get(2);
        System.out.println(s);

        //测试remove方法
        System.out.println("----------");
        String removeResult = linkList.remove(2);
        System.out.println(removeResult);

        System.out.println(linkList.length());

        System.out.println("----------");
        for (String s1 : linkList){
            System.out.println(s1);
        }

        System.out.println("----------");
        System.out.println(linkList.indexOf("李四"));

        System.out.println("----------");
        linkList.clear();
        System.out.println(linkList.isEmpty());







    }
}

2.2 双向链表

双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。
在这里插入图片描述
按照面向对象的思想,我们需要设计一个类,来描述结点这个事物。由于结点是属于链表的,所以我们把结点类作为链表类的一个内部类来实现

2.2.1 结点API设计

在这里插入图片描述

2.2.2 双向链表API设计

在这里插入图片描述

2.2.3 双向链表代码实现

下面展示一些 双向链表代码实现

package LinkTable;

import java.util.Iterator;

/**
 * @author shkstart
 * @create 2021-08-11 19:29
 */
public class TwoWaysLink<T> implements Iterable<T>{
    private Node head;//记录首节点
    private Node last;//记录尾结点
    private int Num;//记录双向链表长度


    //初始化构造器
    public TwoWaysLink() {
        this.head = new Node(null,null,null);
        this.last = null;
        Num = 0;
    }

    //空置线性表
    public void clear() {
        head.next = null;
        head.item = null;
        head.pre = null;
        last = null;
        Num = 0;

    }

    //判断线性表是否为空
    public boolean isEmpty() {
        return Num == 0;

    }

    //获取线性表中的元素的个数
    public int length() {
        return Num;
    }

    //读取并返回线性表中第i个元素的值
    public T get(int i){
        if (i < 0 || i > Num){
            throw new RuntimeException("获取的索引有误!");
        }
        Node cur = head.next;
        //找到位置i的前i个元素,通过遍历实现
        for (int index = 0; index < i; index++) {
            cur = cur.next;
        }

        return cur.item;


    }

    //往线性表中添加一个元素
    public void insert(T t){
        if (isEmpty()){
            //如果链表为空
            //创建新的结点
            Node newNode = new Node(t, head, null);
            //让新结点成为尾结点
            last = newNode;
            //让头结点指向尾结点
            head.next = last;
        }else{
            //链表如果不为空
            Node oldLast = last;

            //创建新的结点
            Node newNode = new Node(t, oldLast, null);
            //让当前的结尾指向新的结点
            oldLast.next = newNode;
            //让新结点成为尾结点
            last = newNode;
        }
        //元素加1
        Num++;

    }

    //在线性表中的第i个元素之前插入一个值为t的数据元素

    public void insert(int i,T t){
        Node pre = head;
        for (int index = 0; index < i; index++) {
            pre = pre.next;
        }

        //找到原有i位置,现向后移一位
        Node cur = pre.next;
        //建立新i位置的新节点
        Node newNode = new Node(t, pre, cur);

        //让原i位置的前一个结点的下一个节点变为现i结点
        pre.next = newNode;
        //原i节点的前一个结点变为现i结点
        cur.pre = newNode;

        //元素个数加1
        Num++;

    }

    //删除并返回线性表中的第i个元素
    public T remove(int i){
        //找到原i位置的结点
        Node pre = head;
        for (int index = 0; index < i; index++) {
            pre = pre.next;

        }
        //原i节点的是原i - 1的下一个结点
        Node cur = pre.next;
        //现i节点是原i结点的下一个结点
        Node nextNode = cur.next;
        //原i -1结点的下一个结点是现i结点
        pre.next = nextNode;
        //现i节点的上结点是原i - 1结点
        nextNode.pre = pre;

        //元素个数减1
        Num--;

        return cur.item;
    }

    //返回线性表中首次出现的指定的数据元素的位序号,若不存在,返回-1
    public int indexOf(T t){
        Node n = head;
        for (int index = 0;n.next != null; index++) {
            n = n.next;
            if (n.item.equals(t)){
                return index;
            }
        }
        return - 1;


    }

    //获取第一个元素
    public T getFirst(){
        if (isEmpty()){
            return null;
        }
        return head.next.item;
    }

    //获取最后一个元素
    public T getLast() {
        if (isEmpty()) {
            return null;
        }
        return last.item;
    }


    @Override
    public Iterator<T> iterator() {
        return new TwoLinkIterator();
    }

    private class TwoLinkIterator implements Iterator<T> {

        private Node n;
        //初始化构造器,使得元素从head起始
        public TwoLinkIterator() {
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return this.n.next != null;
        }

        @Override
        public T next() {
            //head不算链表中的元素
            n = n.next;
            return n.item;
        }
    }

    //创建Node的对象
    class Node{
        public Node pre;//指向上一个节点
        public T item; //存储数据
        public Node next;//指向下一个节点


        public Node() {

        }

        public Node(T item, Node prev,Node next) {
            this.item = item;
            this.next = next;
            this.pre = prev;
        }

    }
}






下面展示一些 双向链表代码功能测试

package LinkTableTest;


import LinkTable.TwoWaysLink;

/**
 * @author shkstart
 * @create 2021-08-11 19:29
 */

//创建双向列表对象
public class TwoWaysLinkTest {
    public static void main(String[] args) {
        //创建双向链表对象
        TwoWaysLink<String> s1 = new TwoWaysLink<>();

        //测试插入
        s1.insert("姚明");
        s1.insert("科比");
        s1.insert("麦迪");
        s1.insert(1,"詹姆斯");

        for (String s : s1) {
            System.out.println(s);
        }

        System.out.println("--------------------------------------");
        System.out.println("第一个元素是:"+s1.getFirst());
        System.out.println("最后一个元素是:"+s1.getLast());

        System.out.println("------------------------------------------");

        //测试获取
        String getResult = s1.get(1);
        System.out.println("获取索引1处的结果为:"+getResult);
        //测试删除
        String removeResult = s1.remove(0);
        System.out.println("删除的元素是:"+removeResult);
        //测试清空
        s1.clear();
        System.out.println("清空后的线性表中的元素个数为:"+s1.length());

    }

}

2.2.4 java中LinkedList实现

java中LinkedList集合也是使用双向链表实现,并提供了增删改查等相关方法
1.底层使用用双向链表实现;
2.结点类是否有三个域

2.3 链表的复杂度分析

get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)
insert(int i,T t):每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n);
remove(int i):每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)
相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换。
相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表

2.4 链表反转(面试高频)

单链表的反转,是面试中的一个高频题目。
需求:
原链表中数据为:1->2->3>4
反转后链表中数据为:4->3->2->1

反转API:
在这里插入图片描述
使用递归可以完成反转,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。
在这里插入图片描述
在这里插入图片描述
下面展示一些 内联代码片

package LinkTable;

import java.util.Iterator;

/**
 * @author shkstart
 * @create 2021-08-10 14:45
 */
public class LinkList<T> implements Iterable<T>{
    //成员变量
    private Node head;//记录首结点
    private int Num;//记录链表的长度


    //Node不需要添加泛型<T>,会导致item的结果与LinkList的泛型结果出现紊乱
    private class Node {
        //存储元素
        T item;
        //指向下一个节点
        Node next;

        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }

    }

    //添加单向链表的构造器,对首结点和链表长度进行初始化
    public LinkList() {
        this.head = new Node(null,null);
        Num = 0;
    }

    //空置线性表
    public void clear() {
        head.next = null;
        head.item = null;
        Num = 0;
    }

    //判断线性表是否为空,是就返回true,反之false
    public boolean isEmpty() {
        return Num == 0;
    }

    //获取线性表中的元素个数
    public int length() {
        return Num;
    }



    //对接口中的iterator方法进行重写,对集合中的元素进行遍历
    @Override
    public Iterator<T> iterator() {
        return new LinkIterator();
    }

    private class LinkIterator implements Iterator<T> {
        //建立一个节点的成员变量
        private Node n;

        //使用构造器初始化首结点为head
        public LinkIterator() {
            this.n = head;
        }

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public T next() {
            n = n.next;
            return n.item;
        }
    }


    //面试题一:计算链表中的节点个数
    //不包含头结点,头结点只是个导引
    public int getPoint(){
        if (isEmpty()){
            return 0;
        }

        int length = 0;
//        Node cur = head.next;
//        for (int index = 0; cur != null; index++) {
//            length ++;
//            cur = cur.next;
//
//        }

        //方式二:道理一样,这个计算了头节点,不算尾结点,上面反之
        Node cur = head;
        for (int index = 0; cur.next != null; index++) {
            length ++;
            cur = cur.next;

        }

        return length;
    }


    //计算链表中倒数第k个结点
    public T getItem(int i){
        if (isEmpty()) {
            return null;
        }

        //方法一:遍历
        Node cur = head.next;
        for (int index = 0; index < Num - i; index++) {
            cur = cur.next;
        }

        return cur.item;

        //方法二:使用快慢指针

    }


    //对链表进行反转,使用递归
    public void reverse(){
        if(isEmpty()){
            return;
        }
        reverse(head.next);

    }

    //反转方法一:利用递归

    //反转指定的结点cur,并把反转的结点返回
    //采用了方法的重载和递归
    public Node reverse(Node cur) {

        //开始进行反转,首先当现结点没有下一个结点时,即为尾结点,则head的下一个结点即为现结点(也是链表的尾结点)
        if(cur.next == null){
            head.next = cur;
            return cur;
        }

        //原cur结点的前一结点成为现cur的下一个结点
        Node pre = reverse(cur.next);
        //原前一个结点的下一个结点就是结点cur
        pre.next = cur;
        //指定结点重置指向null
        cur.next = null;
        return cur;
    }

    //反转方法二:利用指针

    public void reverse1(){
        if (head.next == null || head.next.next == null){
            return;
        }

        //定义一个辅助的指针变量
        Node cur = head.next;
        //初始化指针下一个指向为空
        Node next = null;
        //初始化反转过后的链表头指针.原链表尾指针
        Node reverseHead = new Node(null,null);
        //对链表进行遍历,将链表元素逐一反转添加到反转链表中
        for (int index = 0; cur != null; index++) {
            //先暂时保存当前节点的下一节点,用于后面cur进行迭代
            next = cur.next;

            //以下两句不能混淆,否则会出现死循环
            //将cur的下一个结点指向新链表的最前端
            cur.next = reverseHead.next;
            //将cur连接到反转后的链表上
            reverseHead.next = cur;
            //将cur后移进行迭代
            cur = next;

        }
        //原头结点转变为现头结点
        head.next = reverseHead.next;


    }


}

下面展示一些 内联代码片

public class LinkListTest {
    public static void main(String[] args) throws Exception{
        LinkList<String> linkList = new LinkList<>();
        linkList.insert(0,"张三");
        linkList.insert(1,"李四");
        linkList.insert(2,"王五");
        linkList.insert(3,"小六");
        linkList.insert(4,"小七");
        linkList.insert(5,"小八");
        linkList.insert(6,"小九");
        linkList.insert("七宝");

        //测试length方法
        for (String s:linkList){
            System.out.println(s);
        }
        System.out.println("********");
        //测试节点个数
        int length = linkList.getPoint();
        System.out.println(length);

        System.out.println("********");
        String item = linkList.getItem(2);
        System.out.println(item);

        //测试链表反转的方法
        System.out.println("----------");
        linkList.reverse();
        for (String s1: linkList) {
            System.out.println(s1);
        }


        //测试链表反转指针方法
        System.out.println("*********");
        linkList.reverse1();
        for(String s2 :linkList){
            System.out.println(s2);
        }
    }
}

2.5 快慢指针

快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以然我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍。

2.5.1 中间值问题

需求:
请完善测试类Test中的getMid方法,可以找出链表的中间元素值并返回。利用快慢指针,我们把一个链表看成一个跑道,假设a的速度是b的两倍,那么当a跑完全程后,b刚好跑一半,以此来达到找到中间节点的目的。
如下图,最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针。
在这里插入图片描述
下面展示一些 快慢指针解决中间值问题

public class FastSlow  {
    public static void main(String[] args) {
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);
        //Node<String> eight = new Node<String>("hh", null);


        //实现结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //seven.next = eight;

        //查找中间值
        String mid = getMidValue(first);
        System.out.println("中间值为:"+mid);
    }

    //利用快慢指针找到链表中的中间值
    public static String getMidValue(Node<String> first){

       Node<String> fast = first;
       Node<String> slow = first;
       while (fast != null && fast.next!= null){
           fast = fast.next.next;
           slow = slow.next;
       }
       return slow.item;
   }





}

2.5.2 单向链表是否有环问题

在这里插入图片描述
需求:
请完善测试类Test中的isCircle方法,返回链表中是否有环。
使用快慢指针的思想,还是把链表比作一条跑道,链表中有环,那么这条跑道就是一条圆环跑道,在一条圆环跑道中,两个人有速度差,那么迟早两个人会相遇(这是数学的统计知识),只要相遇那么就说明有环。

下面展示一些 单向链表是否有环问题

    //单向链表是否有环问题
    public static boolean isCircle(Node<String> first){
        Node<String> fast = first;
        Node<String> slow = first;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;

            if (fast.equals(slow)){
                return true;
            }

        }
        return false;

    }

2.5.3 有环链表入口问题

请完善Test类中的getEntrance方法,查找有环链表中环的入口结点。
当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明这一结论牵涉到数论的知识(就不在此多说了)。
在这里插入图片描述
下面展示一些 /查找单向链表中的闭环入口

  //查找单向链表中的闭环入口
    public static Node getEntrance(Node<String> first) {
        Node<String> fast = first;
        Node<String> slow = first;
        Node<String> temp = null;

        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;

            if (fast.equals(slow)){
                temp = first;
                continue;//结束循环执行下面的代码
            }

            if (temp != null){
                temp = temp.next;
                if (temp.equals(slow)){
                    return temp;
                }
            }
        }
        return null;
    }

下面展示一些 快慢指针的问题合集

package LinkTable;

/**
 * @author shkstart
 * @create 2021-08-12 16:45
 */
public class FastSlow  {
    public static void main(String[] args) {
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);
        //Node<String> eight = new Node<String>("hh", null);


        //实现结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;

        //是否有环的判断条件
        seven.next = third;
        //seven.next = eight;

//        //查找中间值
//        String mid = getMidValue(first);
//        System.out.println("中间值为:"+mid);


        //判断是否有环
        boolean isCircle = isCircle(first);
        System.out.println("是否有环:" + isCircle);

        Node<String> entrance = getEntrance(first);
        System.out.println("闭环入口是:" + entrance.item);
    }

//    //利用快慢指针找到链表中的中间值
//    public static String getMidValue(Node<String> first){
//
//       Node<String> fast = first;
//       Node<String> slow = first;
//       while (fast != null && fast.next!= null){
//           fast = fast.next.next;
//           slow = slow.next;
//       }
//       return slow.item;
//   }

    //单向链表是否有环问题
    public static boolean isCircle(Node<String> first){
        Node<String> fast = first;
        Node<String> slow = first;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;

            if (fast.equals(slow)){
                return true;
            }

        }
        return false;

    }

    //查找单向链表中的闭环入口
    public static Node getEntrance(Node<String> first) {
        Node<String> fast = first;
        Node<String> slow = first;
        Node<String> temp = null;

        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;

            if (fast.equals(slow)){
                temp = first;
                continue;//结束循环执行下面的代码
            }

            if (temp != null){
                temp = temp.next;
                if (temp.equals(slow)){
                    return temp;
                }
            }
        }
        return null;
    }




}

2.6 循环链表

循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。
在这里插入图片描述

2.7 约瑟夫问题(面试高频)

问题描述:
传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。

问题转换:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2.自退出那个人开始的下一个人再次从1开始报数,以此类推;
3.求出最后退出的那个人的编号。
在这里插入图片描述
解题思路:
1.构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;
2.使用计数器count,记录当前报数的值;
3.遍历链表,每循环一次,count++;
4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;

下面展示一些 约瑟夫循环问题代码实现

package LinkTable;

/**
 * @author shkstart
 * @create 2021-08-12 19:53
 */

//约瑟夫的循环问题
public class JosephList {

    public static void main(String[] args) {
        //1.构建循环列表,包含41各结点,分别存储1~41之间的值
        //记录第一个结点
        Node<Integer> first = null;

        //记录前一个结点
        Node<Integer> pre = null;

        for (int i = 1; i <= 41 ; i++) {
            //如果是从第一个结点开始
            if (i == 1) {
                first = new Node(i, null);
                //考虑循环的使用
                pre = first;
                continue;
            }
            //如果不是第一个结点开始
            Node<Integer> newNode = new Node<>(i, null);
            //前一个结点的下一个结点就是新结点
            pre.next = newNode;
            //循环刷新
            pre = newNode;

            //如果是最后一个结点,就要把尾结点与头结点连接起来,形成循环链表
            if (i == 41) {
                pre.next = first;
            }

        }

        //2.需要count计数器,模拟报数
        int count = 0;
        //3.遍历循环列表
        //记录每次遍历拿到的结点,默认从首结点开始
        Node<Integer> n = first;
        //记录当前节点的上一个结点
        Node<Integer> before = null;


        while ( n != n.next){
            count++;

            //判断当前报数是否为3
            if (count == 3) {
                //如果为3,将当前结点删除,前一结点指向当前结点的下一结点
                before.next = n.next;
                System.out.print(n.item + ",");
                count = 0;
                n = n.next;
            }else {
                //若果不是3,让before变为当前结点,当前结点后移
                before = n;
                n = n.next;
            }
        }

        //打印最后一个元素
        System.out.println("最后一个元素是:" + n.item);



    }
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

原来如此呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值