Programming Assignment 4 Checklist: 8 Puzzle

典型的8 puzzle如下:

image

算法設計參照A搜索算法筒扒,即使不了解A搜索算法忘苛,題目也已經(jīng)將解法解釋的很具體了。

Best-first search:設計參照A* 搜索算法缔莲。定義一個 search node類哥纫,包含board,從初始狀態(tài)到達當前board狀態(tài)的移動權重moves痴奏,和previous search node蛀骇。

  1. 插入初始的search node,其board設為初始board读拆,moves設為0擅憔,previous search node設置為0
  2. 將初始化的search node置于MinPQ類型的優(yōu)先級隊列中
  3. 刪除優(yōu)先級隊列中的min節(jié)點,再將該節(jié)點的鄰居節(jié)點放入優(yōu)先級隊列中檐晕。
  4. 重復2和3操作直至從優(yōu)先級隊列中刪除的min節(jié)點是目標board

A* 搜索算法的優(yōu)先級判定依據(jù)是f(n) = g(n) + h(n)暑诸,g(n)是從初始節(jié)點到達當前節(jié)點的代價,h(n)是當前節(jié)點到目標節(jié)點的預估代價辟灰。

在本題中search node中的moves就是g(n)个榕,而關于h(n)題目給出了兩種候選:

Hamming priority function: 處于錯誤位置的block的個數(shù)(空白處不算block)

Manhatten priority function: 處于錯誤位置的block距離其各自目標位置的橫向和縱向距離之和

h(n)采用這兩者均可,根據(jù)題目中的圖示芥喇,顯然發(fā)現(xiàn)題目推薦采用manhatten方法西采。

至此優(yōu)先級隊列中的優(yōu)先級判斷依據(jù)就是當前search node的moves+manhatten value

A critical optimization: 上述Best-first search中可能會存在剛出隊列的節(jié)點又被當成其鄰居節(jié)點的鄰居而被放回優(yōu)先級隊列的情況,這種情況會造成很大的性能損失继控。為了阻止這種情況的發(fā)生械馆,可以在Best-first search的第3步“將該節(jié)點的鄰居節(jié)點放入優(yōu)先級隊列”時比較下這個鄰居節(jié)點的board是否與本節(jié)點的board相同。

image

例如此時{{8武通,1霹崎,3},{4冶忱,0仿畸,2},{7朗和,6,5}}就不應該放入優(yōu)先級隊列中簿晓。

A second optimization: 建議在search node的構造函數(shù)中計算其manhattan值眶拉,也就是在search node的構造函數(shù)中確定其優(yōu)先級。

Detecting unsolvable puzzles:如果一個board是不可解的憔儿,那么隨便在該board中選擇一對block互換位置忆植,就能將其變?yōu)榭山獾摹榇瞬捎猛瑫r對board和其互換了一對block的twindboard進行求解,如果board先實現(xiàn)目標解朝刊,那么其就是可解的耀里,相反,如果twinboard先實現(xiàn)目標節(jié)拾氓,那么該board就不可解冯挎。

import java.util.ArrayList;
 
