数据结构与算法 实验题1 Josephus环问题

1.问题描述

设编号为1,2, ……,n的n 个人按顺时针方向围坐一圈,约定编号为k (1≤k≤n) 的人按顺时针方向从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。试设计算法求出n个人的出列顺序。

2.基本要求

程序运行时,首先要求用户指定人数n,第一个开始报数的人的编号k及报数上限值m。然后,按照出列的顺序打印出相应的编号序列。

3.提示与分析

1)由于报到m的人出列的动作对应着数据元素的删除操作,而且这种删除操作比较频繁,因此单向循环链表适于作为存储结构来模拟此过程。而且,为了保证程序指针每一次都指向一个具体的数据元素结点,应使用不带头结点循环链表作为存储结构。相应地,需要注意空表和非空表的区别。


2)思路:创建一个含有n个结点的单循环链表,然后由第一个结点起从1开始计数(此时假设k-1),计到m时,对应结点从链表中删除,接下来从被删除结点的下一个结点重新开始从1开始计数,计到m时,从链表中删除对应结点,如此循环, 直至最后一个结点从链表中删除,算法结束。


4.选作内容

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;
	}
}

你可能感兴趣的:(数据结构)