映射递归循环-约瑟夫环问题递归解法的一点理解
先說明一點,如果有什么不對的地方,歡迎大家批評指正。
先來看這個類型的某個題目描述:
約瑟夫生者死者游戲
約瑟夫游戲的大意:30個游客同乘一條船,因為嚴(yán)重超載, 加上風(fēng)浪大作,危險萬分。因此船長告訴乘客,只有將全船 一半的旅客投入海中,其余人才能幸免于難。無奈,大家只 得同意這種辦法,并議定30 個人圍成一圈,由第一個人數(shù)起,依次報數(shù),數(shù)到第9人,便把他投入大海中,然后再從 他的下一個人數(shù)起,數(shù)到第9人,再將他投入大海中,如此 循環(huán)地進行,直到剩下 15 個游客為止。問:哪些位置是將 被扔下大海的位置?
不失一般性,將 30 改為一個任意輸入的正整數(shù) n,而報數(shù) 上限(原為9)也為一個任選的正整數(shù)k
第一次看到這個題目,我首先想到的是用 鏈表 或者是 數(shù)組 來模擬,但是當(dāng)我寫完之后,與大神對答案,發(fā)現(xiàn)他的c語言代碼是這么寫的:
int ysfdg ( int sum, intvalue, intn)
{
if ( n == 1 )
return ( sum + value - 1 ) %sum;
else
return ( ysfdg ( sum-1, value,n-1 ) +value ) %sum;
}
7行。。。。。。再見(已經(jīng)不能做朋友了嗎。。。)
sum指的是總?cè)藬?shù),value指的是每次最大報到的數(shù)值,n是第n次,該函數(shù)每次可以求出第n次扔海里的人的編號,( ysfdg指的是約瑟夫遞歸 ) 。
大神飄然而去,而我懵逼了一天,才明白了這段代碼的意思。
舉個栗子:
總?cè)藬?shù)sum為10人,從0開始,每報到4就把一人扔下去(value=4)。
初始情況為:
0 1 2 3 4 5 6 7 8 9
扔下去一個之后:
0 1 2 4 5 6 7 8 9
此時,這些編號已經(jīng)不能組成一個環(huán),但是可以看出4至2之間還是連著的(4 5 6 7 8 9 0 1 2),且下一次報數(shù)將從4開始。但是,之后的報數(shù)將總要考慮原編號3處的空位問題。
如何才能避免已經(jīng)產(chǎn)生的空位對報數(shù)所造成的影響呢?
可以將剩下的9個連續(xù)的數(shù)組成一個新的環(huán)(將2、4連接),這樣報數(shù)的時候就不用在意3的空位了。但是新產(chǎn)生的環(huán)的數(shù)字并非連續(xù)的,報數(shù)時不像之前那樣好處理了(之前沒人被扔海里時下一個報數(shù)的人的編號可以遞推,即(當(dāng)前編號+1)%sum ),無法不借助存儲結(jié)構(gòu)得知下一個應(yīng)該報數(shù)的現(xiàn)存人員編號。
如何使新環(huán)上的編號能夠遞推來簡化我們之后的處理呢?
可以建立一種有確定規(guī)則的映射,要求映射之后的數(shù)字可以遞推,且可以將在新環(huán)中繼續(xù)按原規(guī)則報數(shù)得到的結(jié)果逆推出在舊環(huán)中的對應(yīng)數(shù)字。
方法:將它與 sum-1 個人組成的(0 ~ sum-1)環(huán)一 一映射。
比如之前的栗子,將剩余的 9 人與 9 人環(huán)(0~8)一 一映射。既然 3 被扔到海里之后,報數(shù)要從4開始 (4 其實在數(shù)值上等于最大報數(shù)值),那么就將4映射到0~8的新環(huán)中0的位置,也就是說在新環(huán)中從0開始報數(shù)即可,且新環(huán)中沒有與3對應(yīng)的數(shù)字,因此不必?fù)?dān)心有空位的問題。從舊環(huán)的 4 開始報數(shù)等效于從新環(huán)中的 0 開始報數(shù)。
原始 0 1 2 3 4 5 6 7 8 9
舊環(huán) 0 1 2 4 5 6 7 8 9
新環(huán) 6 7 8 0 1 2 3 4 5
新環(huán)有這么一個優(yōu)勢: 相比于舊環(huán)中2與4之間在數(shù)學(xué)運算上的不連續(xù)性,新環(huán)8和0之間在對9取余的運算中是連續(xù)的,也就是說根本不需要單獨費心設(shè)計用以記錄并避開已產(chǎn)生的空位(如 編號3)的機制 ,新環(huán)的運算不受之前遺留結(jié)果的掣肘。同時只要能將新環(huán)與舊環(huán)的映射關(guān)系逆推出來,就能利用在新環(huán)中報數(shù)的結(jié)果退出之前舊環(huán)中的報數(shù)結(jié)果。
以下是新環(huán)與舊環(huán)中下一個要人扔海里的人位置:
舊環(huán) 0 1 2 4 5 6 7 8 9
^新環(huán) 6 7 8 0 1 2 3 4 5
^如何由新環(huán)中的 3 得到舊環(huán)中的 7 呢。其實可以簡單地逆推回去 : 新環(huán)是由 (舊環(huán)中編號-最大報數(shù)值)%舊總?cè)藬?shù) 得到的,所以逆推時可以由 ( 新環(huán)中的數(shù)字 + 最大報數(shù)值 )% 舊總?cè)藬?shù) 取得。即 old_number = ( new_number + value ) % old_sum.
如 : ( 3 + 4 ) % 10 =7 .
也就是說在,原序列( sum ) 中第二次被扔入海中編號可以由新序列( sum - 1) 第一次扔海里的編號通過特定的逆推運算得出。
而新序列 (sum -1)也是(從0開始)連續(xù)的,它的第二次被扔入海中的編號由可以由(sum - 2)的第一次扔入海里的編號通過特定的逆推運算得出,并且它的第二次被扔入海中的編號又與原序列中的第三次被扔入海里的編號是有對應(yīng)關(guān)系的。
也求是說有以下推出關(guān)系:
(sum-2)環(huán)的第1次出環(huán)編號 >>>(sum-1)環(huán)的第2次出環(huán)編號 >>>(sum)環(huán)的第3次出環(huán)編號
即 在以 k 為出環(huán)報數(shù)值的約瑟夫環(huán)中, m人環(huán)中的第n次出環(huán)編號可以由 (m-1) 人環(huán)中的第 (n-1) 次出環(huán)編號通過特定運算推出。
幸運的是,第一次出環(huán)的編號是可以直接求出的,也就是說,對于任意次出環(huán)的編號,我們可以將之一直降到第一次出環(huán)的編號問題,再一 一 遞推而出。
注意 以下圖示中的環(huán)數(shù)字排列都是順序的,且從編號0開始。
由圖知,10人環(huán)中最后入海的是4號,現(xiàn)由其在1人環(huán)中的對應(yīng)編號0來求解。
通過以上運算,其實我們已經(jīng)求出分別位于9個環(huán)中九個特定次數(shù)的結(jié)果,只不過我們需要的是10人環(huán)的結(jié)果罷了。
這種方法既可以寫成遞歸也可以寫成循環(huán),它對于求特定次數(shù)的出環(huán)編號效率較高。
遞歸就比較好寫了,出口即是當(dāng)次數(shù)為1時。
實際編號是從1開始,而不是0,輸出時要注意轉(zhuǎn)換。
借此就可以看懂那個大神的代碼了。
以下是三種約瑟夫環(huán)解法(數(shù)組,鏈表,遞歸)的c語言代碼,作者水平不高,將就看看吧 ╮(╯_╰)╭:
#include<stdio.h> #include<stdlib.h> #define FAIL 0 #define SUCCESS 1typedef struct gamenode {int number;struct gamenode* next;} node;//讀入初值 int getvalue(int* sum,int* count,int* alive) {printf("請輸入要參與約瑟夫生存游戲的人數(shù):(人數(shù)>0)\n");while(1){scanf("%d",sum);if(*sum>0)break;printf("輸入無效,請重新輸入。\n");};printf("請輸入能報到的最大數(shù)字:(1<=數(shù)字)\n");while(1){scanf("%d",count);if(*count>=1)break;printf("輸入無效,請重新輸入。\n");};printf("請輸入要求最后存活的人數(shù):(0<=人數(shù)<=%d)\n",*sum);while(1){scanf("%d",alive);if(*alive>=0&&*alive<=*sum)break;printf("輸入無效,請重新輸入。\n");};return SUCCESS; }//數(shù)組解法 int ysfsz(int n,int k,int s) {int i=0,*p=NULL,sum=n-k,j=1,o=0,pr=0;if ((p=(int*)malloc(sizeof(int)*n))==NULL){printf("FAIL!\n");return FAIL;}for(i=0;i<n;i++){p[i]=1;}i=0;while(1){if (sum==0)break;if (j==s&&p[i]==1){ p[i]=0;j=1;--sum;}else if(p[i]==1){ j++;}i++;i=i%n; }printf("\n生存下來的人的位置是:");for(i=0;i<n;i++){if(p[i]==1)printf("%d ",i+1);}printf("\n");printf("\n扔海里的位置是:");for(i=0;i<n;i++){if(p[i]==0)printf("%d ",i+1);}printf("\n");free(p);return SUCCESS; }//鏈表解法 int ysflb(int n,int k,int s) {node *h=NULL,*p=NULL,*q=NULL;int i=0;if ((h=(node*)malloc(sizeof(node)*n))==NULL){printf("FAIL!\n");return FAIL;}h->number=1;h->next=h;q=h;for(i=1;i<n;i++){if ((p=(node*)malloc(sizeof(node)*n))==NULL){printf("FAIL!\n");exit (-1);}else{ p->next=NULL;p->number=i+1;q->next=p;q=q->next;}}q->next=h;p=h;i=1;k=n-k;while(k>0){if(i==s){if(p!=p->next){q->next=p->next;printf("%d號已被扔進海里。\n",p->number);free(p);p=q->next;}else{printf("%d號已被扔進海里。\n",p->number);p=q=h=NULL;}--k;}else{p=p->next;q=q->next;}++i;if(i>=s+1)i=1;}if(p!=NULL){printf("幸存下來的人有:");h=p;p=p->next;while(p!=h){q=p->next;printf("%d ",p->number);free(p);p=q;}printf("%d ",h->number);free(h);}elseprintf("全部扔海里去了。\n");return SUCCESS; }//遞歸解法 int ysfdg(int sum,int value,int n) {if(n==1)return (sum+value-1)%sum;elsereturn (ysfdg(sum-1,value,n-1)+value)%sum; }//主函數(shù) int main(void) {int sum=0,count=0,alive=0,i=0;//讀入總?cè)藬?shù),報數(shù)值,存活人數(shù)getvalue(&sum,&count,&alive);/*------------------------------------------------------------*/ //1.約瑟夫環(huán)的數(shù)組解法//ysfsz(sum,alive,count);//2.約瑟夫環(huán)的鏈表解法//ysflb(sum,alive,count);//3. 約瑟夫環(huán)遞歸解法for(i=1;i<=sum-alive;i++)printf("第%2d個被扔海里人的編號:%2d\n",i,ysfdg(sum,count,i)+1);return 0; }總結(jié)
以上是生活随笔為你收集整理的映射递归循环-约瑟夫环问题递归解法的一点理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: qxidc项目/crtsurfdata程
- 下一篇: 转载:二叉树的前中后和层序遍历详细图解(