八皇后問題的位運算解決

如題:

這是最后的代碼:
這八層循環(huán)有一種放蕩不羈的美
開始我是想用遞歸做的,畢竟這本來就是遞歸練習(xí)題。
可遞歸太難,不想用,又寫不出來。在我走投無路之際,我突然想到了位運算。
8這個數(shù)字本身就很好玩,正好是一個字節(jié)的位數(shù),也是C語言支持的最小長度的int類型的長度。
于是我的思路開始清晰:如果這八行都各用一個表示,總共是一個
長的
數(shù)組,不就可以表示出棋盤上皇后的位置了嗎?(但這里的
埋下了伏筆)
這樣一來,每行都可以從開始,每次向左移
位,這樣每行都只會有一個皇后。但需要寫八層循環(huán)。當然,我們也可以用遞歸的方法來寫,如果以后我有時間會改一下,畢竟八層循環(huán)確實很丑。
而在從第二層開始的每層循環(huán)開始前,我們都可以先檢查一下這個落子位置是否會和前面的皇后在同一列。而一個一個比較不如把前面的存起來,這就又需要一個數(shù)組。
每次放下皇后,也把這個皇后放進該層的里,下層的
又繼承上層的
,并再加一個該層的皇后。這樣,接下來的循環(huán)中,如果這個數(shù)和上次的
進行按位與操作之后不為0,那就說明不能放在這里,可以
了。
這樣一來,當我們走到循環(huán)底部時,就可以獲得一個每行、每列都有且僅有一個皇后的狀態(tài)??蛇@還不算完,因為皇后還可以斜著吃。那又該怎么辦?
我的思路到這里突然卡了一下,但我瞬間卻又想到了解決方案:移位+按位與!
我們從第一行開始遍歷。顯然,第行應(yīng)該滿足如下條件:
寫到這里的時候,我突然有了新想法:在八層循環(huán)的時候,我們是不是已經(jīng)可以這樣去操作了呢?我們可以再開一個數(shù)組。
數(shù)組的第
個元素是
但這次的數(shù)組并沒有上下繼承關(guān)系,因此每次都是重新計算。
這樣,當我們在遍歷的時候,只需要判斷
,若不為
,那么我們已經(jīng)可以給它判死刑,執(zhí)行
。
無論如何,我們終于得到了想要的:一個符合標準的八皇后棋盤。接下來就是輸出的問題了。
按照我們的思路,皇后是一行一行地遍歷,而遍歷順序是從右往左。題目要求的輸出則是按列遍歷,從上到下。那該怎么辦呢?
其實換個視角,問題就得到解決了。我們可以把這八個數(shù)字豎著放,并且倒著讀。這樣就是按列從上到下了。數(shù)字放好,仍然一行一行輸出,問題就完美解決了。
豎著放很簡單,倒著讀?怎么讀?
我們先看怎么讀。這個問題其實蠻簡單的,同樣是按位與,同樣是循環(huán)移位。我們判斷,當其非零時,得到的
就是我們要的坐標。等等,這樣不就實現(xiàn)倒著讀了嗎?
哈哈,的確是這樣。我們之前從右到左的反常遍歷順序就是移位帶來的,現(xiàn)在我們?nèi)匀挥靡莆缓桶次慌c來讀取,自然就“負負得正”了。
自然,我們也可以從開始向右移位,這樣就是從左到右的遍歷,更符合我們的思維習(xí)慣,不是嗎?

到這里,算法設(shè)計基本告一段落,接下來便是debug時間——我的程序怎么不輸出?
我首先發(fā)現(xiàn)的是運算符優(yōu)先級的隱患。于是我多加了一些括號:這總是有益無害的。然而剛剛我去查證,那些括號實際上并沒有起到什么作用。
上文我寫到,埋下了伏筆,這是因為
實際上是有符號數(shù)。有符號數(shù)!這意味原本的
在這里并不表現(xiàn)為
,而是
.
這導(dǎo)致它在移位的時候會出現(xiàn)各種奇奇怪怪的數(shù)字——這是在我調(diào)試的時候發(fā)現(xiàn)的。我一開始并沒有意識到這是有符號數(shù)導(dǎo)致的。于是我查了很久的移位機制。
但我還是不理解。于是我想用,但考慮之后發(fā)現(xiàn)這是不可行的。因為越界的移位會被保留,而這可能導(dǎo)致本來按位與是
的兩個數(shù)按位與后非
.
這個數(shù)本身就很整,為什么要加一倍呢?
對啊,本身就很整,為什么不行呢?
然后,我突然意識到了。之所以會出現(xiàn)負號,就是因為用了有符號數(shù)!
我迅速把改成了
,運行,輸出了!AC!
我抑制住激動的心情,敲下了這篇專欄。

當然,這里還有一個小問題沒有提到,那就是多層循環(huán)的跳出問題,在判斷是否在同一斜線上時,這里有一個兩層循環(huán)的一層循環(huán)的
,判斷循環(huán)變量不是一個明智的選擇,因為這需要將它的定義置于循環(huán)體外。因此,加一個是否仍符合條件的判斷標志以幫助跳出多層循環(huán)是更為方便而安全的選擇。
當然,一定不要用語句。
另外一個是oj題特有的輸出問題,雖然我們已經(jīng)見過很多次,并且這并非一個大問題,但我仍要提一句。它不允許輸出行的最后有多余的空格,并且這個空格并不能用來刪除。
我們的解決辦法一般是這樣的:將第一個輸出單獨拿出來,不打空格;從第二個開始,每個輸出都是一個空格+所需輸出。

現(xiàn)在是凌晨4:52,這篇專欄寫了大概2h,是時候收工睡覺了。