Joseph problem of data structure and algorithm

What does Joseph's problem describe?

Joseph's problem: there are N people in a circle, and each person has a number. The number is determined by the order of entering the circle. The first person entering the circle is numbered 1, and the last one is N. The number starts from the K (1 < = k < = N), and the person counting to m (1 < = m < = N) will leave the circle, and then the next person continues to count from 1 until everyone leaves the circle, so as to find the number of the circle in turn.

How to store data

Facing a problem, we first need to think about what kind of data structure to use to save data. Joseph problem describes the problem of circular counting out of circles. The counting is always carried out in the same direction, so it can be stored using a one-way ring linked list.

Whenever a person enters the circle, a new node is created, and the nodes are connected end to end. The code is as follows:

//node
class Node {
	//Node serial number
	private Integer no;
	//A reference to the next node
	private Node next;

	public Node(Integer no) {
		this.no = no;
	}

	@Override
	public String toString() {
		return "Node{" +
			"no=" + no +
			",next=" + (next == null ? "" : next.no) +
			'}';
	}
}

//Unidirectional ring linked list
class SingleCycleLinkedList {
	//Header reference
	private Node head;
	//Tail reference
	private Node tail;
	//Linked list length
	private int size;

	/**
	 * Initializes the circular linked list with increasing sequence number of the specified length
	 *
	 * @param size
	 */
	public SingleCycleLinkedList(int size) {
		for (int i = 1; i <= size; i++) {
			add(new Node(i));
		}
		this.size = size;
	}

	/**
	 * Insert circular linked list
	 *
	 * @param node
	 */
	public void add(Node node) {
		if (node == null) {
			return;
		}

		//If the linked list is empty, directly point the head and tail references to the new node
		if (size == 0) {
			head = node;
			tail = node;
			size++;
			return;
		}

		//The linked list is not empty. Put the new node at the end of the linked list, and the next reference of the new node points to the head to complete the ring operation
		tail.next = node;
		tail = tail.next;
		tail.next = head;
		size++;
}

	...
}

The core logic is in the add method. It should be noted that when the linked list is empty, the new node cannot form a ring, that is, the next reference cannot point to itself. Therefore, when adding for the first time, directly point the head and tail to the new node without operating the next reference of the node. When the linked list is not empty, the ring forming step needs to be introduced. The ring forming step is decomposed as follows:

1.tail.next = node

2.tail = tail.next

3.tail.next = head

This completes the looping operation.

Test in the main method and construct a circular linked list with a length of 10:

SingleCycleLinkedList singleCycleLinkedList = new SingleCycleLinkedList(10);
System.out.println(singleCycleLinkedList);

The results are as follows:

Node{no=1,next=2}, Node{no=2,next=3}, Node{no=3,next=4}, Node{no=4,next=5}, Node{no=5,next=6}, Node{no=6,next=7}, Node{no=7,next=8}, Node{no=8,next=9}, Node{no=9,next=10}, Node{no=10,next=1}

Solve the Joseph problem

Through the previous step, the data storage is completed. Next, we need to solve the problem of how to count cycles. The title requires counting from the k-th person, so you should first find the starting position of counting, and then start the cycle of counting. The person counting to m goes out of the circle, that is, the corresponding node should be removed from the linked list. It should be noted that the nodes of the one-way linked list cannot be deleted by themselves, as shown in the figure:

If you want to delete the node numbered 2, the cur reference must point to 1, so that the next reference of 1 can point from the original 2 to 3:

Therefore, when finding the starting position of the number of reports, count from the previous position of the starting position. In this way, when finding the node to be removed, it is actually located to the previous node of the node to be removed. The code for finding the starting position of the alarm is as follows (the start variable in the code is the parameter k):

//Find the node that starts counting (here, start traversing from tail and take the last node of the counting node, because the node deletion of the one-way linked list must depend on the last node)
Node tmp = tail;
	int startIndex = 0;
	while (startIndex++ != size) {
		if (start == startIndex) {
			break;
		}
		tmp = tmp.next;
	}

After finding the starting position of the counting, you should start the counting operation. When removing the node, you should note that when there is only one node in the linked list, you do not need to operate the next reference of the node, and you can directly empty the node.
The code for counting out the circle is as follows (the step variable in the code is the parameter m):

//Save order out of chain nodes
List<Node> list = new ArrayList<>(size);

//Start counting. After counting to the specified interval, the node is out of the chain
int count = 1;
while (size > 1) {
	if (count == step) {
		//Node out of chain
		//1. Define a reference to the node to be deleted
		Node delNode = tmp.next;
		//2. Point the next reference of the current node to the next node of the node to be deleted
		tmp.next = delNode.next;
		//3. List length - 1
		size--;
		//4. Empty the next reference of the node to be deleted
		delNode.next = null;
		//5. Save the deleted node
		list.add(delNode);
		//6. Reset the counter
		count = 1;
	} else {
		//Continue cycle count
		tmp = tmp.next;
		count++;
	}
}

