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