本文是通過例子學習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遍歷即可,理解起來更加直觀。類似的,通過單向循環鏈表、雙向鏈表實現跟靜態鏈表實現邏輯上差不多。區別在於數據結構不同,但算法一樣。
至於通過遞推公式實現,從“計算機”角度看,一般難以“想到”該方法。
通過該例子,可以學習:
- 指針;
- 函數定義、調用;
- 通過優化求解約瑟夫環,加深問題的理解。
本文從構思到完成,可謂是耗費了大量的心血。
如果您閱讀本文后哪怕有一丟丟收獲,請不要吝嗇你手中關注和點贊的權力,謝謝!
另外,如果需要相關代碼,請留言,可以提供完整源代碼!
