数据结构与算法-约瑟夫问题

引言

约瑟夫问题,又称“圆圈问题”或“丢手绢游戏”,是一个源自古代传说的经典数学难题,也是一个展示了离散数学和递归思想魅力的问题。本文将详述约瑟夫问题的背景、规则、解决方案以及其在算法设计和实际应用中的价值。

一、约瑟夫问题概述

约瑟夫问题起源于犹太人的传统故事,讲述了一群人在围成一圈玩游戏,按照一定规则顺次报数并淘汰出局,直至只剩一人。具体规则如下:

  • n个人站成一圈,从某人开始顺时针或逆时针报数(编号从1开始)。
  • 每报到某个特定数值m的人将被淘汰出圈,然后从下一个人重新开始报数。
  • 重复此过程,直至圈子中只剩一人,这个人就是胜者。

二、解决方案与算法设计

1.构建

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

2.遍历

  • 先创建一个辅助指针指向first节点
  • 然后通过循环遍历该环形链表即可
  • curBoy.setNext(first) 此条件为循环结束标志

3.算法实现

目的:根据用户输入,生成一个出队顺序

  • 创建一个辅助指针helper,事先应该指向环形链表的最后节点
  • 报数前,先让first 和 helper 移动 startNo -1 次
  • 报数时,让help 和 first 指针同时移动 count - 1次
  • 此时,可以将first指向的节点出队
  • first = first.getNext()
  • helper.setNext(first)
  • 链表中原first指向的节点在没有任何引用后,就会被回收

三、代码实现

注意:代码实现是基于Boy类编号顺序实现,类中定义两个属性变量为编号和下一个节点,类的一个对象就为一个节点。

1.构建

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

 2.添加节点

//创建环形单向链表
class CircleSingleLinkedList {
    //    创建first节点
    private Boy first = new Boy(-1);

    //    添加节点构建环形链表
    public void addBoy(int nums) {
        if (nums < 1) {
            System.out.println("数据不正确!!!");
            return;
        }
        Boy curBoy = null;
        for (int i = 1; i <= nums; i++) {
//            根据编号创建节点
            Boy boy = new Boy(i);
//            如果是第一个小孩
            if (i == 1) {
                first = boy;
                first.setNext(first);   //构成环
                curBoy = first;         //让curBoy指向第一个节点
            } else {
                curBoy.setNext(boy);
                boy.setNext(first);
                curBoy = boy;
            }
        }
    }
}

3.遍历 

    //    遍历环形链表
    public void show() {
        if (first == null) {
            System.out.println("链表为空!!!");
            return;
        }
        //            创建辅助链表,first头结点不能动
        Boy temp = first;
        while (true) {
            System.out.printf("小孩的编号为 %d \n", temp.getNo());
//            是否到最后节点
            if (temp.getNext() == first) {
                break;
            }
            temp = temp.getNext();    //temp后移
        }
    }

4.算法实现 

//    根据输入计算出出队的顺序

    /**
     * @param startNo  表示从第几个节点出队
     * @param countNum 表示数几下
     * @param nums     表示最初有多少节点在圈中
     */
    public void count(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 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();
            }
//            这时first当前节点就是出队节点
            System.out.printf("出队编号为 %d \n", first.getNo());
//            将first节点出队
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.printf("最后的队列的节点为%d\n", first.getNo());
    }

5.结果展示  

public class Josepfu {
    public static void main(String[] args) {
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        System.out.println("======环形队列原始顺序========");
        circleSingleLinkedList.show();
        System.out.println("======环形队列出队顺序========");
        circleSingleLinkedList.count(1, 2, 5);
    }
}

  

四、拓展与变体

约瑟夫问题有许多有趣的变体,比如:

  • 圆圈中的人可以选择不报数跳过自己
  • 每轮淘汰的人数可以是动态的
  • 淘汰后的人可以根据一定规则重新排列位置

这些问题都可以通过扩展原有的递归或迭代模型来解决。

五、实际应用

约瑟夫问题看似娱乐性强,实际上却在计算机科学和数学领域有广泛应用,如:

  • 并发与分布式系统中的资源分配问题
  • 网络安全中的令牌桶算法
  • 模拟与仿真领域的模型建立

通过研究约瑟夫问题,我们不仅能领略到算法设计的魅力,也能锻炼自己的逻辑推理和问题求解能力,更重要的是,从中学习到如何通过抽象、建模和递归思想去解决复杂问题。

六、实际应用 

        总结,约瑟夫问题是一个将数学理论与实际应用紧密相连的经典问题,其解决方案蕴含了丰富的算法设计思想,值得每一位程序员和数学爱好者深入探究。

 

  • 40
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值