用面向对象实现约瑟夫环算法

约瑟夫环

约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。

模拟场景

下面就来个实际例子:

10(n)个小孩围成一圈,从第1(k)个开始报数:1,2,3,1,2,3,1,2,3......每次报3(m)的小孩退出. 问最后剩下的那个小孩,在之前10人里是第几个?

这里对应约瑟夫环的变量值分别为:

n=10;k=1;m=3;

下面就用面向对象来模拟这个场景,小孩我们可以看成一个类

class Child{
    
}

小孩有哪些属性呢?

1.报数(小孩当前应该报什么数字),2.位置(小孩一开始所在第几个位置)

理论上只有这两个属性,但是既然是模拟现实场景,那么我们自然而然要考虑到每个小孩的真实处境

小孩围成一个圈,那就说明每一个小孩两边都有小孩,这个隐藏属性不能忽略了.最后我们的小孩类就产生了

// 定义小孩类  
class Child{  
    public Child(int position){  
        this.position = position;  
    }  
    public int number; // 小孩当前的报数  
    public final int position; // 小孩的初始位置,固定不变的  
      
    public Child beforeChild; // 小孩前一个孩子  
    public Child nextChild; // 小孩后一个孩子  
}  

类设计出来了,下面就开始玩游戏.

首先小孩围成一个圈,我们可以初始化10个Child对象,然后分别设置他们的左右两边的孩子

Child[] children = new Child[n];  
        // 初始化小孩子,将孩子相互关联起来  
        // 可以理解为,手拉手围成一个圈  
        // 第一个孩子跟最后一个孩子拉手  
        for (int i = 0; i <n; i++) {  
              
            children[i] = new Child(i+1);  
              
            if(i > 0){ // 第二个人开始  
                together(children[i-1],children[i]);  
            }  
              
            if(i == n-1){ // 最后一个  
                // 关联到第一个  
                together(children[i],children[0]);  
            }             
        }  

// 相互关联,手拉手  
    private static void together(Child before,Child next){  
        before.nextChild = next;  
        next.beforeChild = before;  
    }  

最后一个孩子自然要跟第一个孩子关联起来.

准备工作做好了,然后开始报数,报3的人退出.这里遇到的问题有:

  1. 判断当前报3的孩子

  2. 报3的孩子如何退出

  3. 接下来的孩子又开始报1

  4. 如何判断只剩下一个人

第一个问题好办,直接判断number==3即可

第二个问题,让他退出也就是他两边没有小孩了,换句话说就是他左边的小孩跟他右边的小孩相关联起来.

第三个问题,我们让他下面的小孩重新开始报1就可以,currentChild.nextChild.number = 1;

第四个问题,如果只剩下一个人的话,小孩的nextChild属性会指向自己,这个思考下不难理解

解决了上述问题,我们的核心代码也就出来了:

核心代码

// 取第一个孩子开始报数  
        Child currentChild = children[k-1];  
        currentChild.number = 1; // 第一个孩子当然报1  
        // 循环,一直报数,当剩下1个小孩就停下来  
        while(true){  
            // 如果下一个小孩引用的对象是自己说明只剩下一个人了  
            if(currentChild.nextChild == currentChild){  
                break; // 停止报数  
            }  
            // 如果报数是3,把他的上一个小孩跟他的下一个小孩关联起来  
            // 这意味着他失去关联,退出  
            if(currentChild.number == m){  
                together(currentChild.beforeChild,currentChild.nextChild);  
                // 下一个小孩重新报数1  
                currentChild.nextChild.number = 1;  
                  
            }else if(currentChild.number < m){// 如果不是3,转移到下一个小孩  
                // 下一个小孩报数+1  
                currentChild.nextChild.number = (currentChild.number + 1);  
            }     
            // 轮到下一个小孩  
            currentChild = currentChild.nextChild;  
        }  
          
        System.out.println("第"+currentChild.position+"个小孩");

到此程序已经设计完毕了,等到循环退出时,我们只需要打印当前小孩的position属性即可.

