数据结构与算法: 约瑟夫问题(丢手绢)

Posted android超级兵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法: 约瑟夫问题(丢手绢)相关的知识,希望对你有一定的参考价值。

数据结构与算法: 约瑟夫(丢手绢)问题(单向链表,双向链表解决)

Tips: 采用java语言, 关注博主,底部附有完整代码

采用到的知识点:

  • 单向环形链表
  • 双向环形链表
  • 单向 / 双向 环形链表出圈

流程效果图:

单向环形链表双向环形链表

什么是约瑟夫问题

约瑟夫问题好像小时候玩的丢手绢一样

先来说一下规则, 默认从第一个小朋友开始数,在第n个小朋友过后开始寻手绢, 第n个小朋友每k个人就丢下手绢,然后这个小朋友出圈

直到圈中只剩一人才结束

假设5个小朋友围坐一圈来丢手绢

例如这样:

在第2个小朋友开始丢手绢

那么就是元素三出圈

接着每2个元素依次出圈

tips: 这里单向环形链表和双向环形链表思路有所不同, 所以结果也有所不同!

  • 单向环形链表 先创建好5个元素 然后从第n个孩子开始,每k次出圈 , (第一次从第n个孩子数也会出圈)
  • 双向环形链表 直接调用一个方法,并且传入需要“丢手绢”的元素,在开始出圈 (第一次从第n个孩子数也不会出圈)

单向环形链表

自定义链表 AnnularLinkedList.java:

public class AnnularLinkedList 

    //头节点
    private HeroNode3 head;

    /**
     * get头节点
     * @return 头节点
     */
    public HeroNode3 getHead() 
        return head;
    

    /**
     * 创建元素
     * @param sum 需要创建的个数
     */
    public void createHeroNode(int sum) 
        if (sum < 1) 
            System.out.println("请正确输入创建个数");
            return;
        

        HeroNode3 temp = null;

        for (int i = 1; i <= sum; i++) 
            HeroNode3 node3 = new HeroNode3(i, "张三" + i);

            if (i == 1) 
                //记录第一个位置
                head = node3;
                //辅助变量,记录第一个位置
                temp = node3;
                //第一个元素指向自己,构建成环形
                head.next = head;
            

            //指向新的元素
            temp.next = node3;

            //新的元素指向第一个节点
            node3.next = head;

            //temp后移
            temp = temp.next;
        
    

    /**
     * @param start  从元素几开始数
     * @param number 每数几下出圈
     */
    public void count(int start, int number) 

        //获取链表长度
        int sum = size();
        System.out.println("sum为:" + start + "\\t" + number + "\\t" + sum);

        //start < 1  不能从 < 1 开始的数 数
        //sum < start  链表总数不能 < 开始的位置
        if (start < 1 || sum < start) 
            System.out.println("请正确输入");
            return;
        

        HeroNode3 help = head;

        //将 help 移动到 head 后面
        while (help.next != head) 
            help = help.next;
        

        //从第start位置开始循环
        for (int i = 0; i < start - 1; i++) 
            head = head.next;
            help = help.next;
        

        //如果heap != head 那么就继续循环
        //当help == head 时,则退出循环
        while (help != head) 

            //每number下出圈
            for (int i = 0; i < number - 1; i++) 
                head = head.next;
                help = help.next;
            
            //此时head则为要出圈的元素
            System.out.println("要出圈的勇士id为:" + head.id);

            //出圈操作
            head = head.next;
            help.next = head;
        

        //最终help = head  那么help/head就是最后一个在圈中的元素
        System.out.println("留在圈中的勇士id为:" + help.id);
    

    /**
     * 计算链表长度
     */
    public int size() 
        int length = 1;
        HeroNode3 temp = head;
        while (temp.next != head) 
            length++;
            temp = temp.next;
        
        return length;
    

    /**
     * 输出链表所有数据
     */
    public void show() 
        if (head == null) 
            System.out.println("链表为null,不能打印");
        
        HeroNode3 temp = head;

        while (temp.next != null) 
            System.out.println("show:" + temp);

            //当最后一个元素 = 第一个元素时,表示链表当前在最后一个元素,停止循环
            if (temp.next == head) 
                break;
            

            //temp后移
            temp = temp.next;
        
    

HeroNode3.java:

public class HeroNode3 
    int id;
    String name;
    HeroNode3 next;

    public HeroNode3(int id, String name) 
        this.id = id;
        this.name = name;
    

    @Override
    public String toString() 
        return "HeroNode3" +
                "id=" + id +
                ", name='" + name + '\\'' +
                '';
    

调用:

AnnularLinkedList linkedList = new AnnularLinkedList();

linkedList.createHeroNode(5);

System.out.println("测试的值为:"+linkedList.getHead().next.next.next.next.next.next);

linkedList.show();

linkedList.count(2, 2);

来看看运行结果:

如果对单向链表或者约瑟夫问题陌生的,项目中还有我画的2张详细流程图 那就下载看看吧

双向环形链表

AnnularDoubleLinkedList.java

/**
 * @author: android 超级兵
 * @create: 2022-05-05 11:53
 * TODO 约瑟夫问题 双向链表
 **/
