那些经典算法:如果可以重新选择

生活中总有些错误的决定,乃至多年之后,感叹一句,如果可以重新选择就好了,本文要聊的回溯算法就是通过多次重新选择,来达到最期望的效果;贪心算法是每步都求当前的最优解,回溯是类似于遍历所有选择,从而选择最优的选择。
简单举个例子,就如同走迷宫一样,从一个岔路口,选择一个路径,如果发现走不通,则退回到这个路口选择下一个路口,直到达到目的地。

实例

经典的回溯算法有八皇后问题,0-1背包问题:

  • 八皇后问题
    国际象棋中的皇后比中国象棋中的车还厉害,皇后可以横向,纵向和斜向运动,在这三条线上的任何其他棋子都可以被吃掉。
    八皇后问题,就是把八位皇后,放在一张8*8的棋盘上,使得每个皇后都无法吃掉其他皇后。


    那些经典算法:如果可以重新选择_第1张图片
    八皇后

    我们把问题分为8个阶段,依次把皇后放在第一行,第二行,...第八行。在放的过程中,我们都要校验在当前放的办法情况下,是否存在着不满足条件的情况,如果有,则换一种放的方法;如此往复,直到摆满八行为止。

public class EightQueue {

    // 全局或成员变量, 下标表示行, 值表示 queen 存储在哪一列
    int[] result = new int[8];
    int   nums = 0;

    public void cal8queens(int row) {
        // 8 个棋子都放置好了,打印结果
        if (row == 8) {
            printQueens(result);
            nums++;
            // 8 行棋子都放好了,已经没法再往下递归了,所以就 return
            return;
        }
        // 每一行都有 8 中放法
        for (int column = 0; column < 8; ++column) {
            // 有些放法不满足要求
            if (isOk(row, column)) {
                // 第 row 行的棋子放到了 column 列
                result[row] = column;
                // 考察下一行
                cal8queens(row+1);
            }
        }
    }
    // 判断 row 行 column 列放置是否合适
    private boolean isOk(int row, int column) {
        // 分别标识左上角数组标号   右上角数组标号
        int leftup = column - 1, rightup = column + 1;
        // 逐行往上考察每一行
        for (int i = row-1; i >= 0; --i) {
            // 第 i 行的 column 列有棋子吗?
            // 前一行同样位置是否有
            if (result[i] == column) {
                return false;
            }
            // 考察左上对角线:第 i 行 leftup 列有棋子吗?
            if (leftup >= 0) {
                if (result[i] == leftup){
                    return false;
                }
            }
            // 考察右上对角线:第 i 行 rightup 列有棋子吗?
            if (rightup < 8) {
                if (result[i] == rightup) {
                    return false;
                }
            }
          // 每向上一行,位置要偏移一格
            --leftup;
            ++rightup;
        }
        return true;
    }

    // 打印出一个二维矩阵
    private void printQueens(int[] result) {
        for (int row = 0; row < 8; ++row) {
            for (int column = 0; column < 8; ++column)
            {
                if (result[row] == column)
                    System.out.print("Q ");
                else
                    System.out.print("* ");
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String [] args)
    {
        EightQueue eq = new EightQueue();
        eq.cal8queens(0);
        System.out.println("满足的排列方法有:"+eq.nums+"种");
    }
}
  • 01 背包问题
    我们有一个背包,背包的总的承受重要为w。现在又n个物品,每个物品的重量各不相同,且不可分割,我们期望选择几件物品,在不超过背包总重要的前提下,让背包的总重量最大。
    我们把物品依次排列,整个问题分为n个阶段,每个阶段来决定一个物品是否被装入背包,递归处理剩下的物品:
package  lms;
public class Bag
{
    // 存储背包中物品总重量的最大值
    private int maxW = Integer.MIN_VALUE;
    private static int MAX_NUM = 50;
    private int [] chose = new int[MAX_NUM];


    // cw 表示当前已经装进去的物品的重量和;i 表示考察到哪个物品了;
    // w 背包重量;items 表示每个物品的重量;n 表示物品个数
    // 假设背包可承受重量 100,物品个数 10,物品重量存储在数组 a 中,那可以这样调用函数:
    // f(0, 0, a, 10, 100)
    public void f(int i, int cw, int[] items, int n, int w) {
        // cw==w 表示装满了 ;
        // i==n 表示已经考察完所有的物品
        if (cw == w || i == n) {
            if (cw > maxW) {
                maxW = cw;
            }
            return;
        }
        //从最后一个物品装起
        f(i+1, cw, items, n, w);
        if (cw + items[i] <= w) {
            f(i+1,cw + items[i], items, n, w);
        }
    }


    public static void main(String [] args)
    {
       int   num = 5,  capacity = 10;
       int []  weight_list = new int[] {2, 2, 6, 5, 4};
       Bag b = new Bag();
       b.f(0,0,weight_list,num,capacity);
       System.out.println("最大可以放物品总重量:"+b.maxW);
    }
}

回溯算法理解起来比较简单,但是代码实现起来一是不容易想到,二是递归总觉得又点反人类思维,要多练习才能够理解。

你可能感兴趣的:(那些经典算法:如果可以重新选择)