C language - Joseph problem (Joseph Ring)

Posted by BeanoEFC on Thu, 27 Jan 2022 09:13:33 +0100

Joseph problem is also called Joseph Ring. Joseph problem has many variants. This paper introduces several solutions to several classical Joseph problems.

Question 1: Lu Zhishen eats steamed bread. It is said that Lu Zhishen hurried to daxiangguo temple in Kaifeng at noon one day and wanted to have a meal. At that time, there were 99 monks in daxiangguo temple and only made 99 steamed buns. Elder Zhiqing didn't want to offend Lu Zhishen, so he arranged him in a specific position, and then said to everyone: since I started counting (forming a circle), the fifth person can eat steamed bread (and withdraw); The next person to retire starts from 1. The fifth person can eat steamed bread (and retire)... According to this method, all monks ate steamed bread, but Lu Zhishen didn't eat it. Where is he?

We can use an array with a length of 101 and initialize each element to 0 (int a[101] = {0};). Use 1 ~ 100 to represent the serial number of 100 monks and Lu Zhishen. a[i]=0 means that the ith person didn't eat steamed bread, and a[i]=1 means that the ith person ate steamed bread.

Secondly, a variable num is used to store the number of steamed buns (int num = 99;), One less steamed bread is num--;. When num=0, it is considered that the steamed bread has been divided. At this time, let I traverse from 1 to 100. If a[i]=0, it means that the ith person did not eat the steamed bread, and I is Lu Zhishen's position.

Let's look at the description of the title: every five people eat steamed bread, that is, every five elements of the array, we should make the element 1. Because it is a circle, so the last person has to return to the first person. And those who eat steamed bread will step back, that is, they should skip the element with element 1 in the next traversal. You can use a variable flag to record how many elements have passed.

Based on this, we can roughly sort out the following ideas:

int a[101] = {0};
int num = 99, flag = 0;
for (int i=1; num>0; i++)
{
    if (a[i] == 0)
    {
        flag++;
        if (flag == 5)
        {
            a[i] = 1;
            num--;
            flag = 0;
        }
    }
    if (i == 100)
    {
        i = 0;
    }
}
for (int i=1; i<=100; i++)
{
    if (a[i] == 0)
    {
        printf("Lu Zhishen in the second%d Location", i);
        break;
    }
}

This method of solving Joseph's problem is called sieve method. What we said before C language -- sieve method for finding prime numbers (prime numbers) This method is also introduced in the article.

Question 2: n knights with numbers 1, 2,..., n sit around the round table. The knights with number 1 count from 1, and the knights with number m stand out, and then count from 1 in the next position to find out the number of knights left at the round table.

Compared with the above problems, in fact, the monk and Lu Zhishen changed their names, changing skin instead of meat. Therefore, the above-mentioned screening method can also solve this problem quickly. Let's introduce another method:

Like the above algorithm, we use 1~n to represent the serial numbers of N knights. The difference is that we construct a circular linked list with elements with 1~n as nodes. Define the pointer P, which starts from pointing to the first node. Each number reported by the knight is equivalent to p advancing one node along the circular linked list, and the knight reporting to m is equivalent to deleting node P in the circular linked list. Finally, the remaining nodes are the solution.

The algorithm is described as follows:

#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
    int data;
    struct node *next;
} CLinkList;
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);//Input: the number of knights n and the number m listed on check-in
    CLinkList *R = (CLinkList *)malloc(sizeof(CLinkList)), *p, *s;
    p = R;
    for (int i=1; i<=n; i++)
    {
        s = (CLinkList *)malloc(sizeof(CLinkList));
        s->data = i;
        s->next = NULL;
        p->next = s;
        p = p->next;
    }
    p->next = R->next;
    R = p;
    //Construct a circular linked list so that the value of each node is equal to i(i=1,2,3,..., n). At this time, p points to the last node n
    for (int i=1; i<n; i++)
    {
        for (int j=1; j<m; j++)
        {
            p = p->next;
        }
        //Make p advance m-1 nodes along the circular linked list
        s = p->next;
        p->next = s->next;
        free(s);
        //Delete the next node of node p
    }
    printf("The number of the knight left at the round table is%d", p->data);//output
    return 0;//End of algorithm
}