public class Board {
    private final int [][]blocks;
    private static final int BLANK = 0;
    private final int N;
    public Board(int[][] blocks)           // 創(chuàng)建n*n的棋盤
    {
        if (blocks == null) 
            throw new java.lang.IllegalArgumentException("the blocks is null");
        
        N = blocks.length;
        
        this.blocks = new int[N][N];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                this.blocks[i][j] = blocks[i][j];
            }
        }
    }
    
    public int dimension()                 // 棋盤維度n
    {
        return N;  // 返回列數(shù)
    }
    
    private int getIndex(int i, int j) // 二維變一維坐標
    {
        return i * N + j + 1;
    }
   
    private int getRow(int value) // 在目標結果中,值所對應的行數(shù)
    {
        return (value - 1) / N;
    }
     private int getCol(int value) // 在目標結果中咙鞍,值所對應的列數(shù)
    {
        return (value -1) % N;
    }
    public int hamming()                   // hamming優(yōu)先級=錯的位置個數(shù)+已移動次數(shù)
    {
        int wrongNum = 0;
        for (int i = 0; i < N; i++) 
            for (int j = 0; j < N; j++) 
                if (blocks[i][j] != BLANK && blocks[i][j] != getIndex(i, j)) // blocks[i][j]位置上的元素放錯
                    wrongNum++;
        return wrongNum;
    }
    
    public int manhattan()                 // manhattan優(yōu)先級=距離目標距離+已移動次數(shù)
    {
        int wrongNum = 0;
        for (int i = 0; i < N; i++) 
        {
            for (int j = 0; j < N; j++) 
                if (blocks[i][j] != BLANK && blocks[i][j] != getIndex(i, j))  // blocks[i][j]位置上的元素放錯
                {
                    int righti = getRow(blocks[i][j]); // blocks[i][j]位置上的元素正確的i坐標
                    int rightj = getCol(blocks[i][j]); // blocks[i][j]位置上的元素正確的j坐標
                    wrongNum = wrongNum + Math.abs(righti - i) + Math.abs(rightj -j);
                }
        }
        return wrongNum;
    }
    
    public boolean isGoal()                // 棋盤是否為目標位置
    {
        for (int i = 0; i < N; i++) 
            for (int j = 0; j < N; j++) 
                if (blocks[i][j] != BLANK && blocks[i][j] != getIndex(i, j))  // blocks[i][j]位置上的元素放錯
                    return false;
        return true;
    }
    
    private int[][] copy() // 拷貝棋盤元素
    {
        int [][]newblocks = new int[N][N];   
        for (int i1 = 0; i1 < N; i1++)
            for (int j1 = 0; j1 < N; j1++) 
                newblocks[i1][j1] = this.blocks[i1][j1];
        return newblocks;
    }
    
    private Board swap(int i1, int j1, int i2, int j2) // 交換兩個棋盤元素位置
    {
        int [][]newblocks = copy();
        int temp = newblocks[i1][j1];
        newblocks[i1][j1] = newblocks[i2][j2];
        newblocks[i2][j2] = temp;
        return new Board(newblocks);
    }
    
    public Board twin()                    // 通過交換任何一對塊而獲得的板房官。
    {
        int i1 = 0, j1 = 0, i2 = 1, j2 = 1;
        if (blocks[i1][j1] == BLANK) 
        {
            i1 = 1;
            j1 = 0;
        }
        if (blocks[i2][j2] == BLANK)
        {
            i2 = 1;
            j2 = 0;
        }
        
        Board newBoard = swap(i1, j1, i2, j2);
        
        return newBoard;
    }
    public boolean equals(Object y)        // 判斷兩個棋盤是否相等
    {
        if (y == null)  
            return false;
        if (y == this) 
            return true;
        
        if (y.getClass().isInstance(this)) // y和this屬于同一類型  
        {
            Board boardY = (Board) y;
            if (boardY.N != this.N)
                return false;
            for (int i = 0; i < N; i++) 
                for (int j = 0; j < N; j++) 
                    if (this.blocks[i][j] != boardY.blocks[i][j]) 
                        return false;
        }
        else // y和this不屬于同一類型 
        {
            return false;
        }
        
        return true;
    }
    public Iterable<Board> neighbors()     // 所有的鄰居棋盤
    {
        ArrayList<Board> boards = new ArrayList<>();
        
        for (int i = 0; i < N; i++) 
        {
            for (int j = 0; j < N; j++)
            {
                if (blocks[i][j] == BLANK)  // 找到空格位置 
                {
                    // 上
                    if (i > 0) 
                    {
                        Board upBoard = swap(i, j, i-1, j);
                        boards.add(upBoard);
                    }
                    
                    // 下
                    if (i < N-1) 
                    {
                        Board lowBoard = swap(i, j, i+1, j);
                        boards.add(lowBoard);
                    }
                    
                    // 左
                    if (j > 0) 
                    {
                        Board leftBoard = swap(i, j, i, j-1);
                        boards.add(leftBoard);
                    }
                    
                    // 右
                    if (j < N-1) 
                    {
                        Board rightBoard = swap(i, j, i, j+1);
                        boards.add(rightBoard);
                    }
                }
                
            }
            
        }
        return boards;
    }
    public String toString()               // 此板的字符串表示形式(以下面指定的輸出格式)
    {
        StringBuilder sBuilder = new StringBuilder();
        sBuilder.append(N + "\n");
        for (int i = 0; i < N; i++) 
        {
            for (int j = 0; j < N; j++) 
            {
                sBuilder.append(blocks[i][j] +"\t");
            }
            sBuilder.append("\n");
        }
        String string = sBuilder.toString();
        return string;
    }
    public static void main(String[] args) // unit tests (not graded)
    {
        int [][]blocks = new int[3][3];
        
        blocks[0][0] = 8;
        blocks[0][1] = 1;
        blocks[0][2] = 3;
        
        blocks[1][0] = 4;
        blocks[1][1] = 0;
        blocks[1][2] = 2;
        
        blocks[2][0] = 7;
        blocks[2][1] = 6;
        blocks[2][2] = 5;
        Board board = new Board(blocks);
        
        System.out.println(board.manhattan());
        System.out.println(board.toString());
        for (Board it : board.neighbors()) {
            System.out.println(it.toString());
        }
        
        System.out.println(board.twin().toString());
        
    }
}


