Five common strategy of the algorithm - back strategy

Backtracking strategy

Backtracking is one of five commonly used algorithm strategy, its core idea is actually seen as the solution space is a tree structure, in which the path from the root to a leaf node is a possible solution to the constraints, you can get Solutions to meet the requirements. When solving problems, to find a node does not satisfy the conditions for solving on the "back" to return to try a different path. Backtracking is a search method selected from the preferred, optimal selection by the forward search conditions to achieve the goal. Let's discuss this algorithm strategy through a few examples.

Shining problem

There is a salesman to sell goods to the n cities, he has to find a loop containing the shortest distance of all n cities. (Back to the original final change), that is to say to an undirected weighted graph G <V, E>, adjacency matrix with a stored distance (i.e., weight) between the two cities, require a shortest path.

We set a set data was as follows: four cities, between, the default distance from the city below 0

Here Insert Picture Description

From this we can draw a solution space tree :( drew only a part of the right empathy)

Here Insert Picture Description

According to this solution space tree, its depth-first search, by comparing the optimum result can be obtained (i.e., the shortest path)

package BackTrack;
//解法默认从第0个城市出发,减小了问题难度,主要目的在于理解回溯策略思想
public class Saleman {
    
    //货郎问题的回溯解法
    static int[][] map = {
            { 0,10,5,9},
            {10,0,6,9},
            { 5,6,0,3},
            { 9,9,3,0}
    };          //邻接矩阵
    
    public static final int N = 4;      //城市数量
    static int Min = 10000;             //记录最短的长度
    static int[] city = {1,0,0,0};      //默认第0个城市已经走过
    static int[] road = new int[N];     //路线,road[i] = j表示第i个城市是第j个经过的
    /**
     * 
     * @param city      保存城市是否被经过,0表示未被走过,1表示已经走过
     * @param j         上一层走的是第几个城市
     * @param len       此时在当前城市走过的距离总和
     * @param level     当前所在的层数,即第几个城市
     */
    public static void travel(int[] city, int j, int len, int level) {
        if(level == N - 1) {    //到达最后一个城市
            /*do something*/
            if(len+map[j][0] < Min) {
                Min = len + map[j][0];
                for (int i = 0; i < city.length; i++) {
                    road[i] = city[i];
                }
            }
            return;
        }
        for(int i = 0; i < N; i++) {
            if(city[i] == 0 && map[j][i] != 0) {    //第i个城市未被访问过,且上一层访问的城市并不是此城市
                city[i] = level+2;          //将此城市置为已访问
                travel(city, i, len+map[j][i], level+1);
                city[i] = 0;            //尝试完上一层的路径后,将城市又置为未访问,以免影响后面的尝试情况,避免了clone数组的情况,节省内存开销
            }
        }
        
    }
    
    public static void main(String[] args) {
        travel(city,0,0,0);
        System.out.println(Min);
        for (int i = 0; i < N; i++) {
            System.out.print(road[i]+" ");
        }
        System.out.println("1");
    }
}

Eight queens problem

To put n queens n * n chess board in any of the two queens can not eat each other. Rules: Queen can eat the same row, the same column, any pieces of the same diagonal. Find all solutions. n = 8 is the famous eight Queen's problem.

  Represents a placement position by Queen array, position [i] = j represents the i-th row j-th column in Queens, we start from the first row, each column placed attempts, if possible continues second row , empathy second line of each column continue to try, no matter if you find a line on which column are not possible, place a row above description is not feasible, then back to the top row, the row from the placing of a then try ...... down

  This question is very large solution space tree, the first layer 8 nodes, each node then down another eight children (includes all feasible and infeasible solutions, but every attempt in the past), so there are 8 ^ 8 possible solution.

Here Insert Picture Description

public class Empress {
    final static int N = 8;                   //皇后个数
    static int count = 0;                    //输出的结果个数
    static int[] postion = new int[N];      //保存每一行皇后摆放的位置,position[i] = j表示第i行皇后放在第j列