It can be seen that the advantage of this algorithm is to directly change the reported knight from the original skip operation to the delete operation, saving an operation to judge whether the number has been reported to m; In addition, because it is a circular linked list, there is no need to judge whether it has reached the end of the array. The use of linked list can reduce these two judgments and optimize the algorithm to a certain extent, but it also increases the difficulty of thinking.

Question 3: there are N cards, recorded as 1, 2,..., n. Put their cards face down and vertically stacked together. How should they be arranged so that: the first card drawn from the top is 1, then insert the two cards behind the card into the end of the stack in turn, and draw one on the face, which is exactly 2; Then insert the three cards behind the card into the end of the stack in turn, and take out one on the face, which is exactly 3; This continues until the last one is n.

If the 8 cards are arranged in the order of 1, 7, 5, 2, 6, 8, 4 and 3, it just meets the situation of N=8 in the problem. As shown in the figure:

Change of card sequence when N=8
12345678
7677688
585587
24667
6388
874
45
3

Similarly, we use the way of circular linked list to solve this problem, so looking at the card point on the card surface can be regarded as reading the ith data element in the linear table, drawing cards can be regarded as deleting operation, and inserting the end of the stack can be regarded as inserting operation.

Different from the first two problems, after deleting one node at a time and deleting another node after M nodes, m will increase by one in turn; And you need to record the order of deleting nodes. With the help of question 2, this question can be expressed as follows: N knights with numbers 1, 2,..., N sit around the round table. The knights with number 1 report from 1, the knights with number 1 report out, and then the next position reports from 1, the knights with number 2 report out, and then the next position reports from 1, Check in 3 Knight out... Record the order of Knight out from number 1 to number N.

Therefore, we can consider storing the desired card sequence in array B and constructing a circular linked list with the subscript of array B as the node. Define a pointer p, starting from p to the first node. The operation of pulling out the card on the face is equivalent to deleting node p in the circular linked list. The operation of putting i cards at the end is equivalent to that p advances i nodes along the circular linked list and stores i in a position in array B. finally, the card sequence in array B is the solution.

The algorithm is described as follows:

#include <stdio.h>
#include <stdlib.h>
typedef struct node
{
    int data;
    struct node *next;
} CLinkList;
int main()
{
    int N;
    scanf("%d", &N);//Input: total number of cards N
    int B[N+1];//Apply for one-dimensional array B with length N+1 to store the required card sequence
    CLinkList *R = (CLinkList *)malloc(sizeof(CLinkList)), *p, *s;
    p = R;
    for (int i=1; i<=N; i++)
    {
        s = (CLinkList *)malloc(sizeof(CLinkList));
        s->data = i;
        s->next = NULL;
        p->next = s;
        p = p->next;
    }
    p->next = R->next;
    R = p;
    //Construct a circular linked list so that the value of each node is equal to i(i=1,2,3,..., N). At this time, the pointer p points to the last node N
    B[1] = 1;
    s = p->next;
    p->next = s->next;
    free(s);
    p = p->next;
    //Make B[1]=1 and delete the header node; Make p point to the next node
    for (int i=2; i<N; i++)
    {
        for (int j=1; j<i; j++)
        {
            p = p->next;
        }
        //Make p advance i-1 nodes along the circular linked list
        B[p->next->data] = i;
        s = p->next;
        p->next = s->next;
        free(s);
        p = p->next;
        //Delete the next node of node p and make p point to the next node
    }
    B[p->next->data] = N;
    printf("The card sequence is:");
    for (int i=1; i<=N; i++)
    {
        printf("%d,", B[i]);
    }//Output: one dimensional array B
    return 0;//End of algorithm
}

Joseph problem often appears in various programming competitions. Its core idea is to use the knowledge of circular linked list to realize the solution of Joseph Ring through the deletion and traversal of circular linked list.

reference:

Wen Yimin, Zhang Ruixia, Li Jian, data structure and algorithm (2nd Edition), Tsinghua University Press, P19-20, P36-37

Topics: C data structure Back-end linked list