通過例子進階學習C++(六)你真的能寫出約瑟夫環么


本文是通過例子學習C++的第六篇,通過這個例子可以快速入門c++相關的語法。

1.問題描述

n 個人圍坐在一個圓桌周圍,現在從第 s 個人開始報數,數到第 **m **個人,讓他出局;然后從出局的下一個人重新開始報數,數到第 m 個人,再讓他出局......,如此反復直到所有人全部出局為止。

2.問題分析及用數組求解

約瑟夫環是經典的算法問題,如同“一千個讀者就有一千個哈姆雷特”,該問題每個人都有不同的解答。常見的有:數組;單向循環鏈表;靜態鏈表;雙向鏈表;隊列;遞推公式 ......

首先簡化問題,從s=1開始數,通過數組實現需要:

​ 數組 bool a[1000],可能會浪費了大量的存儲空間;

​ 變量 t 從s=1開始數,指示當前數組的位置;

​ 變量 f 記錄出局人數;

​ 變量 s 從1到m;

整個過程一個do-while循環即可實現,但理解起來卻是非常“拗口”。

代碼如下:

#include<iostream>
using namespace std;
int n,m,s,f,t;
bool a[1000];
int main()
{
    cin>>n>>m;		//共n人,從1開始數,數到m出局
    for (int i=1;i<=n;++i){
    	a[i]=false;
	}

	t=0;//從數組a的a[1]開始...記錄數組a的第t個位置 
	f=0;//記錄出局人數 
	s=0;//從1數到m,然后再從1數到m... 
   do
   {
	    ++t;
	 	if (t==n+1) t=1;	//數到最后一個后,將t指向第一個 
	 	if (a[t]==false) ++s;	//第t個位置上有人則報數
	 	if (s==m) 		//當前報的數是m
	 	{
	        s=0;		//計數器清零
	 	    cout<<t<<" ";	//出局人的編號
	 	    a[t]=true;		//設置該位置已出局 
	 	    f++;	 	//出局的人數加一 
	    }
    } while(f!=n);		//所有的人都出局為止
 return 0;
}

程序運行效果如下圖:

3.數組方式求解改進

下面,我們將s調整為鍵盤輸入,即從第s個人開始報數,實現代碼如下:

#include<iostream>
using namespace std;
int n,m,s,f,t;
bool a[1000];
int main()
{
    cin>>n>>t>>m;		//共n人,從t開始數,數到m出局
    cout<<endl;
    for (int i=1;i<=n;++i){
    	a[i]=false;
	}

	t = t -1;
	f=0;//記錄出局人數 
	s=0;//從1數到m,然后再從1數到m... 
   do
   {
	    ++t;
	 	if (t==n+1) t=1;	//數到最后一個后,將t指向第一個 
	 	if (a[t]==false) ++s;	//第t個位置上有人則報數
	 	if (s==m) 		//當前報的數是m
	 	{
	        s=0;		//計數器清零
	 	    cout<<t<<" ";	//出局人的編號
	 	    a[t]=true;		//設置該位置已出局 
	 	    f++;	 	//出局的人數加一 
	    }
    } while(f!=n);		//所有的人都出局為止
 return 0;
}

程序運行效果如下圖:

4.靜態鏈表實現約瑟夫環

靜態鏈表,顧名思義就是用數組模擬鏈表。為了程序的可讀性,特用一個函數表示約瑟夫環求解問題,調用的時候,只需要傳入n,s,m即可。由於數組的長度n是動態生成的,故通過指針來生成數組。

實現代碼如下:

#include<iostream>
using namespace std;
 
//約瑟夫環問題 
void Josephus(int n,int s, int m) 
{
	cout<<n<<" "<<s<<" "<<m<<endl;
	int i,j,k;
	int *next= new int[n];
	//初始化靜態鏈表 
	for(i=0;i<n-1;++i){
		next[i] = i+1;
	} 
	next[n-1] = 0;
	
    //k初始化為s的前一個位置,數組下標從0開始
	if(s==1){
		k = n-1;
	}else{
		k = s-2;
	}
	
	for(i=1;i<=n;++i){
		//找到出局人的前驅 
		for(j=1;j<m;++j){
			k=next[k];
		}	
		cout<<next[k]+1<<" ";//數組下標從0開始,故需要+1 
		//數到m的人出列,刪除該元素
		next[k] = next[next[k]]; 
	}
}
int main(){
	Josephus(9,2,5);
	return 0;
}

程序運行后效果如下:

5.總結

本文中通過數組、靜態鏈表實現了約瑟夫環。數組方式實現,各個元素之間的“線性關系”未在數據結構中體現,需要通過變量t、f、s來分別指示當前數組元素的位置、出局人數、計數1-m直到所有元素都“出局”為止。類似的,通過隊列實現,跟數組實現邏輯上差不多。區別在於數據結構不同,但算法一樣。

靜態鏈表方式實現,各個元素之間的“線性關系”通過next指示了,只需要k遍歷即可,理解起來更加直觀。類似的,通過單向循環鏈表、雙向鏈表實現跟靜態鏈表實現邏輯上差不多。區別在於數據結構不同,但算法一樣。

至於通過遞推公式實現,從“計算機”角度看,一般難以“想到”該方法。

通過該例子,可以學習:

  • 指針;
  • 函數定義、調用;
  • 通過優化求解約瑟夫環,加深問題的理解。

本文從構思到完成,可謂是耗費了大量的心血。

如果您閱讀本文后哪怕有一丟丟收獲,請不要吝嗇你手中關注點贊的權力,謝謝!

另外,如果需要相關代碼,請留言,可以提供完整源代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM