1. 程式人生 > >迴圈連結串列:約瑟夫問題(非常詳細易理解)

迴圈連結串列:約瑟夫問題(非常詳細易理解)

約瑟夫問題來源

據說著名猶太曆史學家 Josephus有過以下的故事:在羅馬人佔領喬塔帕特後,39個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓到,於是決定了一個自殺方式,41個人排成一個圓圈,由1個人開始報數,每報數到第3人該人就必須自殺,然後再由下一個重新報數,直到所有人都自殺身亡為止。然而Josephus和他的朋友並不想遵從。首先從一個人開始,越過k-2個人(因為第一個人已經被越過),並殺掉第k個人。接著,再越過k-1個人,並殺掉第k個人。這個過程沿著圓圈一直進行,直到最終只剩下一個人留下,這個人就可以繼續活著。問題是,給定了和,一開始要站在什麼地方才能避免被處決?Josephus

要他的朋友先假裝遵從,他將朋友與自己安排在第16個與第31個位置,於是逃過了這場死亡遊戲。

如何使用迴圈連結串列進行模擬

因為約瑟夫問題時一個環狀,又被稱為約瑟夫環,故用迴圈連結串列非常好進行模擬

每數2個人後,將第3個人幹掉,在迴圈連結串列中就是把這個節點del掉,然後指標指向後面一個人,數2個人後,又將第3個人幹掉,一直這樣,直到最後不足3個人,依次自殺後退出迴圈

程式碼中有詳細說明

如果願意,你可以跑一下程式碼。其實就是找規律,方法有很多種,不過迴圈連結串列這種方式很貼近約瑟夫問題也更好理解。

詳細程式碼:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0

typedef struct Node
{
	int data;
	struct Node* next;
} Node;
/*
建立迴圈連結串列
在上篇資料結構—迴圈連結串列的實現部落格中迴圈連結串列中有頭結點,在我們的單鏈表中頭結點是非必須,必須要有頭指標,有頭結點頭指標指向頭結點
無頭結點,頭指標指向頭結點。
在迴圈連結串列中,如果有頭結點 那麼尾結點的指標域指向頭結點形成環,如果沒有頭結點 那麼尾結點的指標域指向第一個結點形成環。
有無頭結點都不重要,重要的是這種思想,那種更加方便解決我們的問題,視情況是否需要頭結點。
num 為約瑟夫環的結點個數
建立約瑟夫環的結點,構成約瑟夫環,使用迴圈連結串列模擬
*/
int Create(Node** joseph,int num)
{
	if (joseph == NULL || num < 1)
	{
		return ERROR;
	}
	//建立頭結點
	Node* head = (Node*)malloc(sizeof(Node));
	head->next = NULL;

	//移動的指標,開始指向頭結點
	Node* p = head;
	//建立約瑟夫結點,採用尾插法,每次建立的結點放到連結串列尾部
	for (size_t i = 1; i <= num; i++)
	{
		//建立新結點,並且賦值
		Node* node = (Node*)malloc(sizeof(Node));
		node->data = i;
		node->next = head;

		//下面2步幹啥?
		//p指向連結串列尾結點,p->next=node 將新建結點新增到連結串列尾部。
		//p = node 移動p指標讓其始終指向連結串列尾結點
		p->next = node;
		p = node;
	}
	//迴圈結束 p 指向最後一個結點,此時約瑟夫環包含頭結點
	//方便解決問題,我們把頭結點去掉,整個環剩下約瑟夫結點。
	p->next = head->next;
	free(head);
	//約瑟夫環,迴圈連結串列中頭指標指向環第一個結點
	*joseph = p->next;
	return OK;
}
int Length(Node* joseph)
{
	if (joseph == NULL)
	{
		return 0;
	}
	Node* target = joseph;//開始target指向第一個結點
	int length = 1;
	for (; target->next!=joseph; target = target->next)
	{
		length++;
	}
	return length;
}
/*
模擬約瑟夫自殺問題
每數到第3個人進行自殺,然後從下一個人重新開始數,數到第3個人進行自殺...
如果是4人個進行自殺
1 2 3 4
死亡順序:->3->2->4->1

n 為 數到第 n個人自殺,約瑟夫 問題 是 每數到第3個人自殺。
這裡可以進行設定,也可以每數到 第4個人進行自殺。
*/
int ShowKill(Node* joseph,int n)
{
	if (joseph == NULL)
	{
		return ERROR;
	}
	int num = Length(joseph);
	//skip為殺第n個人要跨越的人數
	int skip = n-1;
	Node* p = joseph;
	Node* begin = p;//begin指向開始數的第1個人
	while (1)
	{
		begin = p;
		//找到自殺人的前一個位置
		for (size_t i = 0; i < skip-1; i++)
		{
			p = p->next;
		}

		//最後剩2個人時,依次自殺
		//temp == p->next 時剩下最後2個人!
		if (begin == p->next)
		{
			printf("->%d", begin->data);
			printf("->%d", begin->next->data);
			free(begin);
			free(p);
			begin = NULL;
			p = NULL;
			break;
		}


		//迴圈完,p 指向自殺前面一個人
		//p->next 進行自殺,實質上就是將p->next結點從迴圈連結串列中幹掉
		Node* kill = p->next;
		printf("-->%d", kill->data);
		//將kill移除結點
		p->next = kill->next;
		free(kill);
		kill = NULL;

		//p移動到後面3個人的第1個人的位置 
		p = p->next;
		
	}
	printf("\n");
	return OK;
}

int main(int argc, char *argv[])
{
	Node* joseph = NULL;
	int num = 1;

	while (num)
	{
		printf("請輸入約瑟夫環人數(輸入0退出):");
		scanf("%d", &num);
		if (!num)
		{
			break;
		}
		printf("約瑟夫環死亡順序:\n");
		joseph = NULL;
		Create(&joseph,num);
		ShowKill(joseph, 3);
	}
	return 0;
}

驗證結果截圖: