• 八数码难题(8 puzzle)深度优先和深度优先算法


    1 搜索策略

    搜索策略是指在搜索过程中如何选择扩展节点的次序问题。一般来说,搜索策略就是采用试探的方法。它有两种类型:一类是回溯搜索,另一类是图搜索策略。

    2 盲目的图搜索策略

    图搜索策略又可分为两种:一种称为盲目的图搜索策略,或称无信息图搜索策略;而另一种称为启发式搜索策略,又称为有信息的图搜索策略。最常用的两种无信息图搜索策略是宽度优先搜索和深度优先搜索。

    2.1 宽度优先搜索

    它是从根节点(起始节点)开始,按层进行搜索,也就是按层来扩展节点。所谓按层扩展,就是前一层的节点扩展完毕后才进行下一层节点的扩展,直到得到目标节点为止。

    这种搜索方式的优点是,只要存在有任何解答的话,它能保证最终找到由起始节点到目标节点的最短路径的解,但它的缺点是往往搜索过程很长。

    2.2 深度优先搜索

    它是从根节点开始,首先扩展最新产生的节点,即沿着搜索树的深度发展下去,一直到没有后继结点处时再返回,换一条路径走下去。就是在搜索树的每一层始终先只扩展一个子节点,不断地向纵深前进直到不能再前进(到达叶子节点或受到深度限制)时,才从当前节点返回到上一级节点,沿另一方向又继续前进。这种方法的搜索树是从树根开始一枝一枝逐渐形成的。

    由于一个有解的问题树可能含有无穷分枝,深度优先搜索如果误入无穷分枝(即深度无限),则不可能找到目标节点。为了避免这种情况的出现,在实施这一方法时,定出一个深度界限,在搜索达到这一深度界限而且尚未找到目标时,即返回重找,所以,深度优先搜索策略是不完备的。另外,应用此策略得到的解不一定是最佳解(最短路径)。

    3 “八”数码难题的宽度优先搜索与深度优先搜索

    3.1“八”数码难题的宽度优先搜索

    步骤如下:

    1、判断初始节点是否为目标节点,若初始节点是目标节点则搜索过程结束;若不是则转到第2步;

    2、由初始节点向第1层扩展,得到3个节点:2、3、4;得到一个节点即判断该节点是否为目标节点,若是则搜索过程结束;若2、3、4节点均不是目标节点则转到第3步;

    3、从第1层的第1个节点向第2层扩展,得到节点5;从第1层的第2个节点向第2层扩展,得到3个节点:6、7、8;从第1层的第3个节点向第2层扩展得到节点9;得到一个节点即判断该节点是否为目标节点,若是则搜索过程结束;若6、7、8、9节点均不是目标节点则转到第4步;

    4、按照上述方法对下一层的节点进行扩展,搜索目标节点;直至搜索到目标节点为止。

    5、搜索过程见附图1所示。

    3.2 “八”数码难题的深度优先搜索

    步骤如下:

    1、设置深度界限,假设为5;

    2、判断初始节点是否为目标节点,若初始节点是目标节点则搜索过程结束;若不是则转到第2步;

    3、由初始节点向第1层扩展,得到节点2,判断节点2是否为目标节点;若是则搜索过程结束;若不是,则将节点2向第2层扩展,得到节点3;

    4、判断节点3是否为目标节点,若是则搜索过程结束;若不是则将节点3向第3层扩展,得到节点4;

    5、判断节点4是否为目标节点,若是则搜索过程结束;若不是则将节点4向第4层扩展,得到节点5;

    6、判断节点5是否为目标节点,若是则搜索过程结束;若不是则结束此轮搜索,返回到第2层,将节点3向第3层扩展得到节点6;

    7、判断节点6是否为目标节点,若是则搜索过程结束;若不是则将节点6向第4层扩展,得到节点7;

    8、判断节点7是否为目标节点,若是则结束搜索过程;若不是则将节点6向第4层扩展得到节点8;

    9、依次类推,知道得到目标节点为止。

    10、搜索过程见附图2所示。

    3.3 搜索效率比较

    不同搜索策略搜索效率的衡量指标:

    1、外显率(P):反映搜索工程中,从初始节点向目标节点进行搜索的区域的宽度

    P=L/T;P∈(0,1]

    L:从初始节点到达目标的路径长度;

    T:整个过程中生成的节点总数(不包括初始节点);

    2、有效分支分数(B):表示每个有效节点平均生成的子节点数目

    T=B+B2+…+BL,B≥1

    当B=1时,T=1,此时搜索效率最高

    当B>1时,clip_image002clip_image004

    3.3.1 采用宽度优先搜索时的搜索效率 

    L=5,T=26,则P=L/T=5/26=0.192,B=1.613;

    3.3.1 采用宽度优先搜索时的搜索效率

    L=5,T=18,则P=L/T=5/18=0.278,B=1.464;

    由以上计算可以看出,深度优先搜索的效率明显高于宽度优先搜索。

    3.4上述两种搜索策略的比较

    在宽度优先搜索过程中,扩展到第26个节点时找到了目标节点;而在深度优先搜索过程中,扩展到第18个节点时得到了目标节点。

    在宽度优先搜索过程中,需要遍历目标节点所在层之前每层的所有节点,即需要遍历所有的分支。而深度优先搜索过程中,则不需要遍历这么多的节点。所以,在“八”数码难题的求解过程中,深度优先搜索的效率明显比宽度优先搜索的效率要高。

    一般情况下,深度优先适合深度大的树,不适合广度大的树,广度优先则正好相反。所谓深度大的树就是指起始节点到目标节点的中间节点多的树(可以理解成问题有很多中间解,这些解都可以认为是部分正确的,但要得到完全正确的结果——目标节点,就必须先依次求出这些中间解)。所谓广度大的树就是指起始节点到目标节点的可能节点很多的树(可以理解成问题有很多可能解,这些解要么正确,要么错误。要得到完全正确的结果——目标节点,就必须依次判断这些可能解是否正确)。

    多数情况下,深度优先搜索的效率要高于宽度优先搜索。但某些时候,对于这两种搜索策略的优劣(或效率)还需要针对不同的问题进行具体分析比较。

    wfs

    dfs

    深度优先C#代码:

     public class DFS
        {
            static public bool DFSOn = false;       //initial flag. if set initializes the array variables.
            static public bool solving = false;     //solving flag set whilst the algorithm is running.
    
            static private bool trackBack = false;  //trackBack flag set if the puzzle get stuck to track back moves
    
            private static Stack stackDFS = new Stack();    //FIFO structure required for DFS
            private static Stack stackMoves = new Stack();  //Stack stores everymove made. used for tracking back
    
            private static ArrayList xVisitedList = new ArrayList();    //list to store all the states that have been expanded
            private static ArrayList yVisitedList = new ArrayList();
    
            public static Double[][] xTempPuzzle = new Double[3][];     //actual x & y 2-d arrays strore the pos of squares
            public static Double[][] yTempPuzzle = new Double[3][];
    
            //gets the number of moves made towards the goal state.
            static public int getNumberOfMovesMade()
            {
                return stackMoves.Count;
            }
    
    
            //initialzes the 2-d arrays xTempPuzzle and yTempPuzzle and sets them to the current value of the
            //Puzzle displayed on the screen.
            static public void initializeArray()
            {
                for (int i = 0; i < 3; i++)
                {
                    xTempPuzzle[i] = new Double[3];
                    yTempPuzzle[i] = new Double[3];
                }
    
                xTempPuzzle[0][0] = PuzzleData.xPuzzle[0][0];
                xTempPuzzle[0][1] = PuzzleData.xPuzzle[0][1];
                xTempPuzzle[0][2] = PuzzleData.xPuzzle[0][2];
                xTempPuzzle[1][0] = PuzzleData.xPuzzle[1][0];
                xTempPuzzle[1][1] = PuzzleData.xPuzzle[1][1];
                xTempPuzzle[1][2] = PuzzleData.xPuzzle[1][2];
                xTempPuzzle[2][0] = PuzzleData.xPuzzle[2][0];
                xTempPuzzle[2][1] = PuzzleData.xPuzzle[2][1];
                xTempPuzzle[2][2] = PuzzleData.xPuzzle[2][2];
    
                yTempPuzzle[0][0] = PuzzleData.yPuzzle[0][0];
                yTempPuzzle[0][1] = PuzzleData.yPuzzle[0][1];
                yTempPuzzle[0][2] = PuzzleData.yPuzzle[0][2];
                yTempPuzzle[1][0] = PuzzleData.yPuzzle[1][0];
                yTempPuzzle[1][1] = PuzzleData.yPuzzle[1][1];
                yTempPuzzle[1][2] = PuzzleData.yPuzzle[1][2];
                yTempPuzzle[2][0] = PuzzleData.yPuzzle[2][0];
                yTempPuzzle[2][1] = PuzzleData.yPuzzle[2][1];
                yTempPuzzle[2][2] = PuzzleData.yPuzzle[2][2];
            }
    
    
            //resets all the variables. called when the reset button is clicked or the algorithm has finished
            static public void resetAll()
            {
                xVisitedList.Clear();
                yVisitedList.Clear();
                stackDFS.Clear();
                stackMoves.Clear();
                DFSOn = true;
                solving = false;
            }
    
    
            //the entry point method of this class. Get's called everytime a next move using DFS is required.
            static public bool solvePuzzle()
            {
                if (DFSOn) { initializeArray(); DFSOn = false; }
    
                if (PuzzleData.isFinalState(DFS.xTempPuzzle, DFS.yTempPuzzle)) { return true; }
    
                addNextMove();
                movePuzzle();
    
                return false;
            }
    
    
            //Each square of the shuffle can have 3 values each for x and y position. Those values
            //are 0, 100 & 200. This function checks if a left, up, down or right move is possible.
            //It does so by looking at the current x and y position of the blank square of the puzzle.
            static private bool isValidMove(int direction)
            {
                if (direction == 0) //check if sliding left is possible
                {
                    if (DFS.xTempPuzzle[2][2] >= 100) { return true; }
                }
                else if (direction == 1) //check if sliding up is possible
                {
                    if (DFS.yTempPuzzle[2][2] >= 100) { return true; }
                }
                else if (direction == 2)//check if sliding right is possible
                {
                    if (DFS.xTempPuzzle[2][2] < 200) { return true; }
                }
                else if (direction == 3) //check if sliding down is possible
                {
                    if (DFS.yTempPuzzle[2][2] < 200) { return true; }
                }
    
                return false;
            }
    
    
            //Checks if the current state has been expanded before or not contrary to the name
            //it doesn't check all the visited states but expanded states.
            static private bool isVisitedState()
            {
                for (int cnt = xVisitedList.Count - 1; cnt > -1; cnt--)
                {
                    int sameElementCnt = 0;
    
                    for (int i = 0; i < 3; i++)
                    {
                        for (int j = 0; j < 3; j++)
                        {
                            if ((((Double[][])xVisitedList[cnt])[i][j] == xTempPuzzle[i][j]) &&
                                ((Double[][])yVisitedList[cnt])[i][j] == yTempPuzzle[i][j])
                            {
                                sameElementCnt++;
                            }
                        }
                    }
    
                    if (sameElementCnt == 9) { return true; }
                }
                return false;
            }
    
    
            //The method below checks for a valid move and then checks if the state has been visited
            //if not the it adds that direction to the stackDFS variable.
            static private void addNextMove()
            {
                bool moveAdded = false;
    
                if (isValidMove(0))
                {
                    PuzzleData.moveLeft(xTempPuzzle, yTempPuzzle);
                    if (isVisitedState())
                    {
                        PuzzleData.moveRight(xTempPuzzle, yTempPuzzle);
                    }
                    else
                    {
                        PuzzleData.moveRight(xTempPuzzle, yTempPuzzle);
                        stackDFS.Push(0);
                        moveAdded = true;
                    }
                }
    
                if (isValidMove(1))
                {
                    PuzzleData.moveUp(xTempPuzzle, yTempPuzzle);
                    if (isVisitedState())
                    {
                        PuzzleData.moveDown(xTempPuzzle, yTempPuzzle);
                    }
                    else
                    {
                        PuzzleData.moveDown(xTempPuzzle, yTempPuzzle);
                        stackDFS.Push(1);
                        moveAdded = true;
                    }
                }
    
                if (isValidMove(2))
                {
                    PuzzleData.moveRight(xTempPuzzle, yTempPuzzle);
                    if (isVisitedState())
                    {
                        PuzzleData.moveLeft(xTempPuzzle, yTempPuzzle);
                    }
                    else
                    {
                        PuzzleData.moveLeft(xTempPuzzle, yTempPuzzle);
                        stackDFS.Push(2);
                        moveAdded = true;
                    }
                }
    
                if (isValidMove(3))
                {
                    PuzzleData.moveDown(xTempPuzzle, yTempPuzzle);
                    if (isVisitedState())
                    {
                        PuzzleData.moveUp(xTempPuzzle, yTempPuzzle);
                    }
                    else
                    {
                        PuzzleData.moveUp(xTempPuzzle, yTempPuzzle);
                        stackDFS.Push(3);
                        moveAdded = true;
                    }
                }
    
    
                //if no move could be found happens when the current state cannot get out because all the
                //neighbouring states have already been expanded. It adds the opposite of the last move made
                //which makes the puzzle go back one step.
                if (!moveAdded)
                {
                    int lastMove = (int)stackMoves.Pop();
                    if (lastMove == 0) { lastMove = 2; }
                    else if (lastMove == 1) { lastMove = 3; }
                    else if (lastMove == 2) { lastMove = 0; }
                    else if (lastMove == 3) { lastMove = 1; }
    
                    stackDFS.Push(lastMove);
                    trackBack = true;   //trackBack flag is set for the movePuzzle method. see there for more explanation.
                }
            }
    
    
            //Method that updates the puzzle with the direction popped of the DFS stack. Then adds the new puzzle state
            //to the visited list.
            static private void movePuzzle()
            {
                int direction = (int)stackDFS.Pop();
    
                if (direction == 0) //Move Left
                {
                    PuzzleData.moveLeft(xTempPuzzle, yTempPuzzle);
                }
                else if (direction == 1) //Move Up
                {
                    PuzzleData.moveUp(xTempPuzzle, yTempPuzzle);
                }
                else if (direction == 2) //Move Right
                {
                    PuzzleData.moveRight(xTempPuzzle, yTempPuzzle);
                }
                else if (direction == 3) //Move Down
                {
                    PuzzleData.moveDown(xTempPuzzle, yTempPuzzle);
                }
    
                //if the trackBack flag is set as there were no moves, it doesn't add the move to the movesList stack.
                if (!trackBack) { addMove(direction); }
                addVisitedList();
    
                trackBack = false;
            }
    
    
            //adds the cuurent state the puzzle is in to the visitedList.
            static private void addVisitedList()
            {
                Double[][] xTemp = new Double[3][];
                xTemp[0] = new Double[3];
                xTemp[1] = new Double[3];
                xTemp[2] = new Double[3];
                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        xTemp[i][j] = xTempPuzzle[i][j];
                    }
                }
                xVisitedList.Add(xTemp);
    
                Double[][] yTemp = new Double[3][];
                yTemp[0] = new Double[3];
                yTemp[1] = new Double[3];
                yTemp[2] = new Double[3];
                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        yTemp[i][j] = yTempPuzzle[i][j];
                    }
                }
                yVisitedList.Add(yTemp);
            }
    
    
            //adds the previous move (while NOT trackingBack) to the visited list.
            static private void addMove(int direction)
            {
                stackMoves.Push(direction);
            }
        }

    最后运行效果图:

    8puzzle

    程序可执行文件下载

  • 相关阅读:
    [笔记]C#基础入门(十九)——C#中else与if的匹配
    [笔记]C#基础入门(十八)——C#中多重if结构
    [笔记]C#基础入门(十七)——C#中嵌套的if结构
    [笔记]C#基础入门(十六)——C#中if...else条件结构
    [笔记]C#基础入门(十五)——C#中判断和分支
    [笔记]C#基础入门(十四)——C#用流程图描述程序逻辑
    [笔记]C#基础入门(十三)——C#的运算符优先级
    [笔记]C#基础入门(十二)——C#的赋值运算符
    [笔记]C#基础入门(十一)——C#的逻辑运算符
    程序员读书目录推荐
  • 原文地址:https://www.cnblogs.com/luoht/p/1965724.html
Copyright © 2020-2023  润新知