当前位置:   article > 正文

数据结构学习-循环链表:处理约瑟夫环问题_循环链表约瑟夫环

循环链表约瑟夫环

目录

问题描述

一、基本概念

 1.普通链表

2.单向循环链表 

二、问题处理

1.创建链表

2.查找

3.删除

 4.其他

 三.实验环节

四.总结

问题描述

约瑟夫环问题的一种描述是:编号为1,2,...,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直到所有人全部出列为止。

基本要求:利用链表模拟此过程,按照出列的顺序印出各人的编号。

一、基本概念

链表是一种链式存储的线性表,用一组地址任意的存储单元存放线性表的数据元素,称存储单元为一个结点(节点)。单向循环链表与普通链表的区别在于:普通链表的最后一个链表的next指向NULL,而单向循环链表的最后一个节点的next指向头结点

 1.普通链表

2.单向循环链表 

 

二、问题处理

1.创建链表

首先因为处理问题时,链表元素的访问并不是从头开始的,而是采用“接力棒”的方式进行访问。所以应该使用不带有头结点的单向循环链表。

代码如下: 

  1. typedef struct Data
  2. {
  3. int number; // 编号
  4. int code; // 密码
  5. /*
  6. 存放数据
  7. */
  8. } Data;
  9. typedef struct SqList
  10. {
  11. Data data; // 数据域
  12. SqList *next; // 指针域
  13. } SqList;
  14. SqList *CreateList(int n) // 生成一个不带头节点的n个元素的循环链表
  15. {
  16. if (n < 1) // 输入的n不合法
  17. {
  18. printf("输入不合法!");
  19. system("pause");
  20. return NULL;
  21. }
  22. SqList *end = new SqList; // 尾指针
  23. for (int i = 1; i < n + 1; i++)
  24. {
  25. SqList *newNode = (SqList *)malloc(sizeof(SqList *));
  26. if (i == 1) // 链表为空时输入首元节点的密码
  27. {
  28. newNode->next = newNode;
  29. cout << "请输入成员" << i << "的密码:";
  30. cin >> newNode->data.code;
  31. newNode->data.number = 1;
  32. end = newNode;
  33. continue;
  34. }
  35. // 链表不为空时输入newNode数据的密码
  36. cout << "请输入成员" << i << "的密码:";
  37. cin >> newNode->data.code;
  38. newNode->data.number = i;
  39. // 尾插法构建链表
  40. newNode->next = end->next;
  41. end->next = newNode;
  42. end = newNode;
  43. }
  44. return end->next;
  45. }

这里需要注意的是因为编号是有序的——按顺时针方向,所以采用尾插法

2.查找

代码如下:

  1. SqList *ListSearch(SqList *L, int k) // 返回链表中的第k个元素
  2. {
  3. if (!L || k < 1 || k > ListLength(L)) // 链表为空或输入的k值不合法
  4. {
  5. cout << "Error3!" << endl;
  6. system("pause");
  7. exit(0);
  8. }
  9. for (int i = 0; i < k - 1; i++)
  10. {
  11. L = L->next;
  12. }
  13. return L;
  14. }

3.删除

代码如下:

  1. SqList *ListDelete(SqList *L, int n) // 删除链表L中指定的第n个元素
  2. {
  3. if (!L || n < 1) // 链表为空或删除位置不合法
  4. {
  5. cout << "Error2!" << endl;
  6. system("pause");
  7. exit(0);
  8. }
  9. SqList *front = L;
  10. SqList *temp = nullptr; // 暂时储存要删除的元素
  11. for (int i = 0; i < n - 1; i++) // 移动指针到要删除的位置上
  12. {
  13. L = L->next;
  14. }
  15. while (front->next != L) // 寻找要删除的元素前面的一个元素
  16. {
  17. front = front->next;
  18. }
  19. front->next = L->next; // 将要删除的元素移出链表
  20. temp = L;
  21. L = L->next;
  22. free(temp); // 释放内存
  23. return L;
  24. }

因为采用“接力棒”的方式访问元素,需要记住被删除元素的后一个元素。所以返回指针类型。

 4.其他

代码如下:

  1. int ListLength(SqList *L) // 返回链表长度
  2. {
  3. if (!L) // 输入空链表时报错并退出函数
  4. {
  5. cout << "Error1!";
  6. system("pause");
  7. exit(0);
  8. }
  9. SqList *F_Node = L;
  10. int count = 1; // 因为不带头节点的链表,所以非空时链表节点个数至少为1
  11. while (L->next != F_Node)
  12. {
  13. count++;
  14. L = L->next;
  15. }
  16. return count;
  17. }
  18. void ListPrint(SqList *L) // 打印链表L中的元素
  19. {
  20. if (!L)
  21. {
  22. return;
  23. }
  24. SqList *Node = L;
  25. do
  26. {
  27. if (Node->next == L)
  28. {
  29. cout << "成员" << Node->data.number << "的密码:" << Node->data.code << "\t";
  30. return;
  31. }
  32. cout << "成员" << Node->data.number << "的密码:" << Node->data.code << "\t";
  33. Node = Node->next;
  34. } while (1);
  35. return;
  36. }

 三.实验环节