完整代码

// 定义小孩类
class Child {
    public Child(int position) {
        this.position = position;
    }

    public int number; // 小孩当前的报数
    public final int position; // 小孩的初始位置,固定不变的

    public Child beforeChild; // 小孩前一个孩子
    public Child nextChild; // 小孩后一个孩子
}

public class Test {
    /**
     * 已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;
     * 他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
     * @param args
     */
    public static void main(String[] args) {
        int n = 10, k = 1, m = 3;

        Child[] children = new Child[n];
        // 初始化小孩子,将孩子相互关联起来
        // 可以理解为,手拉手围成一个圈
        // 第一个孩子跟最后一个孩子拉手
        for (int i = 0; i < n; i++) {

            children[i] = new Child(i + 1);

            if (i > 0) { // 第二个人开始
                together(children[i - 1], children[i]);
            }

            if (i == n - 1) { // 最后一个
                // 关联到第一个
                together(children[i], children[0]);
            }
        }
        // 取第一个孩子开始报数
        Child currentChild = children[k - 1];
        currentChild.number = 1; // 第一个孩子当然报1
        // 循环,一直报数,只剩下1个小孩就停下来
        while (true) {
            // 如果下一个小孩引用的对象是自己说明只剩下一个人了
            if (currentChild.nextChild == currentChild) {
                break; // 停止报数
            }
            // 如果报数是3,把他的上一个小孩跟他的下一个小孩关联起来
            // 这意味着他失去关联,退出
            if (currentChild.number == m) {
                together(currentChild.beforeChild, currentChild.nextChild);
                // 下一个小孩重新报数1
                currentChild.nextChild.number = 1;

            } else if (currentChild.number < m) {// 如果不是3,转移到下一个小孩
                // 下一个小孩报数+1
                currentChild.nextChild.number = (currentChild.number + 1);
            }
            // 轮到下一个小孩
            currentChild = currentChild.nextChild;
        }

        System.out.println("第" + currentChild.position + "个小孩"); // 第436个小孩
    }

    // 相互关联,手拉手
    private static void together(Child before, Child next) {
        before.nextChild = next;
        next.beforeChild = before;
    }

}

程序运行结果:第4个小孩

转载于:https://my.oschina.net/u/3658366/blog/1801383

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是C++面向对象实现的代码: ```c++ #include <iostream> using namespace std; // 定义一个结点类 class Node { public: int data; Node *next; Node(int d) { data = d; next = NULL; } }; // 定义一个类 class Josephus { public: int n, m; // n表示总人数,m表示出列的间隔 Node *head; // 队列的头结点 Josephus(int n, int m) { this->n = n; this->m = m; head = NULL; } // 初始化队列 void init() { for (int i = 1; i <= n; i++) { Node *p = new Node(i); if (head == NULL) { head = p; p->next = head; // 队列首尾相连 } else { Node *tail = head; while (tail->next != head) { tail = tail->next; } tail->next = p; p->next = head; // 队列首尾相连 } } } // 出列操作 void out() { Node *p = head, *pre = head; while (n > 1) { for (int i = 0; i < m - 1; i++) { pre = p; p = p->next; } pre->next = p->next; delete p; p = pre->next; n--; } cout << "最后一个出列的人是:" << p->data << endl; } }; int main() { int n, m; cout << "请输入总人数和出列的间隔:"; cin >> n >> m; Josephus j(n, m); j.init(); // 初始化队列 j.out(); // 开始出列操作 return 0; } ``` 解释一下代码: 首先定义了一个结点类 `Node`,用于构建链表;然后定义了类 `Josephus`,其中包含了总人数 `n`、出列的间隔 `m`、队列的头结点 `head`。类中有两个方法:`init()` 用于初始化队列;`out()` 用于出列操作;main() 函数中创建了一个 `Josephus` 对象,调用 `init()` 和 `out()` 方法,完成的求解。 具体实现细节可以参考代码注释。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值