• 头脑风暴之约瑟夫环问题


    一 问题的引入

    约瑟夫问题的源头完全可以命名为“自杀游戏”。本着和谐友爱和追求本质的目的,可以把问题描述如下:

    • 现有n个人围成一桌坐下,编号从1到n,从编号为1的人开始报数。
    • 报数也从1开始,报到m人离席,从离席者的下一位在座成员开始,继续从1开始报数。
    • 复现这个过程(各成员的离席次序),或者求最后一个在座的成员编号。

    二 思路的讲解

    1. 想必我们看到这个游戏场景,再结合链表相关的知识,我们也就大概有了一个方向了吧~~~

    没错,解决约瑟夫问题的关键就是创建一个带环链表

     2.当我们链表创建好之后,就是考虑如何讲单链表转换成带头循环链表

    是滴,就是将我们的链表的尾结点指向我们的头节点即可

    ptail->next = phead;

     对应代码如下:

    1. ListNode* CreatList(int x)//链表创建
    2. {
    3. ListNode* phead = ListBuyNode(1);//注意是从数据1开始为每一个人创建结点
    4. ListNode* ptail = phead;//注意当链表只有一个数据时,头节点也是尾结点
    5. //来到这里说明头节点已经创建好,下面就需要进行尾插即可,尾插之前需找到前面的结点
    6. for (int i = 2; i <= x; i++)
    7. {
    8. ListNode* node = ListBuyNode(i);
    9. ptail->next = node;
    10. ptail = ptail->next;//尾结点时刻更新
    11. }
    12. //以上只是单链表创建好了,下面需把他变成单向循环链表
    13. ptail->next = phead;
    14. return ptail;//返回尾结点即可,有了尾结点可以直接找到头节点,若是返回头节点,需要遍历才可以找到尾结点
    15. }

    3.以上我们把前期准备工作已经做好了,接下来我们开始约瑟夫游戏

    其实就是一个删除结点的问题

    注意我们这里不能直接删除结点

    1.)删除结点之前我们需要先找到这个结点的前一个结点,也就是pre这个结点

    2.)其次就是找到这个结点的后一个结点,即pcur->next;

    3.)最最最重要的是,我们在删除这个结点之后,不要忘了让下一个人重新报数

    草图如下:

     代码如下:

     接下来重复以上操作即可,也就是对应代码里面的循环,具体详见代码:

    1. while (pcur->next != pcur)
    2. {
    3. if (count == m)
    4. {
    5. //报到为m 的人直接删除就Ok
    6. pre->next = pcur->next;
    7. free(pcur);//此时pcur是个野指针
    8. pcur = pre->next;
    9. count = 1;//删除结点后,别忘了count 是从1重新开始报数
    10. }
    11. else
    12. {
    13. pre = pcur;//pcur移动之前,需让pre 来保存pcur位置,
    14. pcur = pcur->next;
    15. count++;//注意别忘了要报数
    16. }
    17. }

    相信各位对以上的分析应该有了自己的理解了吧~~~

     

    对于IO答题方式:完整代码如下:

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. #include
    4. #include
    5. int yef(int x, int y);
    6. typedef struct ListNode
    7. {
    8. int val;//数据域
    9. struct ListNode* next;//指针域
    10. }ListNode;//重命名
    11. ListNode* ListBuyNode(int x)//创建结点
    12. {
    13. ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    14. if (node == NULL)//会存在开辟失败
    15. {
    16. perror("malloc fail\n");
    17. return 5;
    18. }
    19. //空间开辟成功
    20. node->val = x;
    21. node->next = NULL;
    22. return node;
    23. }
    24. ListNode* CreatList(int x)//链表创建
    25. {
    26. ListNode* phead = ListBuyNode(1);//注意是从数据1开始为每一个人创建结点
    27. ListNode* ptail = phead;//注意当链表只有一个数据时,头节点也是尾结点
    28. //来到这里说明头节点已经创建好,下面就需要进行尾插即可,尾插之前需找到前面的结点
    29. for (int i = 2; i <= x; i++)
    30. {
    31. ListNode* node = ListBuyNode(i);
    32. ptail->next = node;
    33. ptail = ptail->next;//尾结点时刻更新
    34. }
    35. //以上只是单链表创建好了,下面需把他变成单向循环链表
    36. ptail->next = phead;
    37. return ptail;//返回尾结点即可,有了尾结点可以直接找到头节点,若是返回头节点,需要遍历才可以找到尾结点
    38. }
    39. int ysf(int n, int m) {
    40. ListNode* ptail = CreatList(n);//为1~n个人创建单循环链表,注意链表创建返回的就是尾结点
    41. //开始游戏,涉及到删除结点,注意不能直接删除,删除前需要先找到对应的前一个结点和后一个结点
    42. ListNode* pcur = ptail->next;//游戏是从第一个人开始的
    43. ListNode* pre = ptail;//当前节点的前一个结点
    44. int count = 1;//就是一个报数器,注意是从1开始的而不是0开始的,因为游戏是从第一个人开始
    45. while (pcur->next != pcur)
    46. {
    47. if (count == m)
    48. {
    49. //报到为m 的人直接删除就Ok
    50. pre->next = pcur->next;
    51. free(pcur);//此时pcur是个野指针
    52. pcur = pre->next;
    53. count = 1;//删除结点后,别忘了count 是从1重新开始报数
    54. }
    55. else
    56. {
    57. pre = pcur;//pcur移动之前,需让pre 来保存pcur位置,
    58. pcur = pcur->next;
    59. count++;//注意别忘了要报数
    60. }
    61. }
    62. //此时只剩一个结点
    63. return pcur->val;
    64. }
    65. int main()
    66. {
    67. int ret = ysf(43,9001);
    68. printf("%d", ret);
    69. return 0;
    70. }

    对于OJ的答题方式,完整代码如下

    1. //解答思路 首先创建一个带头单向循环链表 其次删除这个链表的结点,注意不能直接删除,要找到删除此节点的前一个和后一个结点
    2. typedef struct ListNode ListNode;//重命名
    3. ListNode* ListBuyNode(int x)//创建结点
    4. {
    5. ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    6. if(node == NULL)//会存在开辟失败
    7. {
    8. perror("malloc fail\n");
    9. }
    10. //空间开辟成功
    11. node->val = x;
    12. node->next = NULL;
    13. return node;
    14. }
    15. ListNode* CreatList(int x)//链表创建
    16. {
    17. ListNode* phead = ListBuyNode(1);//注意是从数据1开始为每一个人创建结点
    18. ListNode* ptail = phead;//注意当链表只有一个数据时,头节点也是尾结点
    19. //来到这里说明头节点已经创建好,下面就需要进行尾插即可,尾插之前需找到前面的结点
    20. for(int i = 2;i <= x;i++)
    21. {
    22. ListNode* node = ListBuyNode(i);
    23. ptail->next = node;
    24. ptail = ptail->next;//尾结点时刻更新
    25. }
    26. //以上只是单链表创建好了,下面需把他变成单向循环链表
    27. ptail->next = phead;
    28. return ptail;//返回尾结点即可,有了尾结点可以直接找到头节点,若是返回头节点,需要遍历才可以找到尾结点
    29. }
    30. int ysf(int n, int m ) {
    31. ListNode* pre = CreatList(n);//为1~n个人创建单循环链表,注意链表创建返回的就是尾结点
    32. //开始游戏,涉及到删除结点,注意不能直接删除,删除前需要先找到对应的前一个结点和后一个结点
    33. ListNode* pcur = pre->next;//游戏是从第一个人开始的
    34. int count = 1;//就是一个报数器,注意是从1开始的而不是0开始的,因为游戏是从第一个人开始
    35. while(pcur->next != pcur)
    36. {
    37. if(count == m)
    38. {
    39. //报到为m 的人直接删除就Ok
    40. pre->next = pcur->next;
    41. free(pcur);//此时pcur是个野指针
    42. pcur = pre->next;
    43. count = 1;//删除结点后,别忘了count 是从1重新开始报数
    44. }
    45. else
    46. {
    47. pre = pcur;//pcur移动之前,需让pre 来保存pcur位置,
    48. pcur = pcur->next;
    49. count++;//注意别忘了要报数
    50. }
    51. }
    52. //此时只剩一个结点
    53. return pcur->val;
    54. }

     各位大佬都已经来这里了,若是觉得还不错,咱点个赞,互关一下呗,蟹蟹大家了,小生有礼了。

  • 相关阅读:
    安全防护的原则
    linux——(4)磁盘与文件系统管理
    一起来打靶 02
    HOT100与剑指Offer
    2024能源动力、机械自动化与航天航空技术国际学术会议(ICEPMAT2024)
    0X03
    联发科最先完成WiFi 7演示,速度是WiFi 6的2.4倍
    ElementPlus·表单验证
    LeetCode-重新安排行程(C++)
    【iOS】引用计数与autorelease
  • 原文地址:https://blog.csdn.net/X_do_myself/article/details/133966689