南邮算法分析与设计实验3 回溯法

回溯法

实验目的:

       学习编程实现深度优先搜索状态空间树求解实际问题的方法。着重体会求解第一个可行解和求解全部可行解之间的区别。

加深理解回溯法通过搜索状态空间树、同一时候用约束函数剪去不含答案状态子树的算法思想,会用蒙特卡罗方法预计算法实际生成的状态空间树的结点数。

                             

实验内容:

1、求24点问题

给定四个1-9之间的自然数,当中每一个数字仅仅能使用一次。用算术运算符+。-,*。/构造出一个表达式,将这4个正整数连接起来(能够使用括号),使终于的得数为24。要求依据问题的特征设计详细算法并编程实现,输入数据为4个自然数。

输出若有多个满足要求的表达式,则仅仅输出当中一组;若搜索失败。则输出“Fail!”。

【演示样例】採用一个表达式中用括号确定运算先后次序的方式,如:

输入1。5。5,5四个自然数,输出((5-(1/5))*5)。

输入3。3,8,8四个自然数。输出(8/(3-(8/3)))。

【測试数据】

(1) 1,5,5,5

(2) 3,3,8,8

(3) 3,8,8,8

(4) 1,2,3,4

(5) 2,4,5,6

(6) 4,2,2,5

(7) 1,2,2,6

(8) 4,2,8,8

(9) 0,3,8,8 


 2、n皇后问题

要求用回溯法求解8-皇后问题,使放置在8*8棋盘上的8个皇后彼此不受攻击。即:不论什么两个皇后都不在同一行、同一列或同一斜线上。请输出8皇后问题的全部可行解。


问题1:通过24点问题,体会用递归程序实现深度优先搜索的技巧。

因为回溯法通过搜索状态空间树求解,因此对输入的自然数与运算符(+。-,*,/)全部可能的组合均需进行检查。每一个表达式中包含4个自然数和3个运算符,怎样决定运算的先后顺序? 

假设每次先从4个数中取出两个数进行运算,然后把运算结果和第三个数进行运算。再把结果与第四个数进行运算。就能够轻易地决定这些数的运算次序,避免了对括号的处理。而且这样做。输出时非常easy通过为表达式逐层加上3层括号。唯一的确定终于结果表达式。

递归函数voidDFS(n)负责寻找可行解,当中n为本层调用的操作数个数。

每次两数运算后。原来的两个操作数被去除。运算结果成为新的操作数。因此总的操作数数量减1。

由于+和*满足交换率。而-和/不满足,因此程序中应当计算a+b。a*b,a-b。b-a,a/b,b/a几种情况。

因为此程序仅仅要求第一个可行解,因此求得第一个可行解后,即能够层层返回并输出结果,同一时候结束程序。

(也能够借助一个标识符found来标识是否已找到第一个可行解)

当递归调用至n==1仅仅剩一个操作数时。推断该操作数是否等于24。假设是,则搜索成功,输出表达式;否则,则程序自己主动回溯至上层。若DFS()函数调用完成后,仍没有满足要求的的表达式,则输出“Fail.”搜索失败。

用全局数组num[]来存放操作数。递归调用时在下一层递归调用前改变,在下一层递归调用返回后恢复。

详细处理过程:每次从当前num[]数组中的n个数中。选择两个数进行一次运算。将这两个数从数组中去除,同一时候使用局部变量c1, c2暂存它们的值。而将它们的运算结果写入到数组中。

接着对当前num[]数组中剩余的n-1个数进行下一层递归调用,在递归调用返回到本层调用点后。再用局部变量c1, c2 恢复整个数组到调用前的初始状态。


实验代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <string>
using namespace std;
double EPS = 1e-10;
double num[4];
bool flag;
int cnt;
string re[4];

bool equel(double a, double b)
{
    if(fabs(a - b) <= EPS)
        return true;
    return false;
}