import edu.princeton.cs.algs4.MinPQ;
import edu.princeton.cs.algs4.Stack;
 
public class Solver {
    private SearchNode currentNode;
    private SearchNode currentTwinNode;
    private Stack<Board> stackBoard;
    
    private class SearchNode implements Comparable<SearchNode>
    {
        private final Board board;   // 當前棋盤情況
        private final int moves;    // 當前棋盤移動步數(shù)
        private SearchNode preSearcchNode = null;  // 當前棋盤前身
        private final int priority;   // 當前狀態(tài)優(yōu)先級
        
        public SearchNode(Board board, SearchNode pNode) 
        {
            this.board = board;
            this.preSearcchNode = pNode;
            if (preSearcchNode == null) 
                moves = 0;
            else
                moves = preSearcchNode.getMoves() + 1;
            priority = board.manhattan() + getMoves();
        }
    
        public int compareTo(SearchNode otherNode) 
        {
            return Integer.compare(this.getPriority(), otherNode.getPriority());
        }
        
        public int getPriority() {
            return priority;
        }
        public Board getBoard()
        {
            return board;
        }
        public int getMoves()
        {
            return moves;
        }
        public SearchNode getPreNode() 
        {
            return preSearcchNode;
        }
        
    }
    
    public Solver(Board initial)           // 找到初始板的解決方案(使用A*算法)
    {
        if (initial == null) 
            throw new java.lang.IllegalArgumentException("The intial Board is null");
        
        currentNode = new SearchNode(initial, null);  // 初始
        MinPQ<SearchNode> minInitialPQ = new MinPQ<>();
        minInitialPQ.insert(currentNode);
        
        currentTwinNode = new SearchNode(initial.twin(), null); // 交換兩個位置后的
        MinPQ<SearchNode> minTwinNode = new MinPQ<>();
        minTwinNode.insert(currentTwinNode);
        
        boolean flag = false;
        while (flag != true) 
        {
            // 對原始棋盤進行操作
            currentNode = minInitialPQ.delMin();  // 取樹中優(yōu)先級最小的值
            if (currentNode.getBoard().isGoal())   // 有解
                flag = true;
            else
                putNeighborsBoardToPQ(currentNode, minInitialPQ); // 將鄰居步驟放入樹中
            
            // 對調整順序的棋盤進行操作
            currentTwinNode = minTwinNode.delMin();   // 取樹中優(yōu)先級最小的樹
            if (currentTwinNode.getBoard().isGoal()) 
                flag = true;
             else 
                 putNeighborsBoardToPQ(currentTwinNode, minTwinNode);     // 將鄰居步驟放入樹中
        }
    }
    
    // 將鄰居情況插入到樹中
    private void putNeighborsBoardToPQ(SearchNode currentNode, MinPQ<SearchNode> pMinPQ) 
    {
        Iterable<Board> boards = currentNode.getBoard().neighbors(); // 所有鄰居情況
        for (Board neighborsBoard : boards) {
            if (currentNode.getPreNode() == null || 
                    neighborsBoard.equals(currentNode.getPreNode().getBoard()) != true)   // 防止回退現(xiàn)象
                pMinPQ.insert(new SearchNode(neighborsBoard, currentNode));  // 將鄰居情況插入樹中
        }
    }
    
