一個經典約瑟夫問題的分析與解答

                                            一個經典約瑟夫問題的分析與解答

一、約瑟夫問題的由來

  約瑟夫問題(Josephus)是由古羅馬的史學家約瑟夫(全名Titus Flavius Josephus)提出的。它是一個出現在計算機科學和數學中的經典問題。在計算機編程的算法中,類似問題又稱爲約瑟夫環。(更好的閱讀體驗,移步程序員在旅途
  Josephus是1世紀的一名猶太曆史學家。他在自己的日記中寫道,他和他的40個戰友被羅馬軍隊包圍在洞中。他們討論是自殺還是被俘,最終決定自殺,並以抽籤的方式決定誰殺掉誰。41個人排成一個圓圈,由第1個人開始報數,每報數到第3人該人就必須自殺,然後再由下一個重新報數,直到所有人都自殺身亡爲止。然而Josephus 和他的朋友並不想遵從。首先從一個人開始,越過k-2個人(因爲第一個人已經被越過),並殺掉第k個人。接着,再越過k-1個人,並殺掉第k個人。這個過程沿着圓圈一直進行,直到最終只剩下一個人留下,這個人就可以繼續活着。Josephus將朋友與自己安排在第16個與第31個位置,於是逃過了這場死亡遊戲。

  約瑟夫問題的一般描述形式爲:設有編號爲1,2,……,n的N個人圍成一個圈,從第1個人開始報數,報到M時停止報數,報M的人出圈,再從他的下一個人起重新報數,報到M時停止報數,報M的出圈,……,按照這個規則進行下來,直到所有人全部出圈爲止。當任意給定N和M後,構建相關數據結構與算法求N個人出圈的次序。

二、典型例題

  有n個人圍成一圈,順序排號。從第一個人開始報數(從1到3報數),凡報到3的人退出圈子,問最後留下來的是原來第幾號的那個人。

三、分析解答

  可以採用數組或者循環鏈表的數據結構來解答這道題目。各有優點。數組實現起來簡單,但是邏輯上覆雜。循環鏈接邏輯上簡單,實現起來複雜。下面採用兩種方法解答此題。

       3.1 採用數組存儲數據

  利用數組保存這N個人的序號,設計兩個計數器,一個k作爲報數計數器,一個m作爲退出人數報數器。從第一個人開始計數(數組下標i=0),計數器k到3後清零,數組元素報到最後的時候再從第一個人開始。每退出一個人,相應的數組元素置0,報數計數時只對非0元素計數,當計數器m到n-1是說明只剩下一個人,這時候算法結束,輸出剩下人的編號即可。

#include<stdio.h>
#define NMAX 50

int main(){

	//k 報數計數器
	//m 退出人數計數器
	int i,k,m,n,num[NMAX];

	scanf("%d",&n);

   //給N個人寫上序號
	for(i=0;i<n;i++){
		num[i] = i+1;
	
	}

	i = k = m =0;
    // m = n-1  時說明只剩下一個人
	while(m < n -1){
	
		//當前位置的值不爲0,則計數,爲0代表退出了
		if(num[i] != 0){

			k++;
		}

	//計數器K=3的位置人數退出,退出的位置記爲0。計數器歸0重新計數。退出人數m+1;
		if(k == 3){
		
			num[i] = 0;
			m++;
			k = 0;
		}

		i++;

		if(i == n){
		
			i = 0;
		}
	}

	//輸出最後留下來的那個數
	    i= 0;
		while(num[i] ==0){
		
			i++;
		}

		printf("Left is %d\n", num[i]);
	
		return 0;

}

       3.2 採用循環鏈表存儲數據

  利用循環鏈表存儲N個人的序號。將報到k=3的節點從循環鏈表彙總移除,最後只剩下一個節點循環結束,輸出剩下人的編號即可。(其中報數器K的值可以自定義)

#include<stdio.h>
#include<stdlib.h>

typedef struct Num_node{

	int data;
	struct Num_node  *next;

}Num_Node;

int main(){

    	//n 總人數,m退出的人數,k報數計數器
    	int n,k;
        Num_Node  *head = NULL , *tail =NULL,  *p = NULL;

		int i=0;
        //輸入N,然後初始化鏈表
    	scanf("%d",&n);
		while(i++<n){
		
				//申請節點所佔用的內存空間
				if( (p = (Num_Node *)malloc(sizeof(Num_Node))) == NULL){
				
					printf("memery is not available");
					exit(1);

				}

				p->data = i;
				p->next = NULL;

				//當前申請的是第一個節點
				if(head == NULL){

					head = tail = p;
			
				}else{
					//鏈尾插值
				
					tail->next = p;
					tail = p;
				
				}
				//如果是最後一個元素,讓其指向head:這樣就構成了循環鏈表
				if(i == n){
					tail->next = head;
				}
		}

		//凡是報到k=3的節點就從鏈表中去除。(這個k可以自己定義,只要K<N就行)
		k = 3;
		p = head;

        Num_Node  *pre_p = NULL; //p的前驅指針,刪除p的時候,需要用到這個指針。

		while(p->next != p)  //直到p指向自己,說明只剩下一個元素了。
		{ 
				for(i = 1; i < k; i++)
				{
					pre_p = p;
					p = p->next;
				}
				pre_p->next = p->next;
				free(p);                      // 刪除結點,從內存釋放該結點佔用的內存空間
				p = pre_p->next;      
		}


		printf("Left is %d \n",p->data);

       return 0;
}

三、總結

  約瑟夫問題是一個經典的計算機與數學問題,由來已久,解法也各異。上面兩種方法,分別利用了數組、循環鏈表數據結構來分析這個題目,雖然都可以解決,但是邏輯上的複雜性卻有很大的差異。這其中就能夠看出數據結構在解決問題過程中的重要性,合適的數據結構會大大降近解題的難度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章