• 第五章-回溯法


    首先,溯字:(su:4声)。

    所有代码中的i是指:解向量的层数。

    学习要点:

    1. 回溯法概述
    2. 典型示例
    3. 效率分析

    1.回溯法概述

    问题的解空间、两类典型的解空间

    解向量:问题的解可以表示成n元(x1, x2, ..., xn)的解向量形式。

    而解空间是指:由于xi的不同取值,所有解向量组成的集合。解空间树:若xi有Si种取值,则最后一层(第n层)包含S1*S2*...*Sn个叶结点——和解空间一样,表示问题的所有可能解。

    可行解:解空间中,满足约束条件(显约束、隐约束)的解向量(我们应该寻求最优解)。其中显约束:即每个xi的取值范围。隐约束:即每个分量之间的关系。

     搜索空间:根据约束条件的不同,可分为回溯法、分支限界法。

    解空间描述清楚了,下面是两类典型的解空间:

    一般在路径问题、连通性问题、可平面性检验、着色问题和网络优化问题中应用。

    子集树空间:

    排列数空间:

    回溯法的基本思想、算法框架

    典型示例:

    nQueen算法:

    解向量:(x1, ..., xn),其中xi是棋盘的每一行(i)放置皇后的列号

    约束条件:显约束:1 <= xi <= n

         隐约束:1、xi != xj同一列排除(同一行已经排除掉);2、同斜向|i - j| != |xi - xj|

    给出代码:

    #include <iostream>
    #include <math.h>
    using namespace std;
    
    bool check(int k, int *x)
    {
    	int i = 1;
    	while (i < k)
    	{
    		if (x[i] == x[k] || abs(x[i] - x[k]) == abs(i - k))
    			return false;
    		i++;
    	}
    	return true;
    }
    
    void nQueen(int n, int *x)
    {
    	int k = 1;	//当前行号
    	x[1] = 0;
    	while (k > 0)
    	{
    		x[k] = x[k] + 1;
    		while ((x[k] <= n) && !check(k, x))
    			x[k] = x[k] + 1;
    		if (x[k] <= n)
    			if (k == n)
    			{
    				for (int i = 1; i <= n; i++)
    					cout << x[i] << " ";
    				cout << "
    ";
    			}
    			else  //下一行
    			{
    				k = k + 1;
    				x[k] = 0;
    			}
    		else  //回溯
    			k = k - 1;
    	}
    }
    
    int main()
    {
    	int n = 4, *x = new int[n];
    	nQueen(n, x);
    	return 0;
    }

    图的m着色问题

    解向量:(x1, x2, ... , xn),其中m种颜色用1...m表示,所以xi的值是每一个顶点的颜色。

    解空间:高度为n+1的满m叉树。

    搜索空间:由算法决定——由我决定。

    子集树问题

    解向量的xi为{0,1},就是子集树问题。遍历子集树需要O(2n)次计算时间。

    经典算法:

    1. 子集和问题
    2. 0-1背包问题
    3. 符号三角形问题

    子集和问题:

    问题实例为<S, t>,其中S={x1, x2, ..., xn}是一个正整数的集合,而c是一个正整数——>而该问题就是要判定是否存在S的一个子集S1,使得∑xS1 x = c。

    子集和问题的回溯法:

    解向量:(

    下面给出代码:

    #include <iostream>
    using namespace std;
    
    int n, C;
    int *S, *x;
    
    bool check(int i)
    {
        long sum = 0;
        for (int j = 0; j < n; j++)
            sum += S[j] * x[j];
        if (sum != C)
            return false;
        return true;
    }
    
    void Backtrace(int i)
    {
        if (i == n)
        {
            if (check(i))
            {
                for (int j = 0; j < n; j++)
                    if (x[j])
                        cout << S[j] << " ";
                cout << endl;
            }
        }
        else
        {
            for (int j = 0; j <= 1; j++)
            {
                x[i] = j;
                Backtrace(i + 1);
            }
        }
    }
    
    int main()
    {
        cin >> n >> C;
        S = new int[n];
        x = new int[n];
        for (int i = 0; i < n; i++)
            cin >> S[i];
        Backtrace(0);
        return 0;
    }
    /*测试数据:
    输入:
    5 10
    2 2 6 5 4
    输出:
    6 4
    2 2 6
    */

    0-1背包问题:

    下面给出代码:

    #include <iostream>
    using namespace std;
    
    int n, carry, carryWeight = 0, carryPrice = 0, bestValue = 0;    //背包容量,当前重量,当前价值,获得的最大价值
    int *price, *weight, *x;    //选或不选
    int *bestX;
    
    void OutPut()
    {
        for (int j = 0; j < n; j++)
            cout << bestX[j];
        cout << endl;
        cout << bestValue << endl;
    }
    
    int boundary(int i)        //判断这条路以后的能达到的最大价值,如果超过bestValue,继续走才有可能真的超过bestValue(起到了边界的作用)
    {
        int tempW = carryWeight + x[i] * weight[i];
        int tempP = carryPrice + x[i] * price[i];
        int carryLeft = carry - tempW;
        int maxValue = tempP;
        int j = 0;
        for (j = i + 1; j < n && weight[j] <= carryLeft; j++)
        {
            maxValue += price[j];
            carryLeft -= weight[j];
        }
        if (j < n - 1)        //如果某一步实在装不下了,就强行地按照比例装满该物品,能装就装(不用考虑其他物品,因为预估和实际差不多)
            maxValue += price[j] * carryLeft / weight[i];
        return maxValue;
    }
    
    bool check(int i)
    {
        if (carryWeight + x[i] * weight[i] > carry || boundary(i) <= bestValue)    //!(先看看当前是否可以装&&如果可以,就从当前装完,看是否可用)
            return false;
        return true;
    }
    
    void Backtrace(int i)        //第i个物品,当前携带的物品重量,物品价值
    {
        if (i == n)
        {
            if (carryPrice > bestValue)
            {
                bestValue = carryPrice;
                for (int j = 0; j < n; j++)
                    bestX[j] = x[j];
            }
        }
        else
        {
            for (int j = 0; j <= 1; j++)
            {
                x[i] = j;
                if (check(i))
                {
                    carryWeight += weight[i] * x[i];
                    carryPrice += price[i] * x[i];
                    Backtrace(i + 1);
                    carryWeight -= weight[i] * x[i];
                    carryPrice -= price[i] * x[i];
                }
            }
        }
    }
    
    int main()
    {
        cin >> n >> carry;
        price = new int[n], weight = new int[n];
        x = new int[n], bestX = new int[n];
        for (int i = 0; i < n; i++)
            cin >> price[i];
        for (int i = 0; i < n; i++)
            cin >> weight[i];
        Backtrace(0);
        OutPut();
        return 0;
    }
    /*测试数据:
    输入:
    5 10
    6 3 5 4 6
    2 2 6 5 4
    输出:
    1 1 0 0 1
    15
    */

     符号三角形问题:

    问题描述:

    有这样的一个三角形:它是由一定数量的符号,"+"和"-"组成的一个倒三角形。其组成规则是同号下面是"+",而异号下面是"-"(根据这样的规定,只要给出第一行,就可以画出整个三角形)。最后得到的三角形的"+"和"-"个数相同。我们要解决的问题是:给定第一行符号的个数,求出有多少个符号三角形。

    问题分析:

    代码:

  • 相关阅读:
    第63天python学习异常
    第62天python 学习TCP三次握手四次挥手详解
    文件操作
    内置函数
    函数递归
    函数补充
    函数
    购物车程序作业
    集合内置函数
    字典三级菜单
  • 原文地址:https://www.cnblogs.com/quanxi/p/5998266.html
Copyright © 2020-2023  润新知