public class AnnularDoubleLinkedList 

    public AnnularHeroNode headNode = new AnnularHeroNode();

    /**
     * TODO 约瑟夫问题 双向链表
     *
     * @param n    从第几个开始 (0 - list.size())
     * @param k    每次走几个 (k < list.size())
     * @param list 元素
     */
    public void josephusProblem(int n, int k, List<AnnularHeroNode> list) 

        if (list.size() < n) 
            System.out.printf("当前元素一共%d个,无法从第%d个元素开始", list.size(), n);
            return;
        

        headNode = list.get(0);

        // TODO 先将每个元素链接起来
        for (int i = 0; i < list.size(); i++) 
            AnnularHeroNode node = list.get(i);

            // 下一个元素
            AnnularHeroNode nextNode;
            if (i != list.size() - 1) 
                nextNode = list.get(i + 1);
             else // 最后一个节点
                node.setLastNode(true);
                nextNode = list.get(0);
            

            node.next = nextNode;
            nextNode.pre = node;
        

        System.out.println("测试是否成功:");
        System.out.println(headNode.next.next.next.next);
        System.out.println(headNode.pre.pre.pre.pre);
        System.out.printf("最后一个节点为:%s\\n", headNode.pre);
        System.out.printf("第一个节点为:%s\\n", headNode);

        show();

        // 从第几个元素开始
        AnnularHeroNode tempNode = list.get(n);

        System.out.printf("从%s开始\\n", tempNode);

        System.out.println();

        AnnularHeroNode currentNode = null;
        // 当前节点上一个节点
        AnnularHeroNode currentPreNode = null;


        while (true) 

            // 每次过n个人
            for (int i = 0; i < k; i++) 
                currentNode = tempNode.next;
                tempNode = tempNode.next;
            
            System.out.printf("currentNode:%s\\n", currentNode);

            // 移除当前选中的
            currentNode.pre.next = currentNode.next;
            currentNode.next.pre = currentNode.pre;

            // 当前位置的最后一个位置
            currentPreNode = currentNode.pre;

            // 如果当前位置后一个位置 = 当前位置 说明只有一个,则退出循环
            if (currentPreNode == currentNode) 
                break;
            
        

        System.out.printf("最终留在圈中的是  %s\\n", currentNode);

    


    private void show() 
        System.out.println("\\n遍历: ======= start =======");

        AnnularHeroNode tempHeadNode = headNode;

        while (true) 
            System.out.println(tempHeadNode);

            if (tempHeadNode.isLastNode()) 
                break;
            

            tempHeadNode = tempHeadNode.next;

        
        System.out.println("遍历:  ======= end =======\\n");
    

AnnularHeroNode.java

/**
 * @author: android 超级兵
 * @create: 2022-05-05 11:54
 * TODO
 **/
public class AnnularHeroNode 
    private final int id;
    private final String name;
    private final String title;

    // 是否是最后一个节点
    private boolean isLastNode;

    // 下一个节点
    public AnnularHeroNode next;

    // 上一个节点
    public AnnularHeroNode pre;

    public AnnularHeroNode() 
        id = 0;
        name = "";
        title = "";
    

    public int getId() 
        return id;
    

    public AnnularHeroNode(int id, String name, String title) 
        this.id = id;
        this.name = name;
        this.title = title;
    

    public boolean isLastNode() 
        return isLastNode;
    

    public void setLastNode(boolean lastNode) 
        isLastNode = lastNode;
    

    @Override
    public String toString() 
        return "AnnularHeroNode" +
                "id=" + id +
                ", name='" + name + '\\'' +
                ", title='" + title + '\\'' +
                ", isLastNode='" + isLastNode + '\\'' +
                '';
    

使用:

package a20220505环形双向列表;

import java.util.ArrayList;

/**
 * @author: android 超级兵
 * @create: 2022-05-05 12:07
 * TODO 约瑟夫问题 双向链表解决
 **/
public class Client 
    public static void main(String[] args) 

        AnnularDoubleLinkedList annularDoubleLinkedList = new AnnularDoubleLinkedList();

        AnnularHeroNode node0 = new AnnularHeroNode(0, "史进", "九纹龙");
        AnnularHeroNode node1 = new AnnularHeroNode(1, "鲁智深", "花和尚");
        AnnularHeroNode node2 = new AnnularHeroNode(2, "林冲", "豹子头");
        AnnularHeroNode node3 = new AnnularHeroNode(3, "宋万", "云里金刚");
        AnnularHeroNode node4 = new AnnularHeroNode(4, "吴用", "智多星");

        ArrayList<AnnularHeroNode> nodeArrayList = new ArrayList<>();
        nodeArrayList.add(node0);
        nodeArrayList.add(node1);
        nodeArrayList.add(node2);
        nodeArrayList.add(node3);
        nodeArrayList.add(node4);

        // 23401
        annularDoubleLinkedList.josephusProblem(1, 2, nodeArrayList);
    

完整代码

原创不易,您的点赞与关注就是对我最大的支持!

以上是关于数据结构与算法: 约瑟夫问题(丢手绢)的主要内容,如果未能解决你的问题,请参考以下文章

算法与数据结构的介绍

1875 丢手绢 (模拟+打表)

算法题之丢手绢问题

单向环形链表解决Josephu(约瑟夫)问题

单向环形链表解决Josephu(约瑟夫)问题

用ArrayList(解决约瑟夫问题)