//When there is only one node left in the linked list, you do not need to operate the next pointer to delete the node, and directly set the head and tail to null
tmp.next = null;
head = null;
tail = null;
size = 0;
list.add(tmp);

Note that after removing nodes, you must ensure that the linked list is still in a ring. The removal steps are broken down as follows (assuming that there are 3 nodes left in the linked list, you should remove the node numbered 3):

1.Node delNode = tmp.next

2.tmp.next = delNode.next

3.delNode.next = null

The complete code of counting out circles is as follows:

	/**
	 * Starting from the start position, the node goes out of the chain every step
	 *
	 * @param start Start position of alarm
	 * @param step Count out the lap interval
	 * @return List the nodes of the chain in turn
	 */
	public List<Node> poll(int start, int step) {
		if (start <= 0 || start > size) {
			throw new RuntimeException("The starting position must be greater than 0 and less than or equal to the length of the linked list");
		}
		if (step <= 0 || step > size) {
			throw new RuntimeException("Interval must be greater than 0");
		}
		if (size == 0) {
			return Collections.emptyList();
		}

		//Find the node that starts counting (here, start traversing from tail and take the last node of the counting node, because the node deletion of the one-way linked list must depend on the last node)
		Node tmp = tail;
		int startIndex = 0;
		while (startIndex++ != size) {
			if (start == startIndex) {
				break;
			}
			tmp = tmp.next;
		}

		//Save order out of chain nodes
		List<Node> list = new ArrayList<>(size);

		//Start counting. After counting to the specified interval, the node is out of the chain
		int count = 1;
		while (size > 1) {
			if (count == step) {
				//Node out of chain
				//1. Define a reference to the node to be deleted
				Node delNode = tmp.next;
				//2. Point the next reference of the current node to the next node of the node to be deleted
				tmp.next = delNode.next;
				//3. List length - 1
				size--;
				//4. Empty the next reference of the node to be deleted
				delNode.next = null;
				//5. Save the deleted node
				list.add(delNode);
				//6. Reset the counter
				count = 1;
			} else {
				//Continue cycle count
				tmp = tmp.next;
				count++;
			}
		}

		//When there is only one node left in the linked list, you do not need to operate the next pointer to delete the node, and directly set the head and tail to null
		tmp.next = null;
		head = null;
		tail = null;
		size = 0;
		list.add(tmp);

		return list;
	}

Test the above code:

//n: Number of people in the circle, k: the starting position of counting, m: the interval of counting out of the team
int n = 10;
int k = 2;
int m = 3;
			
List<Node> pollList = singleCycleLinkedList.poll(k, m);
System.out.printf("size: %d, start: %d, step: %d\n", n, k, m);
System.out.println(pollList.stream().map(node -> node.no).collect(Collectors.toList()));

The results are as follows:

size: 10, start: 2, step: 3
[4, 7, 10, 3, 8, 2, 9, 6, 1, 5]

data validation

When n = 10, k = 2, m = 3, the decomposition steps of node removal are as follows:

Complete nodes: Node{no=1}, Node{no=2}, Node{no=3}, Node{no=4}, Node{no=5}, Node{no=6}, Node{no=7}, Node{no=8}, Node{no=9}, Node{no=10}

4 out of loop: Node{no=1}, Node{no=2}, Node{no=3}, Node{no=5}, Node{no=6}, Node{no=7}, Node{no=8}, Node{no=9}, Node{no=10}

7 out of loop: Node{no=1}, Node{no=2}, Node{no=3}, Node{no=5}, Node{no=6}, Node{no=8}, Node{no=9}, Node{no=10}

10 out turn: Node{no=1}, Node{no=2}, Node{no=3}, Node{no=5}, Node{no=6}, Node{no=8}, Node{no=9}

3 out of loop: Node{no=1}, Node{no=2}, Node{no=5}, Node{no=6}, Node{no=8}, Node{no=9}

8 out of loop: Node{no=1}, Node{no=2}, Node{no=5}, Node{no=6}, Node{no=9}

2 out of loop: Node{no=1}, Node{no=5}, Node{no=6}, Node{no=9}

9 out of loop: Node{no=1}, Node{no=5}, Node{no=6}

6 out of loop: Node{no=1}, Node{no=5}

1 out of loop: Node{no=5}

5 out turn

The order of turning is as follows: [4, 7, 10, 3, 8, 2, 9, 6, 1, 5]. Consistent with the results.

Posted by curby on Sat, 06 Nov 2021 05:25:39 -0700