void DFS(int n)
{
    if(n == 1 && equel(num[0], 24))
    {
        cnt ++;
        cout << re[0] << endl;
        exit(0);
    }
    for(int i = 0; i < n; i++)
    {
        for(int j = i + 1; j < n; j++)
        {
            double c1 = num[i], c2 = num[j];
            string re1, re2;
            num[j] = num[n - 1];
            num[i] = c1 + c2;
            re1 = re[i];
            re2 = re[j];
            re[j] = re[n - 1];
            re[i] = '(' + re1 + '+' + re2 + ')';
            DFS(n - 1);
            num[i] = c1 - c2;
            re[i] = '(' + re1 + '-' + re2 + ')';
            DFS(n - 1);
            num[i] = c2 - c1;
            re[i] = '(' + re2 + '-' + re1 + ')';
            DFS(n - 1);
            num[i] = c1 * c2;
            re[i] = '(' + re1 + '*' + re2 + ')';
            DFS(n - 1);
            if(!equel(c2, 0)) //judge denominator
            {
                num[i] = c1 / c2;
                re[i] = '(' + re1 + '/' + re2 + ')';
                DFS(n - 1);
            }
            if(!equel(c1, 0))
            {
                num[i] = c2 / c1;
                re[i] = '(' + re2 + '/' + re1 + ')';
                DFS(n - 1);
            }
            num[i] = c1;
            num[j] = c2;
            re[i] = re1;
            re[j] = re2;
        }
    }
}

