赞
踩
目录
约瑟夫环问题的一种描述是:编号为1,2,...,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直到所有人全部出列为止。
基本要求:利用链表模拟此过程,按照出列的顺序印出各人的编号。
链表是一种链式存储的线性表,用一组地址任意的存储单元存放线性表的数据元素,称存储单元为一个结点(节点)。单向循环链表与普通链表的区别在于:普通链表的最后一个链表的next指向NULL,而单向循环链表的最后一个节点的next指向头结点
首先因为处理问题时,链表元素的访问并不是从头开始的,而是采用“接力棒”的方式进行访问。所以应该使用不带有头结点的单向循环链表。
代码如下:
- typedef struct Data
- {
- int number; // 编号
- int code; // 密码
- /*
- 存放数据
- */
- } Data;
- typedef struct SqList
- {
- Data data; // 数据域
- SqList *next; // 指针域
- } SqList;
-
- SqList *CreateList(int n) // 生成一个不带头节点的n个元素的循环链表
- {
- if (n < 1) // 输入的n不合法
- {
- printf("输入不合法!");
- system("pause");
- return NULL;
- }
- SqList *end = new SqList; // 尾指针
- for (int i = 1; i < n + 1; i++)
- {
- SqList *newNode = (SqList *)malloc(sizeof(SqList *));
- if (i == 1) // 链表为空时输入首元节点的密码
- {
- newNode->next = newNode;
- cout << "请输入成员" << i << "的密码:";
- cin >> newNode->data.code;
- newNode->data.number = 1;
- end = newNode;
- continue;
- }
- // 链表不为空时输入newNode数据的密码
- cout << "请输入成员" << i << "的密码:";
- cin >> newNode->data.code;
- newNode->data.number = i;
- // 尾插法构建链表
- newNode->next = end->next;
- end->next = newNode;
- end = newNode;
- }
- return end->next;
- }
这里需要注意的是因为编号是有序的——按顺时针方向,所以采用尾插法。
代码如下:
- SqList *ListSearch(SqList *L, int k) // 返回链表中的第k个元素
- {
- if (!L || k < 1 || k > ListLength(L)) // 链表为空或输入的k值不合法
- {
- cout << "Error3!" << endl;
- system("pause");
- exit(0);
- }
- for (int i = 0; i < k - 1; i++)
- {
- L = L->next;
- }
- return L;
- }
代码如下:
- SqList *ListDelete(SqList *L, int n) // 删除链表L中指定的第n个元素
- {
- if (!L || n < 1) // 链表为空或删除位置不合法
- {
- cout << "Error2!" << endl;
- system("pause");
- exit(0);
- }
- SqList *front = L;
- SqList *temp = nullptr; // 暂时储存要删除的元素
- for (int i = 0; i < n - 1; i++) // 移动指针到要删除的位置上
- {
- L = L->next;
- }
- while (front->next != L) // 寻找要删除的元素前面的一个元素
- {
- front = front->next;
- }
- front->next = L->next; // 将要删除的元素移出链表
- temp = L;
- L = L->next;
- free(temp); // 释放内存
- return L;
- }
因为采用“接力棒”的方式访问元素,需要记住被删除元素的后一个元素。所以返回指针类型。
代码如下:
- int ListLength(SqList *L) // 返回链表长度
- {
- if (!L) // 输入空链表时报错并退出函数
- {
- cout << "Error1!";
- system("pause");
- exit(0);
- }
- SqList *F_Node = L;
- int count = 1; // 因为不带头节点的链表,所以非空时链表节点个数至少为1
- while (L->next != F_Node)
- {
- count++;
- L = L->next;
- }
- return count;
- }
-
- void ListPrint(SqList *L) // 打印链表L中的元素
- {
- if (!L)
- {
- return;
- }
- SqList *Node = L;
- do
- {
- if (Node->next == L)
- {
- cout << "成员" << Node->data.number << "的密码:" << Node->data.code << "\t";
- return;
- }
- cout << "成员" << Node->data.number << "的密码:" << Node->data.code << "\t";
- Node = Node->next;
- } while (1);
- return;
- }
-
整体代码:
- /*
- 整个实验重要的步骤就是创建、查找、删除
- */
- #include <iostream>
- using namespace std;
-
- typedef struct Data
- {
- int number; // 编号
- int code; // 密码
- /*
- 存放数据
- */
- } Data;
- typedef struct SqList
- {
- Data data; // 数据域
- SqList *next; // 指针域
- } SqList;
-
- SqList *CreateList(int n) // 生成一个不带头节点的n个元素的循环链表
- {
- if (n < 1) // 输入的n不合法
- {
- printf("输入不合法!");
- system("pause");
- return NULL;
- }
- SqList *end = new SqList; // 尾指针
- for (int i = 1; i < n + 1; i++)
- {
- SqList *newNode = (SqList *)malloc(sizeof(SqList *));
- if (i == 1) // 链表为空时输入首元节点的密码
- {
- newNode->next = newNode;
- cout << "请输入成员" << i << "的密码:";
- cin >> newNode->data.code;
- newNode->data.number = 1;
- end = newNode;
- continue;
- }
- // 链表不为空时输入newNode数据的密码
- cout << "请输入成员" << i << "的密码:";
- cin >> newNode->data.code;
- newNode->data.number = i;
- // 尾插法构建链表
- newNode->next = end->next;
- end->next = newNode;
- end = newNode;
- }
- return end->next;
- }
- int ListLength(SqList *L) // 返回链表长度
- {
- if (!L) // 输入空链表时报错并退出函数
- {
- cout << "Error1!";
- system("pause");
- exit(0);
- }
- SqList *F_Node = L;
- int count = 1; // 因为不带头节点的链表,所以非空时链表节点个数至少为1
- while (L->next != F_Node)
- {
- count++;
- L = L->next;
- }
- return count;
- }
- void ListPrint(SqList *L) // 打印链表L中的元素
- {
- if (!L)
- {
- return;
- }
- SqList *Node = L;
- do
- {
- if (Node->next == L)
- {
- cout << "成员" << Node->data.number << "的密码:" << Node->data.code << "\t";
- return;
- }
- cout << "成员" << Node->data.number << "的密码:" << Node->data.code << "\t";
- Node = Node->next;
- } while (1);
- }
-
- void ListInsert(SqList *L, Data elem, int a) // 向链表L中第a个位置后面插入数据elem
- {
- for (int i = 0; i < a - 1; i++)
- {
- L = L->next;
- }
- if (!L)
- {
- return;
- }
- SqList *newNode = new SqList;
- newNode->data = elem;
- newNode->next = L->next;
- L->next = newNode;
- }
-
- SqList *ListDelete(SqList *L, int n) // 删除链表L中指定的第n个元素
- {
- if (!L || n < 1) // 链表为空或删除位置不合法
- {
- cout << "Error2!" << endl;
- system("pause");
- exit(0);
- }
- SqList *front = L;
- SqList *temp = nullptr; // 暂时储存要删除的元素
- for (int i = 0; i < n - 1; i++) // 移动指针到要删除的位置上
- {
- L = L->next;
- }
- while (front->next != L) // 寻找要删除的元素前面的一个元素
- {
- front = front->next;
- }
- front->next = L->next; // 将要删除的元素移出链表
- temp = L;
- L = L->next;
- free(temp); // 释放内存
- return L;
- }
-
- SqList *ListSearch(SqList *L, int k) // 返回链表中的第k个元素
- {
- if (!L || k < 1 || k > ListLength(L)) // 链表为空或输入的k值不合法
- {
- cout << "Error3!" << endl;
- system("pause");
- exit(0);
- }
- for (int i = 0; i < k - 1; i++)
- {
- L = L->next;
- }
- return L;
- }
测试数据:m的初值为20;n=7,7个人的密码依次为:3,1,7,2,4,8,4
(正确的出列顺序应为6,1,4,7,2,3,5)。
测试如下:
- #define N 7 // 约瑟问题中的人数
-
- int main(void)
- {
- SqList *List = CreateList(N);
- int res[N]; // 存放出列顺序
- int m, i = 0, code = 0;
- cout << "请输入初始的正整数密码m" << endl;
- cin >> m;
- while (ListLength(List) != 1)
- {
- int k = 0;
- m = m % (N - i);
- // cout << "当前m的值是:" << m << endl;
- /*
- 注意在这里m的取值是[0,N-i-1],
- 而实际上m应该属于[1,N-i]。
- 所以需要对求模后的m进行条件判断
- */
- if (m == 0)
- {
- k = N - i;
- }
- else
- k = m;
- code = ListSearch(List, k)->data.code;
- res[i] = ListSearch(List, k)->data.number;
- List = ListDelete(List, k); // 返回新的起始访问位置
- m = code;
- i++;
- }
- res[i] = ListSearch(List, 1)->data.number; //此时链表中仅有一个元素
- cout << "出列顺序为:";
- for (int i = 0; i < N; i++)
- cout << res[i] << "\t";
- free(List); // 释放内存
- return 0;
- }
该问题的难点就在于元素的访问是采取“接力棒”的方式进行访问,需要对循环链表有足够的的理解,同时对密码的处理上也需要小心。对于本次实验来说,还有许多能改进的地方,比如非法输入的检测,可以把它包装成一个函数,这样处理的话,代码会更简洁并且更容易阅读。
如有错误,欢迎在评论区指正。以上内容若涉及侵权请联系作者进行删改。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。