设编号为1,2, ……,n的n 个人按顺时针方向围坐一圈,约定编号为k (1≤k≤n) 的人按顺时针方向从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。试设计算法求出n个人的出列顺序。
程序运行时,首先要求用户指定人数n,第一个开始报数的人的编号k及报数上限值m。然后,按照出列的顺序打印出相应的编号序列。
1)由于报到m的人出列的动作对应着数据元素的删除操作,而且这种删除操作比较频繁,因此单向循环链表适于作为存储结构来模拟此过程。而且,为了保证程序指针每一次都指向一个具体的数据元素结点,应使用不带头结点循环链表作为存储结构。相应地,需要注意空表和非空表的区别。
2)思路:创建一个含有n个结点的单循环链表,然后由第一个结点起从1开始计数(此时假设k-1),计到m时,对应结点从链表中删除,接下来从被删除结点的下一个结点重新开始从1开始计数,计到m时,从链表中删除对应结点,如此循环, 直至最后一个结点从链表中删除,算法结束。
1) 在顺序结构上实现本算法。需要考虑如何实现循环的顺序结构。
2) m不再固定。假设n个人每人持有一个密码(正整数),从编号为k的人开始从1开始顺序报数,报到m的人出列,此时将他的密码作为新的m值,从他顺时针方向上的下一个人开始重新从1报数,报到m的人出列,而后将他的密码作为新的值,如此循环 下去,直到所有人全部出列为止。
3)显示仿真的运行界面。
代码1(单循环链表实现,无头结点):
#include
using namespace std;
typedef struct List {
int num;
struct List* next;
}Node;
List* creatList() {
List* newList = (List*)malloc(sizeof(struct List));
newList->next = newList;
newList->num = 1;
return newList;
}
void push_back(List* list, int num) {
Node* curNode = list;
List* newNode = (List*)malloc(sizeof(struct List));
newNode->num = num;
while (curNode->next != list) {
curNode = curNode->next;
}
curNode->next = newNode;
newNode->next = list;
}
//这个print函数我是用来检验链表的数据是否正确插入的
void print(List* list) {
Node* curNode = list->next;
while (curNode != list) {
printf("%d ", curNode->num);
curNode = curNode->next;
}
putchar('\n');
}
void j_find(List* list,int m,int k,int n) {
//从编号为k的人开始
//数到m的人出列
Node* preNode = list;
Node* curNode = list->next;
while (curNode->num != k) {
preNode = preNode->next;
curNode = curNode->next;
}
while (n) {
for (int i = 0; i < m-1 ; i++) {
preNode = preNode->next;
curNode = curNode->next;
}
printf("%d ", curNode->num);
n--;
preNode->next = curNode->next;
curNode = curNode->next;
}
}
int main() {
List* newList = creatList();
int n, k, m;
cout << "请输入n,m,k:";
cin >> n >> k >> m;
if (n < 0 || k < 0 || m<0 || k>n) {
cout << "你输入的数据有误!";
return 0;
}
for (int i = 2; i <= n; i++) {
push_back(newList,i);
}
// print(newList);
j_find(newList, m, k,n);
}
分析:
首先是单循环链表的定义,和普通链表没啥区别:
typedef struct List {
int num;
struct List* next;
}Node;
然后是创建一个新的链表,因为是没有头结点的,所以我在创建的时候就把创建的这个结点中的值定为了1。n的值肯定是大于等于1的嘛。
List* creatList() {
List* newList = (List*)malloc(sizeof(struct List));
newList->next = newList;
newList->num = 1;
return newList;
}
尾插法:
void push_back(List* list, int num) {
Node* curNode = list;
List* newNode = (List*)malloc(sizeof(struct List));
newNode->num = num;
while (curNode->next != list) { //找到链表尾
curNode = curNode->next;
}
curNode->next = newNode;
newNode->next = list;
}
最关键的是这个函数:
我的思路是先定义两个指针,用于删除操作;然后找到我们开始的位置k;然后有n个人,所以我们要输出n次,用n来控制while循环的次数;while循环内首先进行移动,即开始报数,最后curNode指针指向的结点就是我们要删除的节点。
void j_find(List* list,int m,int k,int n) {
//从编号为k的人开始
//数到m的人出列
Node* preNode = list; //用于删除操作
Node* curNode = list->next;
while (curNode->num != k) { //找到开始的位置k
preNode = preNode->next;
curNode = curNode->next;
}
while (n) {
for (int i = 0; i < m-1 ; i++) { //开始报数
preNode = preNode->next;
curNode = curNode->next;
}
printf("%d ", curNode->num);
n--;
preNode->next = curNode->next;
curNode = curNode->next;
}
}
代码2(单循环链表实现,有头结点):
#include
using namespace std;
typedef struct List {
int num;
struct List* next;
}Node;
List* creatList() {
List* newList = (List*)malloc(sizeof(struct List));
newList->next = newList;
return newList;
}
void push_back(List* list, int num) {
Node* curNode = list;
List* newNode = (List*)malloc(sizeof(struct List));
newNode->num = num;
while (curNode->next != list) {
curNode = curNode->next;
}
curNode->next = newNode;
newNode->next = list;
}
void print(List* list) {
Node* curNode = list->next;
while (curNode != list) {
printf("%d ", curNode->num);
curNode = curNode->next;
}
putchar('\n');
}
void j_find(List* list,int m,int k) {
//从编号为k的人开始
//数到m的人出列
Node* preNode = list;
Node* curNode = list->next;
while (curNode->num != k) {
preNode = preNode->next;
curNode = curNode->next;
}
while (list->next!=list) {
for (int i = 0; i < m-1 ; i++) {
if (curNode->next != list) {
preNode = preNode->next;
curNode = curNode->next;
}
else {
preNode = preNode->next->next;
curNode = curNode->next->next;
}
}
printf("%d ", curNode->num);
preNode->next = curNode->next;
curNode = curNode->next;
}
}
int main() {
List* newList = creatList();
int n, k, m;
cout << "请输入n,m,k:";
cin >> n >> k >> m;
if (n < 0 || k < 0 || m<0 || k>n) {
cout << "你输入的数据有误!";
return 0;
}
for (int i = 1; i <= n; i++) {
push_back(newList,i);
}
//print(newList);
j_find(newList, m, k);
}
有头结点与无头结点的不同之处在于以下几个方面:
1.创建链表,就是普通的创建链表,不再给头结点数据:
List* creatList() {
List* newList = (List*)malloc(sizeof(struct List));
newList->next = newList;
return newList;
}
2.在输入数据方面,无头结点的话i是从2开始,有头结点是从1开始
for (int i = 1; i <= n; i++) {
push_back(newList,i);
}
3.在核心函数这里,少了一个参数n,因为while的循环条件可以变为list->next!=list;然后在for循环也就是报数这里,有一种情况是curNode指针可能会移动到头结点这里,但是头结点没有数据,不能删除,所以当我们将要移动时,判断一下,如果curNode将要移动到头结点,那么我们移动两步。
void j_find(List* list,int m,int k) {
//从编号为k的人开始
//数到m的人出列
Node* preNode = list;
Node* curNode = list->next;
while (curNode->num != k) {
preNode = preNode->next;
curNode = curNode->next;
}
while (list->next!=list) {
for (int i = 0; i < m-1 ; i++) { //报数
if (curNode->next != list) {
preNode = preNode->next;
curNode = curNode->next;
}
else {
preNode = preNode->next->next;
curNode = curNode->next->next;
}
}
printf("%d ", curNode->num);
preNode->next = curNode->next;
curNode = curNode->next;
}
}