约瑟夫问题的学习(基于循环链表)以及基于循环数组
這是17世紀(jì)法國數(shù)學(xué)家加斯帕在《數(shù)目中的游戲問題》講的一個(gè)問題:15個(gè)教徒和15個(gè)非教徒在海上遇險(xiǎn),必須將一般的人投入海中,其他的人才能幸免于難。與實(shí)現(xiàn)各一個(gè)辦法:30個(gè)人圍成一個(gè)圈,從第個(gè)人開始報(bào)數(shù),每報(bào)到第9個(gè)人就將他扔入海中,如此循環(huán)進(jìn)行,直至僅余15個(gè)人為止。問怎么樣排法,才能使每次投入大海的都是非教徒。
可參照以下代碼:
#include<stdio.h>
typedef struct node_s
{
int next;//指向下一個(gè)人的指針(下一個(gè)人的數(shù)組下標(biāo))
int no_out;//是否被扔下海的標(biāo)志,1:沒有被扔下海2:被扔下海。
}node_t;
node_t link[31];//30個(gè)人,0號元素沒有使用。
int main()
{
int i,j,k;
printf(“The original cir is (+:pagandom,@:christian):\n”);
for(i=1;i<=30;i++)//初始化數(shù)組
{
link[i].next=i+1;//指針指向下一個(gè)人(下一個(gè)數(shù)組下標(biāo))
link[i].no_out=1;//標(biāo)志置為1,表示都在船上。
}
link[30].next=1;//第30個(gè)人的指針指向第一個(gè)人以構(gòu)成環(huán)。
j=30;//j指向已經(jīng)處理完畢的數(shù)組元素,從link[j]指向的人開始計(jì)數(shù)
for(i=0;i<15;i++)//已扔下海的人數(shù)計(jì)數(shù)器
{
for(k=0;?//決定哪個(gè)人被扔下海的計(jì)數(shù)器
if(k<15)//計(jì)數(shù)不到15則繼續(xù)計(jì)數(shù)
{
j=link[j].next;//修改指針,指向下一個(gè)人
k+=link[j].no_out;//進(jìn)行計(jì)數(shù),因已扔下海的人標(biāo)志為0
}//這樣標(biāo)志不會(huì)影響計(jì)數(shù)。
else
break;//計(jì)數(shù)到15就停止計(jì)數(shù)
link[j].no_out=0;//標(biāo)志置0,表示該人扔下海
}
for(i=1;i<=30;i++)//輸出結(jié)果。
{
printf("%d:%c\n",i,link[i].no_out?’+’:’@’);
}//’+’:表示被扔下海的非教徒,‘@’:表示留在船上的教徒
return 0;
}
#if 0
下面是轉(zhuǎn)自別人的博客
約瑟夫環(huán)問題,這是一個(gè)很經(jīng)典算法,處理的關(guān)鍵是:偽鏈表
問題描述:N個(gè)人圍成一圈,從第一個(gè)人開始報(bào)數(shù),報(bào)到m的人出圈,剩下的人繼續(xù)從1開始報(bào)數(shù),報(bào)到m的人出圈;如此往復(fù),直到所有人出圈。(模擬此過程,輸出出圈的人的序號)
在數(shù)據(jù)結(jié)構(gòu)與算法書上,這個(gè)是用鏈表解決的。我感覺鏈表使用起來很麻煩,并且這個(gè)用鏈表處理起來也不是最佳的。
我畫了一個(gè)圖用來理解:
有如下問題需要首先考慮:
1、“圈”怎樣形成?
以上圖為例:下標(biāo)從0開始,當(dāng)下標(biāo)為11的時(shí)候,在加1,就應(yīng)該回到0。index = (index+1) % count;2、怎樣處理?數(shù)組or鏈表or其他方法?
鏈表使用起來很笨重,我們有循環(huán)數(shù)組的方法。解法一程序分析:
循環(huán)的開始和結(jié)束:循環(huán)的結(jié)束取決于圈內(nèi)是否還有“人”,可以用一個(gè)變量alive表示初始人數(shù),每一次出圈,alive-1.判斷alive是否非0即可。
while(alive > 0)每一次循環(huán),就是“過”一個(gè)人,但是,這個(gè)人有兩種不同的狀態(tài):在圈內(nèi)和不在圈內(nèi);在圈內(nèi)就報(bào)數(shù),number+1,不在圈內(nèi)就不參與報(bào)數(shù),number不變。
假設(shè)有N個(gè)int元素的數(shù)組,每一個(gè)int元素表示一個(gè)“人”;并且,取值為0和1, 1表示在圈內(nèi),0表示不在圈內(nèi),所以,如果這個(gè)人在圈內(nèi),number+1;如果這個(gè)人不在圈內(nèi),number+0。那么,在報(bào)數(shù)的時(shí)候,不需要考慮這個(gè)人在不在圈內(nèi)就行(每一個(gè)人都需要加1或加0,所以,可以在這塊優(yōu)化一下程序)。
void joseph(int count, int doom) {
int alive = count; //幸存人數(shù)
int number = 0; //計(jì)數(shù),當(dāng)number==doom時(shí),淘汰這個(gè)人
int index = 0; //下標(biāo),為總?cè)藬?shù)-1
int *circle = NULL; //根據(jù)需求設(shè)為循環(huán)數(shù)組,存儲每個(gè)人
}
解法二程序分析:
解法二在解法一的基礎(chǔ)上進(jìn)行了優(yōu)化,對出圈的人的節(jié)點(diǎn)進(jìn)行刪除,可以減少時(shí)間復(fù)雜度。
假設(shè),要?jiǎng)h除的節(jié)點(diǎn)下標(biāo)為curIndex,其前驅(qū)節(jié)點(diǎn)下標(biāo)為preIndex;
circle[preIndex] = circle[curIndex];假設(shè)要?jiǎng)h除的節(jié)點(diǎn)下標(biāo)curIndex=3,那么circle[2] = circle[3] ,circle[2] = 4,circle[2]之前等于3,現(xiàn)在等于4。即下標(biāo)為3的第四個(gè)人已經(jīng)被刪除了。怎樣遍歷?
preIndex = curIndex;curIndex = circle[curIndex];但是,在出圈的時(shí)候,curIndex 和preIndex 的變化有別于上面的操作:
出圈時(shí),curIndex 需要后移,preIndex 應(yīng)該不動(dòng)!
每次循環(huán),直接報(bào)數(shù),因?yàn)楸粍h除的人(例如上面的第四個(gè)人)不可能進(jìn)行報(bào)數(shù)了。
void joseph(int count, int doom) {
int alive = count; // 幸存人數(shù)
int number = 0; // 報(bào)數(shù)的數(shù)
int curIndex = 0; // 當(dāng)前人下標(biāo)
int preIndex = count - 1; // 前一個(gè)人下標(biāo)
int *circle = NULL;
int index;
}
解法三程序分析:
解法三里沒有進(jìn)行number報(bào)數(shù),而是直接計(jì)算出需要移動(dòng)的人數(shù),然后定位到要出圈的人。
num = doom % alive - 1;
for(index = 0; index < (num == -1 ? alive - 1 : num); index++) {
preIndex = curIndex;
curIndex = circle[curIndex];
}
void joseph(int count, int doom) {
int alive = count; // 幸存人數(shù)
int curIndex = 0; // 當(dāng)前人下標(biāo)
int preIndex = count - 1; // 前一個(gè)人下標(biāo)
int *circle = NULL;
int index;
#endif
總結(jié)
以上是生活随笔為你收集整理的约瑟夫问题的学习(基于循环链表)以及基于循环数组的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gets与fgets,puts与fput
- 下一篇: enum的介绍以及和#define的区别