日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

八皇后问题——列出所有的解,可推至N皇后

發布時間:2024/8/24 综合教程 34 生活家
生活随笔 收集整理的這篇文章主要介紹了 八皇后问题——列出所有的解,可推至N皇后 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
《數據結構》——鄧俊輝版本 讀書筆記

今天學習了回溯法,有兩道習題,一道N皇后,一道迷宮尋徑。今天,先解決N皇后問題。由于筆者

擅長java,所以用java重現了八皇后問題

注意是java實現版本,其實用什么語言,都一樣。


寫在前面的話

如果想看代碼,直接往下拉,注釋寫的很清楚,憑借注釋應

該也能理解八皇后問題。


1. 首先知道什么是八皇后問題,不過,相信看到這里的同學,都應該知道八皇后

問題,簡單概括為:每個皇后的勢力范圍如下圖紅線標注所示,也就是橫縱軸、兩條對角線 。在一個皇后的勢力范圍內,就不能再出現其他皇后了。

2. 這里選用經典的八皇后問題,想讓八個皇后,在棋盤上相安無事。用代碼怎么實現?首先,明白一件事,電腦就是小萌新啊,只會計算最簡單的計算,2+3,它興許都得差分為(1+1)+(1+1+1)來計算,唯一的優勢就是計算的速度比我們快。

3. 剛看到回溯法的時候,想著要用代碼,寫出來。感覺無從下手,以為很難的,以為電腦不會笨的一個一個去嘗試,以為有很神奇的方法存在。學完以后,才發現,很簡單,發現回溯法,也是從窮舉起手的,只不過帶著一定的策略,說白了,優化過的暴力求解。以后,遇到各種聽著很高大上的問題別慌,比如,動態規劃、深度優先搜索。都是窮舉!!只是加了一些策略。I can I believe !你之所能,是因為你想信你能!


解題思路:

 都是文字講解,主要是筆者沒有圖!!!誒,希望你們讀完,能學會八皇后問題。如果文字看不下去。

可以直接去看代碼,注釋寫的很詳細。

一行只能有一個皇后,這個根據游戲規則中的皇后的勢力就可以得知。
首先讓皇后從起點開始擺放。因此,第一個皇后的位置先擺放在(0,0)上,第二個皇后根據游戲規則,只能在第二行尋找合適的位置。在第二個皇后的位置確定以后,再去第三行尋找第三個皇后的位置,以此內推。直到所有的皇后的位置都確定了,問題的一組解,就出來了!但是問題并不是這么簡單。
比如現在有三個皇后A、B、C。A在第一行,B在第二行都找到合適的位置了,在第三行所有的位置都是A或者B皇后的勢力范圍。尷尬了,皇后之間要打架了。怎么辦?聰明的你,可能會想到讓B皇后,重新找一個合適的位置(這個合適的位置,從B當前的下一個位置開始尋找),然后再來為 C選擇合適的位置。如果C還是沒有合適的位置,就再次為B尋找合適的位置,循環往復這個動作。
細心的你,可能會有疑問,每次C皇后,找不到合適的位置,就去讓B重新尋找合適的位置難道B皇后就一直會有新的合適的位置嗎?答案是否定的,當B皇后在它所處的行,再也找不到合適的位置,就說明A皇后的位置,也需要變動了。A皇后的位置變動,跟其他皇后不一樣,因為,當前B、C皇后都因為沒有合適的位置,所有,就沒有擺放在棋盤上。因此,這里棋盤上就一個皇后A,所有它只需要右移一個位置即可。A皇后位置更新以后,就再去為B皇后找合適位置,如果有合適位置,就再去為C皇后尋找合適的位置;如果B皇后沒有合適的位置,就再次右移A皇后,循環上面的過程。
有的同學,可能會有疑問,照你說的,計算機就是通過窮舉解決問題的,上面的解題過程,計算機確實也是笨笨的一個一個位置去尋找的。那么這算法有效率嗎?答案是有效率的。不知你們發現沒有,我們每個為下一個皇后尋找合適的位置的時候,都與之前存在的皇后做了比較。只有,有合適的位置,才會擺放皇后;這里尋找合適的位置,會排除沒有意義的試探,比如,將皇后擺放在之前某個皇后的勢力范圍內。沒有排除沒意義的試探,才是正正的傻傻的窮舉。我們的算法,是帶著策略的窮舉。
上述每次為下一個皇后,尋找合適的位置,就是試探。每次試探不到合理的位置,回頭去改變前一個皇后的位置,就是回溯;排除沒意義的試探,就是剪枝。上訴的解題過程,就是一個回溯法的思路


