回溯算法

递归算法切记不要使用debug的方式层层深入进去,这样会把自己绕进去,而且问题也得不到解决。还是要理解递归的思想,从思想的角度去理解代码。

回溯算法

  回溯算法类似于枚举搜索,枚举所有的解,找出满足期望的解.它把问题分为多个阶段,每个阶段都会面对一个岔路口,我们先随意一条路走,当发现这条路走不通的时候,就回退到上一个岔路口,另选一种走法继续走.
  举个例子,最近看了一部电视剧叫做《老酒馆》,主演陈宝国,扮演的主人公叫陈怀海,他为了给儿子报仇,去关东山一个叫做干饭盆的地方去找仇家,可惜到了树林迷路了,不管怎么走都回到同一个地方,于是他想了一个办法,在这个地方绑了一根绳子作为标记,然后随便找个方向寻找出口,如果方向不对,他一定会再回到这地方,然后换一条路走,这样子就找到了出口。
 回溯算法应用有很多,比如数独问题、八皇后问题、0-1背包问题、图的着色、旅行商问题、全排列等等。
  有的时候语言解释比较费劲,想搞清楚只有通过代码理解逻辑,反而语言更能说明清楚,这个时候就得研究下代码了
  回溯算法非常适合用递归来实现,在实现的过程中,剪枝操作是提高.回溯效率的一种技巧。利用剪枝,我们并不需要穷举搜索所有的情况,从而提高搜索效率。

八皇后问题

  八皇后问题是一个8×8的棋盘,往里边放入8颗棋子(皇后),每个棋子所在行、列、对角线都不能出现另一个皇后。8皇后问题就是为了找出所有满足这种要求的棋子放置方式。
  做法就是依次将8个棋子放到第一行、第二行、第三行…第八航。放置过程中,不停检查当前棋子的放置方法是否满足要求。如果满足则继续下一行放置下一个棋子,如果不满足就换一种方法继续尝试。

package com.study.algorithm.backtracking;

/**
 * 8皇后问题的解法
 * @author jeffSheng
 * @date 20191004
 *
 */
public class EightQueue {

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

	public void cal8queens(int row) { // 调用方式:cal8queens(0);
		if (row == 8) { // 8 个棋子都放置好了,打印结果
			printQueens(result);
			return; // 8 行棋子都放好了,已经没法再往下递归了,所以就 return
		}
		for (int column = 0; column < 8; ++column) { // 每一行都有 8 中放法
			if (isOk(row, column)) { // 有些放法不满足要求
				result[row] = column; // 第 row 行的棋子放到了 column 列
				cal8queens(row + 1); // 考察下一行
			}
		}
	}

	private boolean isOk(int row, int column) {// 判断 row 行 column 列放置是否合适
		int leftup = column - 1, rightup = column + 1;
		for (int i = row - 1; i >= 0; --i) { // 逐行往上考察每一行
			if (result[i] == column)
				return false; // 第 i 行的 column 列有棋子吗?
			if (leftup >= 0) { // 考察左上对角线:第 i 行 leftup 列有棋子吗?
				if (result[i] == leftup)
					return false;
			}
			if (rightup < 8) { // 考察右上对角线:第 i 行 rightup 列有棋子吗?
				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 eightQueue = new EightQueue();
		eightQueue.cal8queens(0);
	}
}

0-1背包问题

  0-1背包问题是非常经典的算法题,很多场景都可以抽象成这个问题模型。这个问题的经典解法是动态规划,不过还有一种方法但是没那么高效,就是回溯算法。0-1背包问题变体很多,这里是比较基础的一种,有一个背包,背包总承重X kg。我们有n个物品,每个物品重量不等,且整块不可分割。现在期望选择几件物品,装载到背包中,在不超过背包称重的前提下,如何让背包中的物品总重量最大?
  做法是,对于每种物品来说,都有两种选择,装进背包或者不装进背包。对于n个物品来说,总的装法有2^n次方种,出过去超过承重XKg的,从剩下的装法中选择总重量最接近Xkg的。这里用回溯的方法,可以把物品依次排列,整个问题分为n个阶段,每个阶段对应一个物品怎么选择。先对第一个物品进行处理,选择装进去或者不装进去,然后再递归处理剩下的物品。

package com.study.algorithm.backtracking;

/**
 * 回溯算法的应用  :0-1背包问题
 * @author jeffSmile
 * @date 20191004
 *
 */
public class Knapsack {
	public int maxW = Integer.MIN_VALUE; // 存储背包中物品总重量的最大值
	// 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) {
		if (cw == w || i == n) { // 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);
		}
	}

}

优化后的0-1背包问题:

package com.study.algorithm.backtracking;

/**
 * @Auther: JeffSheng
 * @Date: 2019/10/16 14:45
 * @Description: 回溯算法实现0-1背包问题
 * 在满足背包总重量w=9的前提下,背包中可以容纳的最大值是多少
 */
public class Knapsack {

