数据结构(C语言版)-第2章-线性表(3)-静态链表解决约瑟夫问题

来源书籍:
数据结构(C语言版)(第2版·微课版) 秦锋 汤亚玲主编 清华大学出版社 2021年12月
书籍链接:
(http://www.tup.tsinghua.edu.cn/booksCenter/book_09444301.html#)

1.静态链表解决约瑟夫问题

1.1 约瑟夫问题求解

约瑟夫问题是一个经典的数学问题,描述如下:

有n个人围成一个圆圈,从第k个人开始报数,数到m的人出圈,然后从下一个人开始继续报数,数到m的人再次出圈,如此循环,直到剩下最后一个人。

静态链表是一种使用数组实现的链表结构,它通过数组的下标来模拟链表中的指针关系。

下面是利用静态链表求解约瑟夫问题的示例代码:

#include <stdio.h>

#define MAXSIZE 100

typedef struct {
    
    
    int data;  // 数据域
    int next;  // 指针域,存储下一个结点的下标
} Node;

int Josephus(int n, int k, int m) {
    
    
    Node nodeList[MAXSIZE];  // 静态链表数组
    int i, j, count;
    
    // 初始化静态链表
    for (i = 0; i < n; i++) {
    
    
        nodeList[i].data = i + 1;  // 数据域存储人的编号
        nodeList[i].next = (i + 1) % n;  // 指针域指向下一个结点
    }
    
    i = k;  // 从第k个人开始报数
    j = n;  // 记录剩余人数
    
    // 开始报数并出圈
    while (j > 1) {
    
    
        // 找到第m-1个结点
        for (count = 1; count < m - 1; count++) {
    
    
            i = nodeList[i].next;
        }
        
        // 删除第m个结点
        int nextNode = nodeList[i].next;  // 记录第m个结点的下一个结点
        nodeList[i].next = nodeList[nextNode].next;  // 将第m个结点的前一个结点指向第m个结点的下一个结点
        j--;  // 人数减少
        
        // 下一轮从下一个结点开始报数
        i = nodeList[i].next;
    }
    
    return nodeList[i].data;  // 返回最后剩下的人的编号
}

int main() {
    
    
    int n = 10;  // 总人数
    int k = 3;   // 从第3个人开始报数
    int m = 4;   // 数到第4个人出圈
    
    int lastPerson = Josephus(n, k, m);
    
    printf("最后剩下的人的编号是:%d\n", lastPerson);
    
    return 0;
}

1.2 代码解析

    for (i = 0; i < n; i++) {
    
    
        nodeList[i].data = i + 1;  // 数据域存储人的编号
        nodeList[i].next = (i + 1) % n;  // 指针域指向下一个结点
    }

在这段代码中,我们初始化了静态链表的数组。循环从0到n-1,对每个结点进行初始化操作。
nodeList[i].data = i + 1; 将当前结点的数据域存储为人的编号,这里使用 i + 1 是为了与实际人的编号对应,数组下标从0开始,而人的编号从1开始。
nodeList[i].next = (i + 1) % n; 设置当前结点的指针域,指向下一个结点。这里使用取模运算 (i + 1) % n 是为了形成一个循环链表,即最后一个结点的指针指向第一个结点
通过这样的初始化,我们建立了一个包含n个结点的循环链表,每个结点的数据域存储了人的编号,指针域指向下一个结点,从而实现了约瑟夫问题的求解。

    while (j > 1) {
    
    
        // 找到第m-1个结点
        for (count = 1; count < m - 1; count++) {
    
    
            i = nodeList[i].next;
        }
        
        // 删除第m个结点
        int nextNode = nodeList[i].next;  // 记录第m个结点的下一个结点
        nodeList[i].next = nodeList[nextNode].next;  // 将第m个结点的前一个结点指向第m个结点的下一个结点
        j--;  // 人数减少
        
        // 下一轮从下一个结点开始报数
        i = nodeList[i].next;
    }

在这段代码中,我们使用循环链表来解决约瑟夫问题。
首先,我们使用一个循环 while 循环来模拟报数和删除结点的过程,直到只剩下一个结点为止。
在循环内部,我们使用 for 循环找到第 m-1 个结点。通过循环 m-1 次,每次将当前结点的下一个结点作为当前结点,这样最终就能找到第 m-1 个结点。
接下来,我们需要删除第 m 个结点。我们首先记录第 m 个结点的下一个结点,然后将第 m-1 个结点的指针域指向第 m 个结点的下一个结点,从而将第 m 个结点从链表中删除。
然后,我们将人数减少 1,即 j–。
最后,我们更新变量 i,使其指向下一轮报数的起始结点,即当前结点的下一个结点。
这样,每一轮循环都模拟了报数和删除一个结点的过程,直到只剩下一个结点为止,完成了约瑟夫问题的求解。

int nextNode = nodeList[i].next;

这行代码的作用是将当前结点的下一个结点的位置存储到变量 nextNode 中。
具体来说,nodeList[i].next 表示当前结点的指针域,它存储了当前结点的下一个结点的位置。通过将 nodeList[i].next 的值赋给 nextNode,就将当前结点的下一个结点的位置保存下来,以便后续使用。
在约瑟夫问题的解决过程中,需要删除第 m 个结点,而 nextNode 就是用来记录第 m 个结点的下一个结点的位置。通过 nodeList[i].next = nodeList[nextNode].next; 这行代码,可以将第 m 个结点从链表中删除,然后继续下一轮的报数。
总之,nextNode 的值是为了获取当前结点的下一个结点的位置,方便进行链表操作。

nodeList[i].next = nodeList[nextNode].next; 

这行代码的作用是将第 m 个结点的前一个结点的指针域指向第 m 个结点的下一个结点。
具体来说,nodeList[i].next表示第 m-1 个结点的指针域,它存储了第 m 个结点的位置。而nodeList[nextNode].next表示第 m 个结点的下一个结点的位置。
通过将nodeList[i].next赋值为nodeList[nextNode].next,就实现了将第 m 个结点从链表中删除的操作。即将第 m-1 个结点的指针域指向第 m 个结点的下一个结点,跳过了第 m 个结点,相当于将第 m 个结点从链表中移除。
这样,在后续的循环中,当再次经过第 m-1 个结点时,它的指针域就会指向第 m 个结点的下一个结点,从而跳过了已经被删除的结点,实现了约瑟夫问题中每次删除第 m 个结点的操作。

i = nodeList[i].next;

这行代码的作用是将指针 i 移动到下一个结点,以便进行下一轮的报数。
在约瑟夫问题的求解过程中,每轮报数完毕后需要移动到下一个结点,以继续进行下一轮的报数。通过 i = nodeList[i].next; 这行代码,将指针 i 更新为当前结点的下一个结点,实现了指针的移动。
该行代码放在循环中的最后,确保每轮报数完毕后指针都能正确地移动到下一个结点,以便进行下一轮的报数。

return nodeList[i].data

这行代码的作用是返回约瑟夫问题中最后剩下的那个人的编号。
在约瑟夫问题的求解过程中,当只剩下最后一个人时,循环结束,此时 nodeList[i].data 存储了最后剩下的那个人的编号。通过 return nodeList[i].data; 这行代码,将最后剩下的人的编号作为函数的返回值,以便在调用该函数时获取到结果。
可以将该代码放在解决约瑟夫问题的循环之后,表示在循环结束后返回最后剩下的人的编号。

猜你喜欢

转载自blog.csdn.net/aaaccc444/article/details/130547515