用代碼求解八皇后問題

    回溯法,如何回溯?需要我們的棧結構,來保存我們每次已經找到合適位置的皇后的列坐標,坐標從0開始。為什么不保存行坐標呢?因為,一行只能有一個皇后,棧中皇后的下標,就是對應皇后的行坐標啦。

首先我們定義一個皇后類

在皇后類中,我們寫了一個方法判斷,兩個皇后之間是否安全
/**
 * 皇后類
 */
class Queen {
    //    棋盤中的皇后下標,從0開始
    int x;
    int y;

    public Queen(int x, int y) {
        this.x = x;

        this.y = y;
    }

    public Queen() {
    }

    /*
        判斷皇后所處位置是否相互安全,接收一個皇后對象
        安全返回真,不安全返回假
         */
    public boolean isSafe(Queen q) {
        return !(this.x == q.x ||
                this.y == q.y ||
                this.x + this.y == q.x + q.y ||
                this.x - this.y == q.x - q.y);
    }

}

測試類中定義一個方法,求新皇后應該放置的合理位置

這個方法中創建一個新皇后對象,與之前已經存在的皇后作比較。比較函數則是皇后類定義的方法。

找到合適位置,就返回合適的位置的列坐標;返回-1,代表沒有合適位置。
      /**
     * 為皇后尋找合法位置
     */

    public int setY(int N, ArrayList<Integer> list, int y) {

        Queen q;
        Queen q_exist;
//       做個標記,
        boolean flag ;
//        遍歷一行的所有位置
        for (int z = y; z < N; z++) {
//          為每個格子創建一個新的皇后
            q = new Queen(list.size(), z);
//            起始標記為真
            flag = true ;
            for (int x = 0; x < list.size(); x++) {
//          創建之前存在的皇后
                q_exist = new Queen(x, list.get(x));
//           安全 就返回真,循環作比較
                if (!(q_exist.isSafe(q))) {
//          只要和之前存在的的任何一個皇后沖突,
//          并且說明當前皇后不是真的,將標記改為假
//          并跳出當前跳出循環,
                    flag = false;
                    break;
                }
            }
//          如果標記還為真,說明皇后是對滴,并返回新皇后的列坐標,結束函數
            if (flag) {
                list.add(q.y);
                return q.y;
            }
        }
//          函數沒有提前結束,說明沒有合適的位置,返回-1;
        return -1;
    }

求出所有組解

