twelvet

当前位置:首页 > 技术分享 > 数据结构

数据结构

【数据结构】约瑟夫链表-环形链表

2022-12-09 13:49:08 TwelveT 215
一、双向链表双向链表的定义:在单链表的每个结点里再增加一个指向其直接前驱的指针域prior,这样链表中就形成了有两个方向不同的链,故称为双向链表。优点:双向链表可以克服单链表查找链表中某结点不方便的缺点。双向循环链表:和单链的循环表类似,双向链表也可以有循环表。让头结点的前驱指针指向链表的最后一个结点,让最后一个结点的...

一、约瑟夫问题

  1. 约瑟夫问题是个著名的问题:N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。
    例如只有三个人,把他们叫做A、B、C,他们围成一圈,从A开始报数,假设报2的人被杀掉。
  2. 首先A开始报数,他报1。侥幸逃过一劫。
  3. 然后轮到B报数,他报2。非常惨,他被杀了
  4. C接着从1开始报数
  5. 接着轮到A报数,他报2。也被杀死了。
  6. 最终胜利者是C

二、基本原理

  1. 约瑟夫环算法是:n个人围成一圈,每个人都有一个互不相同的密码,该密码是一个整数值,选择一个人作为起点,然后顺时针从1到k(k为起点人手中的密码值)数数,数到k的人退出圈子,然后从下一个人开始,继续从1到j(j为刚退出圈子的人的密码)数数,数到j的人退出圈子。重复上面的过程,直到剩下最后一个人。
【数据结构】约瑟夫链表-环形链表 图1

三、代码实现

1.创建链表节点

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) {
        this.next = next;
    }
}

2.链表实现

/**
 * 创建一个环形的单向链表
 */
class CircleSingleLinkedList {
    private final static Logger log = LoggerFactory.getLogger(CircleSingleLinkedList.class);
    /**
     * 创建一个first节点,当前没有编号
     */
    private Boy first = new Boy(-1);
    /**
     * 添加小孩节点,构成一个环形链表
     *
     * @param nums 数量
     */
    public void addBoy(int nums) {
        if (nums < 1) {
            log.error("nums的值不正确");
            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;
            } else {
                curBoy.setNext(boy);
                boy.setNext(first);
                curBoy = boy;
            }
        }
    }
    /**
     * 遍历环形链表
     */
    public void showBoys() {
        // 判断是否为空数据
        if (first == null) {
            log.error("数据为空");
            return;
        }
        // first不能移动,需要使用辅助指针
        Boy curBoy = first;
        while (true) {
            log.info("小孩编号为:{}", curBoy.getNo());
            // 判断是否循环完毕
            if (curBoy.getNext() == first) {
                break;
            }
            // curBoy后移
            curBoy = curBoy.getNext();
        }
    }
    /**
     * 根据用户的输入,计算小孩的出拳顺序
     *
     * @param k  表示从第几个小孩开始
     * @param m 表示数几下
     * @param nums     表示最初有多少小孩在圈中
     */
    public void countBoy(int k, int m, int nums) {
        // 校验数据
        if (first == null || k < 1 || k > nums) {
            log.error("参数有误,请重新输入");
            return;
        }
        // 创建辅助指针,帮助小孩出圈
        Boy helper = first;
        // 指针指向链表最后一个节点,让其以最后一个开始数 1->5
        while (helper.getNext() != first) {
            // curBoy后移
            helper = helper.getNext();
        }
        // 小孩报数前,先让first和helper移动k-1次,移动到需要开始的位置前一个
        for (int i = 0; i < k - 1; i++) {
            // 1 -> 2
            first = first.getNext();
            // 5 -> 1
            helper = helper.getNext();
        }
        // 当小孩报数时,让first和helper指针同时的移动m-1次(移动到需要出圈的小孩),然后出圈
        // 这里是一个循环操作,知道圈中只有一个节点
        while (helper != first) {
            // 说明圈中只有一个节点
            // 让first和helper指针同时移动m-1
            for (int i = 0; i < m - 1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            // 这时first指向的节点就是要出圈的小孩节点
            log.info("小孩{}出圈", first.getNo());
            // 这时将first指向小孩节点出圈
            first = first.getNext();
            helper.setNext(first);
        }
        log.info("最后留在圈中的小孩编号:{}", helper.getNo());
    }
}

免责声明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络收集整理,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序和内容,请支持正版,购买注册,得到更好的正版服务。我们非常重视版权问题,如有侵权请邮件与我们联系处理。敬请谅解!

标签:数据结构  约瑟夫  环形链表  

文章评论