八皇后和約瑟夫問題

今天在寫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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容