    public boolean isSolvable()            // 初始板是可解的嗎?
    {
        return currentNode.getBoard().isGoal();
    }
    public int moves()                     // 求解初始板的最小移動數(shù)续滋;如果不可解翰守,則為-1
    {
        if (isSolvable()) 
            return currentNode.getMoves();
        else 
            return -1;
    }
    public Iterable<Board> solution()      // 最短解中的板序列;若不可解則為空
    {
        if (isSolvable() != true) 
            return null;
        stackBoard = new Stack<>();
        SearchNode nowNode = currentNode;
    
        while (nowNode != null) {
            stackBoard.push(nowNode.getBoard());
            nowNode = nowNode.getPreNode();
        }
        
        return stackBoard;
            
    }
    public static void main(String[] args) // solve a slider puzzle (given below)
    {
        int [][]blocks = new int[3][3];
        
        blocks[0][0] = 8;
        blocks[0][1] = 1;
        blocks[0][2] = 3;
        
        blocks[1][0] = 4;
        blocks[1][1] = 0;
        blocks[1][2] = 2;
        
        blocks[2][0] = 7;
        blocks[2][1] = 6;
        blocks[2][2] = 5;
        Board board = new Board(blocks);
        
        Solver solver = new Solver(board);
        System.out.println(solver.currentNode.getPreNode() == null);
        System.out.println(solver.currentNode.getPreNode());
        if (solver.isSolvable() != false) {
            System.out.println("this board is can't resolve");
        }
        
        Iterable<Board> bIterable = solver.solution();
        System.out.println(bIterable.toString());
        System.out.println("444");
        
        for (Board it : bIterable) {
            System.out.println(it.toString());
        }
   
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末疲酌,一起剝皮案震驚了整個濱河市蜡峰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌朗恳,老刑警劉巖湿颅,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異僻肖,居然都是意外死亡肖爵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門臀脏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劝堪,“玉大人,你說我怎么就攤上這事揉稚∶肜玻” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵搀玖,是天一觀的道長余境。 經(jīng)常有香客問我,道長灌诅,這世上最難降的妖魔是什么芳来? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮猜拾,結果婚禮上即舌,老公的妹妹穿的比我還像新娘。我一直安慰自己挎袜,他們只是感情好顽聂,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布肥惭。 她就那樣靜靜地躺著,像睡著了一般紊搪。 火紅的嫁衣襯著肌膚如雪蜜葱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天耀石,我揣著相機與錄音牵囤,去河邊找鬼。 笑死娶牌,一個胖子當著我的面吹牛奔浅,可吹牛的內容都是我干的。 我是一名探鬼主播诗良,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼汹桦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鉴裹?” 一聲冷哼從身側響起舞骆,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎径荔,沒想到半個月后督禽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡总处,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年狈惫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹦马。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡胧谈,死狀恐怖,靈堂內的尸體忽然破棺而出荸频,到底是詐尸還是另有隱情菱肖,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布旭从,位于F島的核電站稳强,受9級特大地震影響,放射性物質發(fā)生泄漏和悦。R本人自食惡果不足惜退疫,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸽素。 院中可真熱鬧蹄咖,春花似錦、人聲如沸付鹿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舵匾。三九已至俊抵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坐梯,已是汗流浹背徽诲。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吵血,地道東北人谎替。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像蹋辅,于是被迫代替她去往敵國和親钱贯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容

  • 四. 走向世界之巔——快速排序 你可能會以為歸并排序是最強的算法了侦另,其實不然秩命。回想一下褒傅,歸并的時間效率雖然高弃锐,但空...
    Leesper閱讀 1,671評論 9 7
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,101評論 1 32
  • 一些概念 數(shù)據(jù)結構就是研究數(shù)據(jù)的邏輯結構和物理結構以及它們之間相互關系,并對這種結構定義相應的運算殿托,而且確保經(jīng)過這...
    Winterfell_Z閱讀 5,802評論 0 13
  • 這個不錯分享給大家霹菊,從扣上看到的,就轉過來了 《電腦專業(yè)英語》 file [fail] n. 文件支竹;v. 保存文...
    麥子先生R閱讀 6,565評論 5 24
  • 寫在前面的話 無意中在cocoaChina的首頁看到了一篇介紹A*算法用swift實現(xiàn)的文章旋廷,對A*尋路算法產(chǎn)生了...
    好好學習天天引體向上閱讀 15,118評論 2 57