整体代码:

  1. /*
  2. 整个实验重要的步骤就是创建、查找、删除
  3. */
  4. #include <iostream>
  5. using namespace std;
  6. typedef struct Data
  7. {
  8. int number; // 编号
  9. int code; // 密码
  10. /*
  11. 存放数据
  12. */
  13. } Data;
  14. typedef struct SqList
  15. {
  16. Data data; // 数据域
  17. SqList *next; // 指针域
  18. } SqList;
  19. SqList *CreateList(int n) // 生成一个不带头节点的n个元素的循环链表
  20. {
  21. if (n < 1) // 输入的n不合法
  22. {
  23. printf("输入不合法!");
  24. system("pause");
  25. return NULL;
  26. }
  27. SqList *end = new SqList; // 尾指针
  28. for (int i = 1; i < n + 1; i++)
  29. {
  30. SqList *newNode = (SqList *)malloc(sizeof(SqList *));
  31. if (i == 1) // 链表为空时输入首元节点的密码
  32. {
  33. newNode->next = newNode;
  34. cout << "请输入成员" << i << "的密码:";
  35. cin >> newNode->data.code;
  36. newNode->data.number = 1;
  37. end = newNode;
  38. continue;
  39. }
  40. // 链表不为空时输入newNode数据的密码
  41. cout << "请输入成员" << i << "的密码:";
  42. cin >> newNode->data.code;
  43. newNode->data.number = i;
  44. // 尾插法构建链表
  45. newNode->next = end->next;
  46. end->next = newNode;
  47. end = newNode;
  48. }
  49. return end->next;
  50. }
  51. int ListLength(SqList *L) // 返回链表长度
  52. {
  53. if (!L) // 输入空链表时报错并退出函数
  54. {
  55. cout << "Error1!";
  56. system("pause");
  57. exit(0);
  58. }
  59. SqList *F_Node = L;
  60. int count = 1; // 因为不带头节点的链表,所以非空时链表节点个数至少为1
  61. while (L->next != F_Node)
  62. {
  63. count++;
  64. L = L->next;
  65. }
  66. return count;
  67. }
  68. void ListPrint(SqList *L) // 打印链表L中的元素
  69. {
  70. if (!L)
  71. {
  72. return;
  73. }
  74. SqList *Node = L;
  75. do
  76. {
  77. if (Node->next == L)
  78. {
  79. cout << "成员" << Node->data.number << "的密码:" << Node->data.code << "\t";
  80. return;
  81. }
  82. cout << "成员" << Node->data.number << "的密码:" << Node->data.code << "\t";
  83. Node = Node->next;
  84. } while (1);
  85. }
  86. void ListInsert(SqList *L, Data elem, int a) // 向链表L中第a个位置后面插入数据elem
  87. {
  88. for (int i = 0; i < a - 1; i++)
  89. {
  90. L = L->next;
  91. }
  92. if (!L)
  93. {
  94. return;
  95. }
  96. SqList *newNode = new SqList;
  97. newNode->data = elem;
  98. newNode->next = L->next;
  99. L->next = newNode;
  100. }
  101. SqList *ListDelete(SqList *L, int n) // 删除链表L中指定的第n个元素
  102. {
  103. if (!L || n < 1) // 链表为空或删除位置不合法
  104. {
  105. cout << "Error2!" << endl;
  106. system("pause");
  107. exit(0);
  108. }
  109. SqList *front = L;
  110. SqList *temp = nullptr; // 暂时储存要删除的元素
  111. for (int i = 0; i < n - 1; i++) // 移动指针到要删除的位置上
  112. {
  113. L = L->next;
  114. }
  115. while (front->next != L) // 寻找要删除的元素前面的一个元素
  116. {
  117. front = front->next;
  118. }
  119. front->next = L->next; // 将要删除的元素移出链表
  120. temp = L;
  121. L = L->next;
  122. free(temp); // 释放内存
  123. return L;
  124. }
  125. SqList *ListSearch(SqList *L, int k) // 返回链表中的第k个元素
  126. {
  127. if (!L || k < 1 || k > ListLength(L)) // 链表为空或输入的k值不合法
  128. {
  129. cout << "Error3!" << endl;
  130. system("pause");
  131. exit(0);
  132. }
  133. for (int i = 0; i < k - 1; i++)
  134. {
  135. L = L->next;
  136. }
  137. return L;
  138. }

测试数据:m的初值为20;n=7,7个人的密码依次为:3,1,7,2,4,8,4

(正确的出列顺序应为6,1,4,7,2,3,5)。 

测试如下:

  1. #define N 7 // 约瑟问题中的人数
  2. int main(void)
  3. {
  4. SqList *List = CreateList(N);
  5. int res[N]; // 存放出列顺序
  6. int m, i = 0, code = 0;
  7. cout << "请输入初始的正整数密码m" << endl;
  8. cin >> m;
  9. while (ListLength(List) != 1)
  10. {
  11. int k = 0;
  12. m = m % (N - i);
  13. // cout << "当前m的值是:" << m << endl;
  14. /*
  15. 注意在这里m的取值是[0,N-i-1],
  16. 而实际上m应该属于[1,N-i]。
  17. 所以需要对求模后的m进行条件判断
  18. */
  19. if (m == 0)
  20. {
  21. k = N - i;
  22. }
  23. else
  24. k = m;
  25. code = ListSearch(List, k)->data.code;
  26. res[i] = ListSearch(List, k)->data.number;
  27. List = ListDelete(List, k); // 返回新的起始访问位置
  28. m = code;
  29. i++;
  30. }
  31. res[i] = ListSearch(List, 1)->data.number; //此时链表中仅有一个元素
  32. cout << "出列顺序为:";
  33. for (int i = 0; i < N; i++)
  34. cout << res[i] << "\t";
  35. free(List); // 释放内存
  36. return 0;
  37. }

 

四.总结

该问题的难点就在于元素的访问是采取“接力棒”的方式进行访问,需要对循环链表有足够的的理解,同时对密码的处理上也需要小心。对于本次实验来说,还有许多能改进的地方,比如非法输入的检测,可以把它包装成一个函数,这样处理的话,代码会更简洁并且更容易阅读。

如有错误,欢迎在评论区指正。以上内容若涉及侵权请联系作者进行删改。 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/551217
推荐阅读
相关标签
  

闽ICP备14008679号