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