回溯法个人理解记录(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+"\t");
            }
            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到达最后的时候就是一个结果!

 

posted @ 2020-05-25 10:19  程序杰杰  阅读(279)  评论(0编辑  收藏  举报