    /**
     * @param row   判断第row行摆放是否合理
     * @return      合理返回true,否则false
     */
    public static boolean IsOk(int row){
        for (int i = 0; i < row; i++) {     //和前面的每一行进行对比
            if(postion[i] == postion[row] || Math.abs(i-row) == Math.abs(postion[i] - postion[row])){
                //如果在同一列则postion[i] == postion[row]
                //如果在同一斜线上Math.abs(i-row) == Math.abs(postion[i] - postion[row])
                return false;
            }
        }
        return true;
    }

    public static void Print(){
        System.out.println("This is the No."+(++count)+" result:");
        for (int i = 0; i < N; i++) {           //i为行序号
            for (int j = 0; j < N; j++) {       //j为第i行皇后的列的序号
                if(postion[i] == j){    //不是皇后的拜访地址
                    System.out.print("# ");
                }else{
                    System.out.print("@ ");
                }
            }
            System.out.println();       //换行
        }
    }

    /**
     * @param row   尝试第row行皇后的摆放位置,找到可行位置就继续深度搜索下一行,否则在尝试完i的所有取值无果后回溯
     */
    public static void backtrack(int row){
        if(row == N){       //若已经等于N,则说明0~N-1已经赋值完毕,直接打印返回
            Print();
            return;
        }
        for (int i = 0; i < N; i++) {
            postion[row] = i;           //第row行皇后的位置在i处
            if(IsOk(row)){
                backtrack(row+1);
            }else{
                /**
                 * 如果第row行的皇后拜访在i(0-N)处可行,则继续向下深度搜索,否则就直接让这层递归函数出栈
                 * 此层函数出栈也就是当前结点不满足继续搜索的限制条件,即回溯到上一层,继续搜索上一层的下一个i值
                 */
            }
        }
    }

    public static void main(String[] args) {
        backtrack(0);
    }
}

0-1 knapsack backtracking solution

Given n weight w1, w2, w3, ..., wn, the value of v1, v2, v3, ..., vn items and backpack capacity C, find one of the most valuable items in this sub set, so that the capacity of the premise of meeting the backpack, the maximum value of the total package.

Solution space tree this issue is a decision tree, each node has two child nodes, corresponding to the two cases whether the items into the backpack, 0 indicates no load, 1 load, then we 3 can be drawn articles solution space tree

Here Insert Picture Description

package BackTrack;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class Package {
    
    //0-1背包问题,回溯解法
    public static final int N = 5;      //物品数量
    static int[] values = {4,5,2,1,3};  //物品的价值
    static int[] weights = {3,5,1,2,2}; //物品的质量
    public static final int C = 8;      //背包的容量
    static int MAX = -1;                //记录最大的价值
    static int[] bag = {0,0,0,0,0};     //物品放置情况,bag[i] = 0表示第i个物品未被装入,等于1则表示已装入
    static List<int[]> list = new LinkedList<int[]>();  //保存最优的结果,可能有多个结果,所以用链表装
    
    public static boolean IsOk(int[] bag, int level) {  //判断当前背包是否超重
        int weight = 0;
        for (int i = 0; i <= level; i++) {  //计算当前背包中所有的物品的总质量
            if(bag[i] == 1) {   //bag[i] == 1表示这个物品已被装入背包
                weight += weights[i];
                if(weight > C)
                    return false;
            }
        }
        return true;
    }
    
    public static void MaxValue(int[] bag, int level) {
        if(level == N) {                //已经判断完最后一个物品
            //先计算当前总价值
            int value = 0;
            for (int i = 0; i < N; i++) {
                if(bag[i] == 1) {
                    value += values[i];
                }圆排列
            }
            if(value > MAX) {
                list.clear();       //发现更优的结果
                MAX = value;
                list.add(bag.clone());
            }else if (value == MAX) {   //其他放置情况的最优解
                list.add(bag.clone());
            }
            return;
        }
        for (int i = 0; i < 2; i++) {
            bag[level] = i;
            if(IsOk(bag, level)) {
                MaxValue(bag, level+1);
            }
        }
    }

    public static void main(String[] args) {
        MaxValue(bag, 0);
        System.out.println(MAX);
        Iterator<int[]> iter = list.iterator();
        while(iter.hasNext()) {
            int[] temp = iter.next();
            for (int i = 0; i < temp.length; i++) {
                System.out.print(temp[i]+" ");
            }
            System.out.println();
        }
    }
}

