詳解約瑟夫環(huán)問題及其相關(guān)的C語(yǔ)言算法實(shí)現(xiàn)
約瑟夫環(huán)問題
N個(gè)人圍成一圈順序編號(hào),從1號(hào)開始按1、2、3......順序報(bào)數(shù),報(bào)p者退出圈外,其余的人再?gòu)?、2、3開始報(bào)數(shù),報(bào)p的人再退出圈外,以此類推。
請(qǐng)按退出順序輸出每個(gè)退出人的原序號(hào)
算法思想
用數(shù)學(xué)歸納法遞推。
無(wú)論是用鏈表實(shí)現(xiàn)還是用數(shù)組實(shí)現(xiàn)都有一個(gè)共同點(diǎn):要模擬整個(gè)游戲過程,不僅程序?qū)懫饋肀容^煩,而且時(shí)間復(fù)雜度高達(dá)O(nm),若nm非常大,無(wú)法在短時(shí)間內(nèi)計(jì)算出結(jié)果。我們注意到原問題僅僅是要求出最后的勝利者的序號(hào),而不是要讀者模擬整個(gè)過程。因此如果要追求效率,就要打破常規(guī),實(shí)施一點(diǎn)數(shù)學(xué)策略。
為了討論方便,先把問題稍微改變一下,并不影響原意:
問題描述:n個(gè)人(編號(hào)0~(n-1)),從0開始報(bào)數(shù),報(bào)到(m-1)的退出,剩下的人繼續(xù)從0開始報(bào)數(shù)。求勝利者的編號(hào)。
我們知道第一個(gè)人(編號(hào)一定是m%n-1) 出列之后,剩下的n-1個(gè)人組成了一個(gè)新的約瑟夫環(huán)(以編號(hào)為k=m%n的人開始):
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2并且從k開始報(bào)0。
現(xiàn)在我們把他們的編號(hào)做一下轉(zhuǎn)換:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
k-1 --> n-1
變換后就完完全全成為了(n-1)個(gè)人報(bào)數(shù)的子問題,假如我們知道這個(gè)子問題的解:例如x是最終的勝利者,那么根據(jù)上面這個(gè)表把這個(gè)x變回去不剛好就是n個(gè)人情況的解嗎???!變回去的公式很簡(jiǎn)單,相信大家都可以推出來:x'=(x+k)%n
如何知道(n-1)個(gè)人報(bào)數(shù)的問題的解?對(duì),只要知道(n-2)個(gè)人的解就行了。(n-2)個(gè)人的解呢?當(dāng)然是先求(n-3)的情況——這顯然就是一個(gè)倒推問題!好了,思路出來了,下面寫遞推公式:
令f[i]表示i個(gè)人玩游戲報(bào)m退出最后勝利者的編號(hào),最后的結(jié)果自然是f[n]
遞推公式
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
實(shí)現(xiàn)方法
一、循環(huán)鏈表
建立一個(gè)有N個(gè)元素的循環(huán)鏈表,然后從鏈表頭開始遍歷并計(jì)數(shù),如果基數(shù)i == m,則踢出該元素,繼續(xù)循環(huán),直到當(dāng)前元素與下一個(gè)元素相同時(shí)退出循環(huán)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct lnode
{
int pos;
struct lnode *next;
} lnode;
/**
* 構(gòu)建循環(huán)鏈表&&循環(huán)遍歷
*/
void create_ring(lnode **root, int loc, int n)
{
lnode *pre, *current, *new;
current = *root;
pre = NULL;
while (current != NULL) {
pre = current;
current = current->next;
}
new = (lnode *)malloc(sizeof(lnode));
new->pos = loc;
new->next = current;
if (pre == NULL) {
*root = new;
} else {
pre->next = new;
}
// 循環(huán)鏈表
if (loc == n) {
new->next = *root;
}
}
/**
* 約瑟夫環(huán)
*/
void kickoff_ring(lnode *head, int p)
{
int i;
lnode *pre, *pcur;
pre = pcur = head;
while (pcur->next != pcur) {
for (i = 1; i < p; i ++) {
pre = pcur;
pcur = pcur->next;
}
printf("%d ", pcur->pos);
pre->next = pcur->next;
free(pcur);
pcur = pre->next;
}
printf("%d\n", pcur->pos);
free(pcur);
}
void print_ring(lnode *head)
{
lnode *cur;
cur = head;
while (cur->next != head) {
printf("%d ", cur->pos);
cur = cur->next;
}
printf("%d\n", cur->pos);
}
int main()
{
int i, p, n;
lnode *head;
while (scanf("%d %d", &n, &p) != EOF) {
// 構(gòu)建循環(huán)鏈表
for (i = 1, head = NULL; i <= n; i ++)
create_ring(&head, i, n);
// 約瑟夫環(huán)
if (p != 1)
kickoff_ring(head, p);
else
print_ring(head);
}
return 0;
}
/**************************************************************
Problem: 1188
User: wangzhengyi
Language: C
Result: Accepted
Time:110 ms
Memory:912 kb
****************************************************************/
二、數(shù)組模擬
思想跟循環(huán)鏈表類似,少了構(gòu)建循環(huán)鏈表的過程
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i, index, p, n, remain, delete[3001], flag[3001] = {0};
while (scanf("%d %d", &n, &p) != EOF) {
remain = n;
index = 0;
while (remain >= 1) {
for (i = 0; i < n; i ++) {
if (flag[i] == 0) {
// 報(bào)數(shù)
index ++;
// 報(bào)p者退出圈外
if (index == p) {
// 退出圈外
flag[i] = 1;
// 重新報(bào)數(shù)
index = 0;
delete[remain - 1] = i + 1;
remain --;
}
}
}
}
// 輸出每個(gè)退出人的序號(hào)
for (i = n - 1; i >= 0; i --) {
if (i == 0) {
printf("%d\n", delete[i]);
} else {
printf("%d ", delete[i]);
}
}
}
return 0;
}
三、數(shù)學(xué)推導(dǎo)
#include <stdio.h>
int main(void)
{
int i, n, m, last;
while (scanf("%d", &n) != EOF && n != 0) {
// 接收?qǐng)?bào)數(shù)
scanf("%d", &m);
// 約瑟夫環(huán)問題
for (i = 2, last = 0; i <= n; i ++) {
last = (last + m) % i;
}
printf("%d\n", last + 1);
}
return 0;
}
相關(guān)文章
OpenCV邊緣提取算法流程的實(shí)現(xiàn)(附DEMO)
本文主要介紹了OpenCV邊緣提取算法流程的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
C語(yǔ)言線性表順序存儲(chǔ)結(jié)構(gòu)實(shí)例詳解
這篇文章主要介紹了C語(yǔ)言線性表順序存儲(chǔ)結(jié)構(gòu)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06
C++11 線程同步接口std::condition_variable和std::future的簡(jiǎn)單使用示例詳
本文介紹了std::condition_variable和std::future在C++中的應(yīng)用,用于線程間的同步和異步執(zhí)行,通過示例代碼,展示了如何使用std::condition_variable的wait和notify接口進(jìn)行線程間同步2024-09-09
詳解VS2019 dumpbin查看DLL的導(dǎo)出函數(shù)
這篇文章主要介紹了詳解VS2019 dumpbin查看DLL的導(dǎo)出函數(shù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
C語(yǔ)言編程中分配內(nèi)存空間的相關(guān)函數(shù)
這篇文章主要介紹了C語(yǔ)言編程中分配內(nèi)存空間的相關(guān)函數(shù),分別是malloc()函數(shù)和calloc()函數(shù),需要的朋友可以參考下2015-08-08

