• 回溯法个人理解记录(C#八皇后)


    回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

    对于八皇后和矩阵与寻找字符串也是如此,我一个个分析,为了自己更加深刻地理解记忆,我将尝试非常清晰的描绘出整个思路,当然如何能帮到查看的你,我也十分欣慰。

    第一题:

    分析:

    大思路:

    看到本体,主要思路就是你既然要我找字符串,那么我就遍历整个矩阵,一个个元素节点开始出发,如果我能找到一个,本题就结束了,如果找到最后一个都没找到,那么over直接return false即可。

    细节:

    假设我们需要找“path“,那么我们需要首先找p如果满足条件再找他的周围有没有a,..直到h, 当然如果p都没找到,那么遍历下一个,如果找到p但是从该点出发找不到a那么也遍历下一个,只有我们找到h,才算满足条件。

    题目也要求我们同一节点只能走一次,那么我需要一个和矩阵大小相同的另一个矩阵【isPass】来记录我走过的路,在我们判别该节点出发找下一个节点的时候,我们需要结合isPass来判断。

    ok思路就是如此

    接下来代码分析,另外提一点,我们输入的是string类型字符串,如果我们把它转化为m*n矩阵的话,在这里我给出给一个不包含异常判别的方法。

      //这个方法我没有做防错,默认是我们输入是合法的。
            private static char[,] getMatrix(string matrix, int rows, int cols)
            {
                char[,] strArr = new char[rows, cols];
                int rowIndex = 0, colIndex = 0;
                for (int i = 0; i < matrix.Length; i++)
                {
                    strArr[rowIndex, colIndex++] = matrix[i];
                    if (colIndex == cols)
                    {
                        colIndex = 0;
                        rowIndex++;
                    }
                }
                return strArr;
            }

     //接下来我们暂时不使用矩阵转化,代码如下:

      //一个个找
            //首先从0索引开始 如果找到就找下一个索引 直到找到path个长度 说明全部找完
            public bool hasPath(string matrix, int rows, int cols, string path)
            {
                // write code here
                bool[,] isPass = new bool[rows,cols];
                for (int i = 0; i < rows; i++)
                {
                    for (int j = 0; j < cols; j++)
                    {
                        if (isFind(matrix,rows,cols,i,j,path,0, isPass))
                            return true;
                    }
                }
                return false;
            }
     //初始k带入都是0  如何能到path.Length呢? 只有在isFind内部 满足条件的时候k才能继续增长  
            //当然要记住 如果不满足条件 不要忘记将该点标记置为false,因为数组为引用类型
            private bool isFind(string matrix, int rows, int cols,int rowIndex,int colIndex,string path,int k, bool[,] isPass)
            {
                //这样使用深拷贝 ,然后下方可以不写  isPass[rowIndex, colIndex] = false; 但是这样 运行速度 会变慢不少
                //因此能使用同一个数组就是用同一个数组
                //对于是否有一个满足的我们就是用同一个
                //对于找出所有我们使用 下方数组深拷贝,同时注意在本次赋值前,需要将本次的其他值清空
                //bool[,] isPass2 = new bool[isPass.GetLength(0), isPass.GetLength(1)];
                //for (int i = 0; i < isPass.GetLength(0); i++)
                //{
                //    for (int j = 0; j < isPass.GetLength(1); j++)
                //    {
                //        isPass2[i, j] = isPass[i,j];
                //    }
                //}
    
                if (k == path.Length) { return true; }
                if(rowIndex>=0&& rowIndex<rows&& colIndex>=0&& colIndex<cols&& !isPass[rowIndex,colIndex]&& matrix[rowIndex* cols+ colIndex]==path[k])
                {
                    isPass[rowIndex, colIndex] = true;
                    if(
                        isFind(matrix, rows, cols, rowIndex,colIndex-1, path, k+1, isPass) ||//
                        isFind(matrix, rows, cols, rowIndex,colIndex+1, path, k+1, isPass) ||//
                        isFind(matrix, rows, cols, rowIndex-1,colIndex, path, k+1, isPass) ||//
                        isFind(matrix, rows, cols, rowIndex+1,colIndex, path, k+1, isPass)   //
                        )
                        return true;
                    //没成功
                    isPass[rowIndex, colIndex] = false;
                }
                return false;
            }

    上方hasPath就是遍历矩阵每个节点,进行判别

    isFind是回溯的核心,就是当满足条件的时候,就是行、列在数组范围内,本节点可以走,该节点满足我们需要查找的字符,这样的话我们标记节点为已经走过,同时判断周围节点,如果周围节点有一个可以达到,那么我们就找到答案了,如果周围节点都达不到,我们一定要将刚刚标记的节点,取消标记,因为数组是引用类型,本次失败了,我们要回溯到之前的状态。

    当k达到了字符的最后一位时,说明我们已经找到,可以返回true了。

    对于代码中isFind下方的注释,我们等下看完八皇后之后,可以做个总结,就很容易理解了。

    题目二:八皇后问题

    大家应该都知道什么是八皇后问题,当然下方我写的代码是针对于八皇后的,对于扩展性也没考虑,如果需要扩展的话,稍微改改就好了,OK我们主要聊思路。

    首先对于初始化,和打印,这种简单的方法,写法如下:

        static void Print(int[,] arr)
            {
                for (int i = 0; i < arr.GetLength(0); i++)
                {
                    for (int j = 0; j < arr.GetLength(1); j++)
                    {
                        Console.Write(arr[i, j] + " ");
                    }
                    Console.WriteLine();
                }
                Console.WriteLine("");
            }
            static int[,] initArr()
            {
                int[,] res = new int[8, 8];
                for (int i = 0; i < 8; i++)
                {
                    for (int j = 0; j < 8; j++)
                    {
                        res[i, j] = 0;
                    }
                }
                return res;
            }
    棋盘初始化和打印

    对于八皇后问题解很多,我们需要找各个解,因此呢,我们不能只是用一个棋盘

    完整的思路:我们从第一行开始逐列遍历,我们判断皇后放在当前位置是否ok? 如果ok的话我们将本棋盘该点置为1,然后继续判别第二行,然后第三行...如果在最后一行也就是第7行的时候也能找到满足的位置,那么ok一个棋盘已经成型。

    对于这种情况:当我们判别到第6行第一列满足条件,在第7行也找到了一个满足条件的,然后我们会继续寻找第6行从第一列后面开始找,我们一定找不到,为什么呢,因为我们在第1行的时候满足条件已经将该点标识为1了,因此按照要求该行已经不能放了,但是呢上个节点结果已经判别完了,我们需要将它清掉,ok那我们怎么清空呢,此时和那一点已经没关系了,所以我们需要在每次判断该行各个列之前,将该行的棋盘全部0。

    代码:

     static void EightQueen()
            {
                int[,] arr = initArr();
                findRes(0, arr);
            }
      static void findRes(int rowIndex, int[,] arr)
            {
                //每次执行方法前深拷贝一个数组,这样才不会污染原数组,本次失败和其他层级之间并不会产生影响
                int[,] arr2 = new int[8, 8];
                for (int i = 0; i < 8; i++)
                {
                    for (int j = 0; j < 8; j++)
                    {
                        int a = arr[i, j];
                        arr2[i, j] =a;
                    }
                }
                if (rowIndex == 8)
                {
                    num++;
                    Console.WriteLine("" + num + "个为:");
                    Print(arr2);
                    return;
                }
                for (int i = 0; i < 8; i++)
                {
                    //加入i=1时候isok可以进去 当判断i=2也可以的时候,i=1并没有清除掉,因此这里需要在调整本行之前,先让本行全部清0  然后再对我们需要的进行设置
                    //必须先清掉 才能判别,否则可能判别不成功,因此这个需要在isOk判别之前写。
                    for (int j = 0; j < 8; j++)
                    {
                        arr2[rowIndex, j] = 0;
                    }
                    //如果放在本行的i该位置满足条件  这样才能继续找下一行
                    if (isOk(rowIndex, i, arr2))
                    {
                        arr2[rowIndex, i] = 1;
                        findRes(rowIndex + 1, arr2);
                    }
                }
            }

    对于其中的isOk其实很简单,判断上下 左右 左上 左下 右上 右下是否都满足条件,如果满足条件即可嘛。

            static bool isOk(int rowIndex, int colIndex, int[,] arr)
            {
                bool res = false;
                //判断上下 左右 左上 左下 右上 右下是否都满足条件
                bool topBottom = true, leftRight = true, leftTop = true, leftBottom = true, rightTop = true, rightBottom = true;
                //上下
                for (int i = 0; i < arr.GetLength(0); i++)
                {
                    if (arr[i,colIndex] != 0) { topBottom = false; }
                }
                //左右
                for (int i = 0; i < arr.GetLength(1); i++)
                {
                    if (arr[rowIndex, i] != 0) { leftRight = false; }
                }
                //左上
                int tempR = rowIndex;
                int tempC = colIndex;
                while (--tempR >= 0&&--tempC>=0)
                {
                    if (arr[tempR, tempC]!=0) { leftTop = false; }
                }
                //左下
                tempR = rowIndex;
                tempC = colIndex;
                while (++tempR <= 7 && --tempC >= 0)
                {
                    if (arr[tempR, tempC] != 0) { leftBottom = false; }
                }
                //右上
                tempR = rowIndex;
                tempC = colIndex;
                while (--tempR >=0 && ++tempC <= 7)
                {
                    if (arr[tempR, tempC] != 0) { rightTop = false; }
                }
                //右下
                tempR = rowIndex;
                tempC = colIndex;
                while (++tempR <= 7 && ++tempC <= 7)
                {
                    if (arr[tempR, tempC] != 0) { rightTop = false; }
                }
                if (topBottom&& leftRight&& leftTop&& leftBottom && rightTop && rightBottom)
                {
                    return true; 
                }
                return res;
            }

     java求解,精简思路在手机留言

    public class EightQueenOptimization {
        public static void main(String[] args) {
            findResolve(0);
            System.out.println(count);
        }
        static boolean[][] visited=new boolean[8][8];
        static int count;
        public static void findResolve(int k){
            if(k==8){
                Print();
                System.out.println();
                count++;
                return;
            }
            for (int i = 0; i < 8; i++) {
                visited[k][i]=true;
                if(!isDange(k,i)){
                    findResolve(k+1);
                }
                //清除该行,另该节点为true
                //放在这是因为最后一次走的时候他直接返回到上一层栈中了,只有放在这才会都执行,如果放在上方,最后一次返回上一层栈的时候就
                //不会清掉该行,就会有问题了!
                for (int j = 0; j < 8; j++) {
                    visited[k][j]=false;
                }
            }
        }
        public static void Print(){
            for (int i = 0; i < 8; i++) {
                for (int j = 0; j < 8; j++) {
                    int n=visited[i][j]?1:0;
                    System.out.print(n+"	");
                }
                System.out.println("");
            }
        }
        public static boolean isDange(int rowIndex,int colIndex){
            //整行
            //整列
            for (int i = 0; i < 8; i++) {
                if(visited[rowIndex][i]){
                    if(i==colIndex){
                        continue;
                    }
                    return true;
                }
            }
            for (int i = 0; i < 8; i++) {
                if (visited[i][colIndex]) {
                    if(i==rowIndex){
                        continue;
                    }
                    return true;
                }
            }
            //左上、左下。右上。右下
            int cl,cr,rt,rb;
            cl=colIndex-1;rt=rowIndex-1;
            while (cl>=0&&rt>=0){
                if(visited[rt][cl]){
                    return true;
                }
                cl--;
                rt--;
            }
            //左下
            cl= colIndex-1;rb=rowIndex+1;
            while (cl>=0&&rb<8){
                if(visited[rb][cl]){
                    return true;
                }
                cl--;rb++;
            }
            //右上
            cr=colIndex+1;rt=rowIndex-1;
            while (cr<8&&rt>=0){
                if(visited[rt][cr]){
                    return true;
                }
                cr++;rt--;
            }
            //右下
            cr=colIndex+1;rb=rowIndex+1;
            while (cr<8&&rb<8){
                if(visited[rb][cr]){
                    return true;
                }
                cr++;rb++;
            }
            return false;
        }
    }

    综上:很容易发现,回溯问题求解法和定义一样,我发现回溯问题的 都有一个共同点,就是使用k来记录当前可以到达的位置,只有满足条件的时候k才能继续往下走,当k到达最后的时候就是一个结果!

  • 相关阅读:
    C#中的Byte,String,Int,Hex之间的转换函数。
    js继承之原型继承
    node.js module初步理解
    手游热更新方案--Unity3D下的CsToLua技术
    像素迷踪,当Unity的Frame Debugger力不从心时
    Unity内存理解(转)
    Unity5的AssetBundle(二、加载&卸载)
    Sprite Atlas与Sprite Mask详解
    Unity2017新功能Sprite Atlas详解
    Util应用程序框架公共操作类(一):数据类型转换公共操作类(介绍篇)
  • 原文地址:https://www.cnblogs.com/ningxinjie/p/12955336.html
Copyright © 2020-2023  润新知