    /**
     *  回溯算法实现。注意:我把输入的变量都定义成了成员变量。
     *     结果放到 maxW 中
     */
    private int maxW = Integer.MIN_VALUE;
    /**
     *  物品重量
     */
    private int[] weight = {2,2,4,6,3};
    /**
     *  物品个数
     */
    private int n =  5;
    /**
     * 背包承受的最大重量
     */
    private int w = 9;

    /**
     * 调用 f(0, 0)
     * @param i
     * @param cw
     */
    public void f(int i,int cw){
        //cw==w 表示装满了,i==n 表示物品都考察完了
        if(cw == w || i == n){
            if(cw > maxW){
                maxW = cw;
            }
            return;
        }
        // 选择不装第 i 个物品
        f(i+1,cw);
        // 选择装第 i 个物品
        if(cw + weight[i] <= w){
            f(i+1,cw + weight[i]);
        }
    }
    /**
     *  备忘录,默认值 false
      */
    private boolean[][] mem = new boolean[5][10];


    /**
     * 优化后的0-1背包问题,使用备忘录存储已经计算过的值,再次遇到直接取出来用
     *   调用 f(0, 0)
      * @return
     */

    public void f1(int i, int cw) {
        // cw==w 表示装满了,i==n 表示物品都考察完了
        if (cw == w || i == n) {
            if (cw > maxW){
                maxW = cw;
            }
            return;
        }
        // 重复状态
        if (mem[i][cw]){
            return;
        }
        // 记录 (i, cw) 这个状态
        mem[i][cw] = true;
        // 选择不装第 i 个物品
        f1(i+1, cw);
        // 选择装第 i 个物品
        if (cw + weight[i] <= w) {
            f1(i+1,cw + weight[i]);
        }
    }


    /**
     * 结果放到 maxV 中
     */
    private int maxV = Integer.MIN_VALUE;

    /**
     *  物品的价值
     */

    private int[] value = {3,4,8,9,6};

    /**
     *当把物品价值考虑在内时,使用回溯算法计算袋子承重限制条件下的物品最大总价值
     * 调用 f(0, 0, 0)
     * @param i 表示处理的是第几个物品
     * @param cw 表示袋子中物品的重量
     * @param cv 表示袋子中物品总价值
     */
    public void ff(int i, int cw, int cv) {
        // cw==w 表示装满了,i==n 表示物品都考察完了
        if (cw == w || i == n) {
            if (cv > maxV){
                maxV = cv;
            }
            return;
        }
        // 选择不装第 i 个物品
        ff(i+1, cw, cv);
        if (cw + weight[i] <= w) {
            // 选择装第 i 个物品
            ff(i+1,cw + weight[i], cv + value[i]);
        }
     }

    public static void main(String[] args) {
        Knapsack k = new Knapsack();
        k.f(0,0);
        System.out.println(k.maxW);
    }
}

正则表达式

  正则表达式中主要就是通配符。假设正则表达式中只有*和?两种通配符,并且这俩个通配符语义这样定义:*表示匹配任意多个任意字符,?匹配不大于1个任意字符,来判断给定一个文本,能否跟给定的正则表达式匹配?

package com.study.algorithm.backtracking;

/**
 * 自定义正则表达式的实现
 * 
 * @author jeffSmile
 * @date 20191004
 *
 */
public class Pattern {
	private boolean matched = false;
	private char[] pattern; // 正则表达式
	private int plen; // 正则表达式长度

	public Pattern(char[] pattern, int plen) {
		this.pattern = pattern;
		this.plen = plen;
	}

	public boolean match(char[] text, int tlen) { // 文本串及长度
		matched = false;
		rmatch(0, 0, text, tlen);
		return matched;
	}

	private void rmatch(int ti, int pj, char[] text, int tlen) {
		if (matched)
			return; // 如果已经匹配了,就不要继续递归了
		if (pj == plen) { // 正则表达式到结尾了
			if (ti == tlen)
				matched = true; // 文本串也到结尾了
			return;
		}
		if (pattern[pj] == '*') { // * 匹配任意个字符
			for (int k = 0; k <= tlen - ti; ++k) {
				rmatch(ti + k, pj + 1, text, tlen);
			}
		} else if (pattern[pj] == '?') { // ? 匹配 0 个或者 1 个字符
			rmatch(ti, pj + 1, text, tlen);
			rmatch(ti + 1, pj + 1, text, tlen);
		} else if (ti < tlen && pattern[pj] == text[ti]) { // 纯字符匹配才行
			rmatch(ti + 1, pj + 1, text, tlen);
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jeff.sheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值