Graph coloring problem

  Given no = (V, E), and m different colors, each vertex is colored with the color of the graph G, vertex of each color to the communication graph G. Is there a G coloring of the two adjacent vertices have different colors?
  This question is determined graph coloring problems can be m. If a graph of m colors requires a minimum in order to make the drawing color of each two different vertices connected by edges, the number of colors called for the number m of FIG. Problems FIG m seeking a number of colors m may be referred to FIG optimization coloring.
  Programming Calculation: Given a graph G = a different color (V, E), and m types, to find all the different shading.

Shaped like the following situation:

Here Insert Picture Description

Backtracking policy adopted, there is a vertex with each attempt of each color, according to the example of FIG, 3 is a graph coloring colors four vertices of the solution space tree (including all possible solutions and possible solutions) 3 ^ 4 possible solution. I.e., m ^ n possible solution (m is the number of colors, n is the number of nodes).

package BackTrack;

import java.util.Scanner;

public class Paint {

    static int[][] p_Maxtrix = new int[4][4];       //图的邻接矩阵
    static int Colornum = 3;                        //颜色数目
    static int[] result = {-1,-1,-1,-1};            //保存结果
    
    /**
     * @param index         当前顶点的下标
     * @param color         颜色的编号
     * @return                     染色方案是否可行
     */
    public static boolean IsOk(int index, int color) {      //判断是否可以染色
        for (int i = 0; i < p_Maxtrix.length; i++) {
            if(p_Maxtrix[index][i] == 1 && result[i] == color) {
                return false;
            }
        }
        return true;
    }
    
    public static void backtrack(int index) {
        if(index == p_Maxtrix.length) {         //完成最后一个顶点的着色,输出其中一种结果
            for (int i = 0; i < result.length; i++) {
                System.out.print(result[i]+" ");
            }
            System.out.println();
            return;
        }
        for (int i = 0; i < Colornum; i++) {    //对每一种颜色进行尝试
            result[index] = i;
            if(IsOk(index, i)) {
                backtrack(index+1);
            }
            result[index] = -1;
        }
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        for (int i = 0; i <p_Maxtrix.length ; i++) {
            for (int j = 0; j < p_Maxtrix.length; j++) {
                p_Maxtrix[i][j] = in.nextInt();
            }
        }
        backtrack(0);
    }
}

Yuan with the problem

Given n ranging from the size of the circle c1, c2, ..., cn, n round which now want discharged into a rectangular box, and requires each base circle and tangent to the rectangular frame. Circular permutation problem from the n asked to find all permutations of the circle with a circular arrangement of minimum length. For example, when n = 3, and a given radius of the circle 3 are 1,1,2, the minimum length of three round circle arrangement shown in FIG. Its minimum length img.

img

Algorithm idea: each round all at each location may be arranged as a whole, find the shortest distance.

  • swap function: for sequentially switching among the circle, the circle two positions interchanged.

  • center Function: calculating the t-th coordinates of the center of the circle, to x for array assignment.

  • compute function: in fact, when the last operation after completion of a circle arranged to calculate the distance to this arrangement, compared with the known optimal solution, more preferably updated if the optimal solution.

  • backtrack function: permutations for each attempt backtracking.

    Reference Bowen: circular arrangement

package BackTrack;

public class Round {
    
    public static final int N = 3;          //圆的数目
    static double min = 1000;               //最短距离
    static double[] x = new double[N];      //每个圆的圆心
    static int[] r = {1,1,2};               //每个圆的半径
    static int[] best = new int[N];         //最优解
    
    //交换函数,交换某两个圆的位置
    public static void swap(int[] a, int x, int y) {
        int temp1 = a[x];
        int temp2 = a[y];
        a[x] = temp2;
        a[y] = temp1;位置
    }
    