跟著注釋看,相信大家都能學會
/**
     * 求解N皇后的問題,并給出所有的解,找到一組解,就讓最后的皇后換位置,直達第一個皇后的角標越界
     * 筆者認為 next_y 變量的動態賦值的 需要很好的理解
     * @param N 表示一共有多少皇后,也是棋盤的規模  N x N
     */
    private void placeQueen(int N) {
//        表示一共有多少種解法
        int all = 1;
//        代表皇后的y坐標
        int y = 0;
//        在當前行,下一個位置開始重新尋找合適的位置
        int next_y = 0;
//        建立一個棧,用于回溯
//        棧中存放的是皇后的列坐標,也就是y坐標
        ArrayList<Integer> list = new ArrayList();
//         定義一個皇后
        Queen queen;
//        開始計算每種情況
        while (true) {
//                先判斷之前有沒有皇后存在
            if (list.size() == 0) {
//                創建一個皇后對象,從0開始擺放
                queen = new Queen(0, y);
//                將皇后的y坐標記錄下來,而x坐標就是棧的長度-1
                list.add(queen.y);
//              下一次,棧再次空了,說明第一行的皇后需要右移
                y++;
            } else {
//                 如果已經有皇后存在,既需要為當前皇后尋找合適的位置了
//                就是看setY()方法的返回值


//                在尋找當前皇后合適位置之前,我們先判斷是否求出一組解
//                如果,求出一組解,我們打印當前的解
                if (list.size() == N) {
                    System.out.print("第" + all + "組解:  ");
                    all++;
                    for (int num : list) {
                        System.out.print(num + " ");
                    }
                    System.out.println();

//                    當前一組解,已經打印完畢
//                    需要重新尋找新的解
//                    我們這里讓棧頂的皇后,重新尋找位置
//                    改變next_y的值就好了。
                    next_y = list.get(list.size() - 1) + 1;
//                    記得彈出棧頂的皇后
                    list.remove(list.size() - 1);
//                    }
                } else {
//  *****************************************************
//                就是這里 我上午寫了一個比較臃腫的方法
//                我下午回來,試著重構,結果改了3個小時,才改對了!
//                簡直日了狗了,澡都沒趕上洗,午覺也沒睡成


//                    setY()方法中,尋找到合適的位置,就會自動添加到棧中
//                    因此,這里只關注返回-1的情況
//                    返回-1 ,表示需要回溯前一個皇后了
                    if (-1 == setY(N, list, next_y)) {

//                    記錄下,先開始的位置
//                     這個next_y 很重要的
//                     如果需要在當前行 重新尋找合適的位置
//                      就需要從當前位置的下一個位置開始遍歷尋找
//                     因此next_y的值,為當前位置+1
                        next_y = list.get(list.size() - 1) + 1;
                        list.remove(list.size() - 1);

//                    這里針對的是求出所有解的情況下,退出
//                        當求出最后一組解的時候,再次將棧頂元素彈出,重新回溯的話
//                        必然會回溯到最初的皇后,即第一個皇后,彈出第一個皇后的時候
//                        棧為空。這里就需要作出判斷了
//                        第一個皇后的下一個位置,是否超出了N的限制
                        if (list.size() == 0) {
//                            判斷是右移還是退出
                            if (next_y < N) {
                                list.add(next_y);
                                next_y = 0;
                            } else {
                                break;
                            }

                        }


                    } else {
//                        如果setY()沒有返回-1,說明找到了合適的位置
//                        就需要為下一個皇后尋找合適的位置了
//                        在新的一行開始尋找,必然需要從0開始尋找位置
                        next_y = 0;
//  ****************************************************

                }
                }
            }
        }
    }

測試方法

不了解Junit測試框架的同學,這里將 placeQueen(6);寫到main函數里面即可。
    @Test
    public void test() {

        placeQueen(6);

    }

運行結果

六皇后問題一共有四組解

得出的解,只是皇后從0開始的 “列坐標” ,它們的行坐標,是0到N-1順序排列;
比如 : 1 3 5 0 2 4,就是下面的情況
列  行 
1   0
3   1
5   2
0   3
2   4

4 5

下面運算一下經典的八皇后,看看一個有多少組解

在圖中可以看出,一共有92組解。并且可以看到我們計算所有的解,只花費了0.026s。還是很快的。


八皇后問題,已經被我們解決了,用的就是回溯法,可能你

已經對回溯法,有了一定的了解,就是試探-剪枝-回溯

希望你下次遇到什么新的算法策略。不要緊張,它們只是窮

舉,帶有策略的窮舉,策略還需要你來寫呢!


回溯法還有一個好玩的題,迷宮尋徑。過幾天,我在寫一個關于迷宮尋徑的博

客,題目有千千萬,但是思想是一樣的。希望大家和我,都能在解題過程中,對

回溯法有一個很好的掌握,下一次遇到問題的時候,能熟練使用回溯法解決它。

總結

以上是生活随笔為你收集整理的八皇后问题——列出所有的解,可推至N皇后的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。