int main()
{
    //consider the rational numbers, we define them as double
    scanf("%lf %lf %lf %lf", &num[0], &num[1], &num[2], &num[3]);
    re[0] = num[0] + '0';
    re[1] = num[1] + '0';
    re[2] = num[2] + '0';
    re[3] = num[3] + '0';
    cnt = 0;
    DFS(4);
    if(cnt == 0)
        printf("Fail!
");
   return 0;
}


測试数据:

(1) 1,5,5,5     ((5-(1/5))*5)

(2) 3,3,8,8     (8/(3-(8/3)))

(3) 3,8,8,8     (((3+8)-8)*8)

(4) 1,2,3,4     (((1+2)+3)*4)

(5) 2,4,5,6     (((2+4)*5)-6)

(6) 4,2,2,5     ((4*2)*(5-2))

(7) 1,2,2,6     ((1+2)*(6+2))

(8) 4,2,8,8     (((4-2)*8)+8)

(9) 0,3,8,8     (((0*8)+3)*8)



问题2:通过求解n-皇后问题,体会回溯法深度优先遍历状态空间树,并利用约束函数进行剪枝的算法思想。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
using namespace std;
int cnt = 0;

bool Place(int k, int i, int *x) 
{
   for(int j = 0; j < k; j++)
   if((x[j] == i) || (abs(x[j] - i) == abs(j - k))) 
       return false;
   return true;
}

void NQueens(int k, int n, int *x) 
{
    for(int i = 0; i < n; i++)
    {
        if(Place(k, i, x))
        {
            x[k] = i;
            if(k == n - 1)    
            {
                for(i = 0; i < n; i++) 
                  cout << x[i] << " ";
                cout << endl;
                cnt ++;
            }
            else
            {
               NQueens(k + 1, n, x);
            }
       }
    }
}
void NQueens(int n, int *x)
{
    NQueens(0, n, x);
}

int main()
{
    int x[8];
    cnt = 0;
    for(int i = 0; i < 8; i++) 
        x[i] = -1;
    NQueens(8, x);
    printf("Answers num: %d
", cnt);
    return 0;
}


本次实验的思考题太烦,就写了一个。将求解24点的表达式拆分:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <stack>
#include <string>
#include <cmath>
using namespace std;
double EPS = 1e-10;
double num[4];
bool flag;
int cnt;
string re[4];
string ans;

bool equel(double a, double b)
{
    if(fabs(a - b) <= EPS)
        return true;
    return false;
}

void DFS(int n)
{
    //if(flag)
    //    return;
    if(n == 1 && equel(num[0], 24))
    {
        cnt ++;
        cout << re[0] << endl;
        //ans = re[0];
        ///flag = true;
        return;
    }
    for(int i = 0; i < n; i++)
    {
        for(int j = i + 1; j < n; j++)
        {
            double c1 = num[i], c2 = num[j];
            string re1, re2;
            num[j] = num[n - 1];
            num[i] = c1 + c2;
            re1 = re[i];
            re2 = re[j];
            re[j] = re[n - 1];
            re[i] = '(' + re1 + '+' + re2 + ')';
            DFS(n - 1);
            num[i] = c1 - c2;
            re[i] = '(' + re1 + '-' + re2 + ')';
            DFS(n - 1);
            num[i] = c2 - c1;
            re[i] = '(' + re2 + '-' + re1 + ')';
            DFS(n - 1);
            num[i] = c1 * c2;
            re[i] = '(' + re1 + '*' + re2 + ')';
            DFS(n - 1);
            if(!equel(c2, 0)) //judge 
            {
                num[i] = c1 / c2;
                re[i] = '(' + re1 + '/' + re2 + ')';
                DFS(n - 1);
            }
            if(!equel(c1, 0))
            {
                num[i] = c2 / c1;
                re[i] = '(' + re2 + '/' + re1 + ')';
                DFS(n - 1);
            }
            num[i] = c1;
            num[j] = c2;
            re[i] = re1;
            re[j] = re2;
        }
    }
}

int main()
{
    //consider the rational numbers, we define them as double
    flag = false;
    scanf("%lf %lf %lf %lf", &num[0], &num[1], &num[2], &num[3]);
    re[0] = num[0] + '0';
    re[1] = num[1] + '0';
    re[2] = num[2] + '0';
    re[3] = num[3] + '0';
    cnt = 0;
    DFS(4);
    if(cnt == 0)
        printf("Fail!
");
    else
    {
        string o[10];
        stack <char> stk;
        int cnt1 = 0;
        int len = ans.length();
        cout << ans << endl;
        for(int i = 0; i < len; i++)
        {
            if((ans[i] != ')'))
                stk.push(ans[i]);
            else 
            {
                char tmp[100];
                int cnt2 = 0;
                char ch1, ch2;
                char tp;
                while(stk.top() != '(')
                {
                    tmp[cnt2 ++] = stk.top();
                    stk.pop();  
                }
                stk.pop();
                double num3;
                double num2 = 0;
                double num1 = 0;
                int le1 = 0;
                int le2 = 0;
                bool f = false;
                bool f1 = false;
                bool f2 = false;
                for(int i = cnt2 - 1; i >= 0; i--)
                {
                    o[cnt1] += tmp[i];
                    if(!f && tmp[i] != '+' && tmp[i] != '-' && tmp[i] != '*' && tmp[i] != '/')
                    {
                        if(f1)
                            le1 ++;
                        if(tmp[i] == '.')
                        {
                            f1 = true;
                            continue;
                        }
                        num1 = num1 * 10 + tmp[i] - '0';
                    }
                    else if(tmp[i] == '+' || tmp[i] == '-' || tmp[i] == '*' || tmp[i] == '/')
                    {
                        tp = tmp[i];
                        f = true;
                    }
                    else if(f)
                    {
                        if(f2)
                            le2 ++;
                        if(tmp[i] == '.')
                        {
                            f2 = true;
                            continue;
                        }
                        num2 = num2 * 10 + tmp[i] - '0';
                    }
                }
                num1 /= pow(10.0, le1);
                num2 /= pow(10.0, le2);
                // printf("num1 = %lf
", num1);
                // printf("num2 = %lf
", num2);
                if(tp == '+')
                {
                    num3 = num1 + num2;
                    char t4[10];
                    sprintf(t4, "%.6f", num3);
                    int le = strlen(t4);
                    for(int i = 0; i < le; i++)
                        stk.push(t4[i]);
                }
                else if(tp == '-')
                {
                    num3 = num1 - num2;
                    char t4[10];
                    sprintf(t4, "%.6f", num3);
                    int le = strlen(t4);
                    for(int i = 0; i < le; i++)
                        stk.push(t4[i]);
                }
                else if(tp == '*')
                {
                    num3 = num1 * num2;
                    char t4[10];
                    sprintf(t4, "%.6f", num3);
                    int le = strlen(t4);
                    for(int i = 0; i < le; i++)
                        stk.push(t4[i]);
                }
                else if(tp == '/')
                {
                    num3 = num1 / num2;
                    char t4[10];
                    sprintf(t4, "%.6f", num3);
                    int le = strlen(t4);
                    for(int i = 0; i < le; i++)
                        stk.push(t4[i]);
                }
                o[cnt1] += '=';
                char t4[10];
                sprintf(t4, "%.6f", num3);
                o[cnt1] += t4;
                cnt1 ++;
            }
        }
        for(int i = 0; i < cnt1; i++)
            cout << o[i] << endl;4
        
    }
}



回溯法实例:Sticks

Description

Georgetook sticks of the same length and cut them randomly until all parts became atmost 50 units long. Now he wants to return sticks to the original state, but heforgot how many sticks he had originally and how long they were originally.Please help him and design a program which computes the smallest possibleoriginal length of those sticks. All lengths expressed in units are integersgreater than zero.

Input

Theinput contains blocks of 2 lines. The first line contains the number of sticksparts after cutting, there are at most 64 sticks. The second line contains thelengths of those parts separated by the space. The last line of the filecontains zero.

Output

Theoutput should contains the smallest possible length of original sticks, one perline.

Sample Input

9 5 2 1 5 2 1 5 2 1 4 1 2 3 4 0

Sample Output

6 5

Source

Central Europe 1995

题目大意:给若干根切断的木条,用完所有所给木条拼成n根等长的原始木条,求所拼原始木条的最短长度

题目分析:回溯法 + 剪枝 ,本题限制时间为1000ms,剪枝不到位绝对会超时,以下列出几条不可少的剪枝
1. 设有n根木条枚举能拼成的长度为sum/n~1。这里n必须是sum的约数否则无解,找到了则为最优解。由于均分的越多长度就越短
2.按长度从大到小排序,最长的一根长度必定小于sum/n,否则无解。从长的開始取,由于长的灵活度比較低,之后便于剪枝。
3.若搜索时某两根的长度同样,第一根没取那第二根也不会取
4.每次取的木条加上取之前的长度要小于sum/n
5.假设当前长度cur为0,当前取的最长木条为stick[i],结果stick[i]拼接失败则我们能够直接退出搜索,当前情况无解,由于假设某次当前 最长的木条没被选上,则之后木条数更少了它更不会被选上,由于我们是依照降序排列的


#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const MAX = 64;
int vis[MAX];
int stick[MAX];
int num, one, div_num;

bool cmp(int a, int b) 
{
    return a > b;
}
//cur代表当前某根木条的拼接长度,other代表未用木条。count代表拼成的根数
bool DFS(int cur, int other, int count)
{
    if(count == div_num) //若拼成的根叔等于份数,则找到返回
        return true;
    for(int i = other; i < num; i++)
    {
        //若当前供选长度与前一个相等且前一个未使用则不考虑当前这根
        if(i && !vis[i-1] && stick[i] == stick[i-1])
            continue;
        if(cur + stick[i] <= one && !vis[i])
        {
            //若加上小于原始长度将其标记为已使用继续搜索
            if(cur + stick[i] < one)
            {
                vis[i] = 1;
                if(DFS(cur + stick[i], i + 1, count))
                    return true;
                //若之前方案不可行。将当前这根标记为未使用
                vis[i] = 0;
                //这是个非常重要的剪枝。若当前选择的最长长度得不到解,该方案必定无解
                if(cur == 0)
                    return false;
            }
            //若加上等于原始长度将其标记为已使用拼成一根,開始拼下一根
            if(cur + stick[i] == one)
            {
                vis[i] = 1;
                if(DFS(0, 0, count + 1))
                    return true;
                //若之前方案不可行,将当前这根标记为未使用
                vis[i] = 0;
                //这是个非常重要的剪枝,若之前的DFS(0,0,count+1)有一个不满足
                //则该方案必定无解
                return false;
            }
        }
    }
    return false;
}

int main()
{
    int  sum;
    while(scanf("%d",&num) != EOF && num)
    {
        sum = 0;
        memset(vis,0,sizeof(vis));
        for(int i = 0; i < num; i++)
        {
            scanf("%d",&stick[i]);
            sum += stick[i];
        }
        sort(stick, stick+num, cmp);  //从大到小排序
         //从份数最多的枚举起,则找到一组解就能够退出。由于它必为最短
        for(int i = num; i > 0; i--)
        {
            if(sum % i == 0)   //每根原始木条的长度必须是整数
            {
                one = sum / i;  //一根原始木条的长度
                //若当前的原始木条短于最长的切断的木条长度则原始木条值非法
                if(stick[0] > one) 
                    continue;
                else
                {
                    div_num = i; //份数
                    if(DFS(0,0,0)) //找到一个可行解就退出
                        break;
                }
            }
        }
        printf("%d
",one);
    }
}



原文地址:https://www.cnblogs.com/clnchanpin/p/6800215.html