单向环形链表&解决Josephu问题

本文介绍了如何使用单向环形链表解决Josephu问题。Josephu问题是一个数学应用,人们按编号围坐一圈,从指定编号开始报数,数到特定数值的人出列,然后从下一个人继续报数,直到所有人都出列。解决方案是创建一个不带头的单向环形链表,从k节点开始计数,每数到m就删除节点,直到链表只剩下一个节点。
摘要由CSDN通过智能技术生成

单项环形链表

思路
长度为1时
(1)新创建的boy节点是首节点。
firts=boy。
(2)自己构成环形链表。
first.next = frist。
(3)first是首节点,同时又是尾节点,辅助指针(变量)指向fisrt。
curBoy=first。

长度不为1时
(1)讲boy节点添加至环形链表最后。
curBoy.next = boy;//curBoy节点永远是环形链表的尾结点
(2)构成环形链表。
boy.next = first。
// 创建一个Boy类,表示一个节点(Boy 节点就是个普普通通的单向链表节点)
class Boy {
	private int no;// 编号
	private Boy next; // 指向下一个节点,默认null
	(getter setter省略)
}

// 创建一个环形的单向链表
class CircleSingleLinkedList {
	// 创建一个first节点,当前没有编号
	private Boy first = null;// 单向循环链表的首节点,是真实存放数据的节点,不是头结点
   
  	// 添加小孩节点,构建成一个环形的链表
	public void addBoy(int nums) {
	    // nums 做一个数据校验
	    if (nums < 1) {
	        System.out.println("nums的值不正确");
	        return;
	    }
	    Boy curBoy = null; // 辅助指针,帮助构建环形链表
	    // 使用for来创建我们的环形链表
	    for (int i = 1; i <= nums; i++) {
	        // 根据编号,创建小孩节点
	        Boy boy = new Boy(i);
	        // 如果是第一个小孩
	        if (i == 1) {
	            first = boy; // 初始化 first 节点
	            first.setNext(first); // 构成环
	            curBoy = first; // 让curBoy指向第一个小孩
	        } else {
	            curBoy.setNext(boy); // 将 boy 节点加到链表尾部
	            boy.setNext(first); // 构成环
	            curBoy = boy; // curBoy 指针后移
	        }
	    }
	}

	// 遍历当前的环形链表
	public void showBoy() {
	    // 判断链表是否为空
	    if (first == null) {
	        System.out.println("没有任何小孩~~");
	        return;
	    }
	    // 因为first不能动,因此我们仍然使用一个辅助指针完成遍历
	    Boy curBoy = first;
	    while (true) {
	        System.out.printf("小孩的编号 %d \n", curBoy.getNo());
	        if (curBoy.getNext() == first) {// 当 curBoy 已经指向环形链表的尾节点,说明已经遍历完毕
	            break;
	        }
	        curBoy = curBoy.getNext(); // curBoy后移
	    }
	}
}

解决Josephu(约瑟夫)问题

Josephu(约瑟夫, 约瑟夫环)是一个数学应用问题

n个人按编号1, 2, …n围坐一圈, 从编号为 k的人开始报数, 数到 m的那个人出列, 此时它的下一个人又从1开始报数, 数到 m的那个人再出列, 依次反复, 直到所有的人全部出列为止, 最后输出出列人的编号顺序。

解决思路

首先创建有 n个节点的不带头的单向环形链表, 然后从 k节点开始计数, 计到 m时, 删除对应当前节点, 然后再从被删节点的下一个节点开始重新计数, 依次反复, 直到最后一个节点从链表中删除算法结束。

代码示例
/** 定义人(表示节点)*/
class Person {
    /** 人编号*/
    private int no;
    /** 指向下一个节点*/
    private Person next;

    public Person(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public Person getNext() {
        return next;
    }

    public void setNext(Person next) {
        this.next = next;
    }
}

/** 定义不带头的单向环形链表*/
class CircularSingleLinkedList {
    /** 定义起始节点*/
    private Person first;

    /** 构建 n个节点的链表*/
    public void addPersons(int n) {
        if (n < 1) {
            System.out.println("构建链表失败, 参数错误!");
            return;
        }

        Person current = null;
        for (int i = 1; i <= n; i++) {
            /** 设定节点编号*/
            Person person = new Person(i);
            /** 定义起始节点*/
            if (i == 1) {
                first = person;
                /** 起始节点, 指向自己(自己指向自己)*/
                first.setNext(first);
                /** 保存当前节点, 用于下次循环时配置下一个节点的引用*/
                current = first;
            } else {
                /** 之前的有效环形的指向是 first, 改成当前节点(current的下一个节点)*/
                current.setNext(person);
                /** 当前节点的下一个节点引用起始节点*/
                person.setNext(first);
                current = person;
            }
        }
    }

    /** 打印所有节点*/
    public void print() {
        if (first == null) {
            System.out.println("链表为空!");
            return;
        }

        Person current = first;
        while (true) {
            System.out.printf("节点编号 %d \n", current.getNo());
            if (current.getNext() == first) {
                break;
            }
            /** 往后移动一个节点*/
            current = current.getNext();
        }
    }

    /**
     * 计算后打印每个人出列的顺序编号:
     * @param startNo: 表示从第几个小孩开始数数
     * @param countNum: 表示数几下
     * @param nums: 表示最初有多少小孩在圈中
     * */
    public void countPerson(int startNo, int countNum, int nums) {
        if (first == null || startNo < 1 || startNo > nums) {
            System.out.println("参数错误!");
            return;
        }

        Person helper = first;
        /** 将 helper指向最后节点*/
        while (true) {
            /** 循环后, 当前节点与起始节点相同, 则循环结束*/
            if (helper.getNext() == first) {
                break;
            }

            helper = helper.getNext();
        }

        /**
         * 例: startNo = 1; -1 = 0时不成立跳过!, 如果 startNo大于1就会调整起始节点
         * */
        for(int i = 0; i < startNo - 1; i++) {
            first = first.getNext();
            helper = helper.getNext();
        }

        /** 每次计数时, 将起始节点与最后节点同时向下移动  m - 1次, 然后出列*/
        while(true) {
            /** 当链表中只有一个节点时结束*/
            if(helper == first) {
                break;
            }

            /** 每次循环, 将起始节点与最后节点向下移动: (计数指定次 -1)个节点, */
            for(int j = 0; j < countNum - 1; j++) {
                first = first.getNext();
                helper = helper.getNext();
            }

            /** 此时起始节点就是要出列的节点*/
            System.out.printf("%d号节点出列\n", first.getNo());
            /** 将起始使用第二个节点覆盖*/
            first = first.getNext();
            /** 最后节点的下一个节点(之前的起始节点), 改成目前的起始节点*/
            helper.setNext(first);
        }

        System.out.printf("最后剩余的节点编号 %d \n", first.getNo());
    }

}

public class JosephuApp {
    public static void main(String[] args) {
        /** 创建单向环形链表实例*/
        CircularSingleLinkedList linkedList = new CircularSingleLinkedList();
        /** 构建5个节点*/
        linkedList.addPersons(5);
        System.out.println("输出所有节点编号:");
        linkedList.print();

        System.out.println("计算后打印每个人出列的顺序编号:");
        linkedList.countPerson(1, 2, 5);
    }
}

输出:
> 输出所有节点编号:
> 节点编号 1 
> 节点编号 2 
> 节点编号 3 
> 节点编号 4 
> 节点编号 5 
> 计算后打印每个人出列的顺序编号:
> 2号节点出列
> 4号节点出列
> 1号节点出列
> 5号节点出列
> 最后剩余的节点编号 3 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值