小言_互联网的博客

算法设计与分析——回溯法

264人阅读  评论(0)

回溯法

简介

回溯法又称为试探法,实际上一个类似穷举的搜索尝试过程,主要是在搜索尝试过程中寻找向题的解,当发现已不满足求解条件时,就“回溯”(即回退),尝试别的路径。回溯法有“通用解题法”之称它适合于解一些 组合数 较大的最优化向题。

算法设计思想

回溯法从根结点出发,按照深度优先策略遍历解空间树,搜索满足约束条件的解。在搜索至树中任一结点时,先判断该结点对应的部分解 是否满足约束条件(约束函数),或者是否超出限界函数的界限(限界函数),也就是判断该结点是否可能包含问题的答案解,如果肯定不包含,则跳过对以该结点为根的子树的搜索,即所谓剪枝( Pruning);否则,进入以该结点为根的子树,继续按照深度优先策略搜索并进行判断。若用回湖法求问题的所有解时,需要回溯到根结点,且根结点的所有可行的子树都要被搜索完才结束。而若使用回湖法求任一个解时,只要搜索到向题的一个解就可以结束。

重要概念:

	 解空间:排列组合数的枚举
	 解空间树:
		 - 子集树,2的n次方个叶子节点(在集合中找某个符合条件的子集);
		 - 排列树,Amn个叶子节点(在确定n个数中找到符合条件的排列);

	解决:求任一解,求全部可行解,求最优解
	约束:显式约束(给定问题集合),隐式约束(目标求解条件)
	剪枝函数:
		 - 约束函数:用约束函数在扩展结点处剪除不满足约束的子树;
		 - 限界函数:用限界函数剪去得不到向题解或最优解的子树。   

子集树示例:

排列树示例:

解题步骤:

1、针对所给向题,确定问题的解空间树,问题的解空间树应至少包含问题的一个解就者最优解
2、确定结点的扩展搜索规则
3、以深度优先方式搜索解空间树,并在搜索过程中采用剪枝函数来避免无效搜索。
    其中,深度优先方式可以选择递归回溯或者迭代回溯

代码实例:

子集树参考代码,经典问题 0/1背包:
    有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?

/*0-1背包  子集树 */ 
void calV(int i,int tolw,int tolv){
    if(i>NUM){
        if (tolw <= WEIGHT && tolv > maxv) {
            maxw=tolw;
            maxv=tolv;
            System.arraycopy(x,0,res,0,x.length);
        }
        return;
    }
    if(tolw+tolW(i)>WEIGHT){ // 左剪枝 减去加上当前i小于等于WEIGHT的节点
        x[i]=0;
        calV(i+1,tolw,tolv);
    }
    if(tolw+w[i]<=WEIGHT){// 右剪枝 减去加上当前节点大于WEIGHT的节点
        x[i]=1;
        calV(i+1,tolw+w[i],tolv+v[i]);
    }
}

排列树参考代码:
    坐标中多个点,从出发点一次经过其他点然后回到出发点,要求路径最短,只能按坐标方格边走,求最短路径?

/*bool标识全遍历递归*/ 
private static void calculate(Point p, int count) {
    //判断停止条件,如果所有的点遍历完,则返回
    if (count == points.length) {
        minPath = tolLen + p.getLength(StartPoint);
    }
    //否则遍历其余的点并进行路径和的计算
    for (Point point : points) {
        //判断此点是否遍历过
        if (!point.isVisited) {
            //计算起始点到遍历点之间的距离,从而更新最小路径
            tolLen += point.getLength(p);
            //该点的标志位置为true
            point.isVisited = true;
            //若小于最小路径,则遍历此点继续下一层
            if (tolLen + point.getLength(StartPoint) < minPath) {
                //每遍历一个点,层次加1,起始点更改为遍历后的点,继续计算其余点
                calculate(point, count + 1);
            }
            //将路径和倒减,标志置为false,进行下一个方案的计算
            tolLen -= point.getLength(p);
            point.isVisited = false;
        }
    }
}

/** 排列树 实排列 上下交换递归
 * 将所有的点进行全排列,然后一次累加求tolLen,同时进行剪枝
 */
private static void calculate1(Point p,int i, int n) {
    if(i>=n){
        minPath=tolLen+p.getLength(StartPoint);
        //dispa();
        return;
    }
    // i 层的枚举
    for (int j = i; j < n; j++) {
        swap(i,j); // 交换实现排列元素不同
        tolLen+=p.getLength(points[i]); // 目标条件等处理
        if(tolLen+points[i].getLength(StartPoint)<minPath){ // 剪枝函数
            calculate1(points[i],i+1,n);
        }
        tolLen-=p.getLength(points[i]);
        swap(i,j); // 还原环境
    }
}

剪枝改进:
0/1背包问题

    左剪枝对于第层的有些结点,tw+[]已超过了M,显然再选择w[]是不合适的。如第2层的(5,4)结点,tM=5,M[2]=3,而tw+WM[2]>M,选择物品2进行扩展是不必要的,可以增加一个限界条件进行剪枝,如若选择物品i会导致超重即tw+W[i]>M,就不再扩展该结点,也就是仅仅扩展tM+W[]sM的左孩子结点。


    右剪枝用rw表示考虑第讠个物品时剩余物品的重量当不选择物品时,若tw+rw<M(注意w中包含[])时,也就是说即使选择后面的所有物品,重量也不会达到M,因此不必要再考虑扩展这样的结点,也就是说,对于右分枝仅仅扩展tw+rw>M的结点(注意不包含等于)如第2层的(0,0)结点,此时tw=8,rw=6(物品2、3、4的重量和),tw+rw=6,不大手于M(此时又不选择物品2),所以不必扩展其右孩子结点.


转载:https://blog.csdn.net/qq_41242233/article/details/105388230
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场