    /**
     *  对为什么要使用循环做一解释:
     *      因为可能存在第x个圆,它太过于小,而导致其对第x-1和x+1,甚至其他的圆来说,第x个圆存在于不存在都是没影响的
     *      取x-1,和x+1来说:可能x太小,x+1与x-1相切,所以计算第x+1圆心坐标的时候,只能以x-1的圆心与它的圆心来计算
     *      所以要每次循环比较选择最大的那一个做半径
     *      可以参考https://blog.csdn.net/qq_37373250/article/details/81477394中的图
     */
    public static double center(int t) {
        double max = 0.0;               //默认第一个圆的圆心是0.0
        for(int i = 0; i < t; i++) {
            double temp = x[i]+2.0*Math.sqrt(r[i]*r[t]);    //计算得到“以第i个圆的半径和待计算圆的半径”得出的圆心
            //取最大值
            if(temp > max) {
                max = temp;
            }
        }
        return max;
    }
    
    /**
     * 针对为什么不能直接temp = x[N-1]+x[0]+r[N-1]+r[0](直接用第一个圆到最后一个圆的圆心距离加上两圆半径)做一解释:
     *      为避免第一个圆太小,而第二个圆太大,而导致第二个圆的边界甚至超过了第一个圆的边界,最右边同理
     *      那也可以依次推出可能第三个,第四个...的边界超过了第一个圆的边界,右边同理,所以需要每一个都做一下比较
     *      但是可以放心,x是按圆当前排列顺序放置圆心坐标的
     */
    public static void compute() {          //计算按此排列得到的结果
        double low = 0, high = 0;           //分别表示最左边的边际,和最右边的边际
        for(int i = 0; i < N; i++) {
            if(x[i]-r[i] < low) {
                low = x[i]-r[i];
            }
            if(x[i]+r[i] > high) {
                high = x[i]+r[i];
            }
        }
        double temp = high - low;
        if(temp < min) {
            min = temp;
            for (int i = 0; i < N; i++) {
                best[i] = r[i];
            }
        }
    }
    
    public static void backtrack(int t) {
        if(t == N) {
            compute();
            //return;
        }
        for(int i = t; i < N; i++) {    //t之前的圆已经排好顺序了,可能不是最优解,但是一种可能解
            swap(r, t, i);
            double center_x = center(t);
            x[t] = center_x;
            backtrack(t+1);
            /*下面是使用了较为简陋的剪枝算法进行优化
              if(center_x+r[i] < min) {
                x[t] = center_x;
                backtrack(t+1);
            }             
             */
            swap(r, t, i);          //恢复交换之前的
        }
    }
    public static void main(String[] args) {
        backtrack(0);
        for (int i = 0; i < N; i++) {
            System.out.print(best[i]+" ");
        }
        System.out.println();
        System.out.println(min);

    }
}

Continuous postage problem

Assume a national issue n different denominations of stamps and envelopes on each provision only allows a maximum of stamps affixed m. Problems continuous postage value required for a given n and m are given optimum design of postage stamp on an envelope can be put postage starting from 1, in increments of 1, the maximum continuous section of postage. For example, when n = 5 and m = 4, the par value (1,3,11,15,32) five types of postage stamps can post interval is the maximum continuous postage 1-70.

Solution idea: to solve this problem is actually only used backtracking, also used the front of the dynamic programming strategy.

  • First, we should have to have a face value of 1 stamp, or even the beginning of a dollar postage can not be posted.
  • And x is the first denominations of stamps must be between (closed interval) between the first maximum x-1 + 1 postage stamp denominations before x-1 +1 and stamps can be posted
  • After the previous two clear, backtracking method is very simple. Each layer is a layer between the face value and the maximum postage + 1 + 1 on the floor to find the optimal solution for all possible attempts.
  • The final step is to solve each level dp postage biggest problem. Dismantling of view, divided down the right dp and dp, dp understand specific methods can refer to this blog, write out some of the details in the comments below. Continuous postage problem
package BackTrack;

public class Postage {

