今天在寫C語言報告的時候,收獲了兩種算法的實現,分別是八皇后和約瑟夫問題。
八皇后:
總的來說,八皇后問題就是一種backtrace算法的實例,通過不斷的試探,如果遇到不滿足的情況就回退一步,繼續下一次的試探,直到試探完所有可能的結果。
在這個二維棋盤中,我們可以把二維數組壓縮成一維數組。如a[i] = 4意味著棋盤中第i行的皇后放在了第4列。
判斷皇后是否能夠相互攻擊到的方法是a[i] == a[j](列沖突)
abs(i-j) == abs(a[i]-a[j])斜線沖突i.e.斜率為1/-1
void queen() {
int i = 0, j = 0;
while (i < N) {
while (j < N) {
if (is_valid(i, j)) {
queens[i] = j;
j = 0;
break;
}else {
j++;
}
}
if (queens[i] == INITIAL) {
if (i == 0) {
break;
}else {
--i;//trace back
j = queens[i] + 1;
queens[i] = INITIAL;
continue;
}
}
if (i == N-1) {
printf("answer %d: \n", ++i);
lyz_print();
count++;
--i;
j = queens[i] + 1;
queens[i] = INITIAL;
continue;
}
i++;
}
}
核心代碼如上。每次都會試探第i行第j列是否可以放,如果可以就讓queens[i] = j,j歸零以便進行i+1行的試探。如果試探失敗,說明需要回溯,從上一行的queens[i]+1列重新試探,同時讓queens[i]回到初始值,因為初始值表明這一行還未找到合適的列。如果回溯到了第一行了,說明試探已經結束,算法可以退出。如果試探到了最后一行,說明已經找到了某個解,打印后回溯到上一行,以求解下一個。
精妙的位處理方法
核心代碼:
int lim = (1 << 8) - 1;
int ans = 0;
void bit_queen(int row, int ld, int rd) {
if (row == lim) {
ans++;
return;
}
int pos = lim & (~(row | ld | rd));
while (pos) {
int p = pos & (-pos);
pos -= p;
bit_queen(row+p, (ld+p)<<1, (rd+p)>>1);
}
}
傳入參數(0, 0, 0),row中1代表已經放置了皇后的行,ld中1代表禁止放置的位置,rd同前。<</>>操作是因為對角線上的皇后可以互相攻擊。lim & (~(row | ld | rd));
是將放置了皇后的位置和禁位一起排除掉,所剩的1即為可以放置的位置。pos & (-pos);
能取到pos最右邊的1。pos-=p
即為嘗試這個最右邊的1,然后進行遞歸。看是否能遞歸到row為全1,若遞歸到全1,則說明有這個解,若遞歸中途因為pos==0,就會自動退出,進行回溯(while循環),直到試探完所有,輸出ans即為所有解的總和。
約瑟夫問題的數學優化
M個人數到N即出列的約瑟夫問題可以用一個一維數組,每次數到N時,就將這一位設為0,下一次數的時候跳過所有的0即可。也可以將數到的人從數組中刪除,后面的項前移(太消耗時間,可以考慮用鏈表),剩下的人就是數組的第一個元素。
優化方法
剩下n個人時,要去除報數為m-1的人。編號為k=m%n人為0,將剩下的人按如下方式對應:k->0,k+1->1...n-1->n-1-k,0->n-k,1->n-k+1...k-2->n-2.在n-1個人中,某個存活的元素設為Xn-1,則它與Xn的關系為:
Xn
=(Xn-1 + k)%n
=(Xn-1 + m%n)%n
=((Xn-1)%n + (m%n)%n)%n
=((Xn-1)%n + m%n) % n
=(Xn-1+m) % n
BaseCase為X1=0。(我們用它驗證一下m=3時X2=1。如果有兩個人,輪流報數到2,則第二個人存活下來,結論正確).由此構造循環
for(i=2;i<=n;i++)
{
s=(s+m)%i;
}
最后輸出s即可。
順便附上關于取模運算的一些公式(據說在數論和程序設計中都有很大用處):
(a+b)%p = (a%p + b%p) % p
(a-b)%p = (a%p - b%p) % p
(a*b)%p = (a%p * b%p)%p
a^b % p = ((a%p)^b)%p