    public static final int MAX_Postage = 300;  //最大邮资不应该超过这个值
    public static final int N = 6;                              //所用邮票张数
    public static final int M = 5;                              //所用面值种数        
    public static int[] best = new int[M+1];                    //存放最优解,即所有的面值best[0]不用于存储
    public static int[] x = new int[M+1];                       //当前解
    public static int MAX = 0;                                  //最大邮资
    public static int[][] dp = new int[M+1][MAX_Postage];       //用于动态规划保存第x[0]到x[cur]的最大邮资,dp[i][j] = k表示用i种面值的邮票表示j元最少需要k张
    //应该将dp数组初始化到dp[1][i] = i;      即让第一层都等于张数      
    
    
    public static int findMax(int cur) {        
        if(cur == 1) {          //第一层,只能用面值为1的,能表达出的最大邮资为N(张数)
            return N;
        }
        //向下dp
        int j = 1;      //指示列坐标
        while (dp[cur-1][j] != 0) {
            //此处dp的思路其实就是利用动态规划解决0-1背包问题时的思路,对新加入面值的邮票用与不用?用了用几张的问题?
            //不用时
            dp[cur][j] = dp[cur-1][j];
            //用的时候,用几张?
            for(int i = 1; i*x[cur] <= j; i++) {        //i表示面值张数
                int temp = dp[cur-1][j-i*x[cur]] + i;   //dp[cur-1][j-i*x[cur]]表示除了新加入的面值之外前面所有的面值共同表达j-i*x[cur]元所需张数
                dp[cur][j] = Math.min(temp, dp[cur][j]);        //取最小的张数
            }
            j++;
        }
        
        //向右dp
        while(true) {
            int temp = MAX_Postage;     
            for(int i = 1; i <= cur; i++) {
                /**
                 * 这里很妙,因为向右dp时每次都是向右一个一个推进,所以我们从x[]的第一种面值开始往上加,直到超过限制张数,那么如果x[]的
                 * 第二种面值刚好能将前面的多个第一种替换,那就替换更新张数
                 * 反正意思就是每一次for循环是对前面的较小面值的邮票是一个浓缩的过程
                 */
                temp = Math.min(dp[cur][j-x[i]]+1, temp);
            }
            if(temp > N || temp == MAX_Postage) {     //不管怎么使用当前解x[]中的已知面值,都不能在张数限制内表达出j元
                break;
            }else {
                dp[cur][j] = temp; 
            }
            j++;
        }
        /**对下面这条语句做一个解释
         * 确保同一层上一次dp的结果不会影响下一次**尝试**时的dp,因为可能上一次尝试的一个分支中dp时已经给dp[2][10]赋过值了,但如果没有这一句
         * 就会导致后面的某次尝试时一个分支中dp的时候,向下dp的时候直接将dp[2][10]向下dp了,而事实上,应该向右dp的时候才给dp[2][10]赋值的
         * 其实就是向回溯的下一层发一个信号,表示这块是我上一层dp停止的地方,过了这块可能就是别的回溯分支给dp赋的值了
         */
        dp[cur][j] = 0; 
        return j-1;
    }
    
    public static void backtrack(int t) {                       //t表示当前层数
        if(t == M) {    //已经选够最多的面值种类
            int max = findMax(t);
            if(max > MAX) {
                MAX = max;
                for (int i = 0; i < best.length; i++) {
                    best[i] = x[i];
                }
            }
        //return;   
        }else {
            int temp = findMax(t);                              //得到当前层的最大邮资    
            for(int i = x[t]+1; i <= temp+1; i++) {
                x[t+1] = i;
                backtrack(t+1);
            }           
        }
    }
    
    public static void main(String[] args) {
        for (int i = 0; i <= N; i++) {
            dp[1][i] = i;
        }
        x[0] = 0;
        x[1] = 1;
        backtrack(1);
        System.out.println(MAX);
        for (int i = 0; i < best.length; i++) {
            System.out.print(best[i]+" ");
        }
    }
}

Guess you like

Origin www.cnblogs.com/vfdxvffd/p/12484932.html