算法设计与分析(CHA5回溯代码复现)


前言

王晓东<计算机算法设计与分析>第五章回溯代码复现
包含内容:
回溯法算法框架;装载问题;批处理作业调度;符号三角形问题;n后问题;0-1背包问题;最大团问题;图的m着色问题;旅行售货员问题;圆排列问题

如果可以在OJ中找到题目,则OJ和题目编号在注释中给出(如HDU2510表示杭电OJ2510号题目)。
如果不能在OJ中找到题目,则自拟了输入输出(如批处理作业调度)或给出核心求解代码(如圆排列问题)

均使用回溯求解的方法求解。

内容可供大家参考,如有问题希望不吝赐教!


一、回溯法的算法框架

// 递归回溯框架
void BackTrack(int t)
{
    if (t > n)
    {
        // 搜索完了输出答案
        Output(x);
    }
    else
    {
        for (int i = f(n, t); i <= g(n, t); i++)
        {
            // f(n, t) ~ g(n, t)当前扩展结点为搜索过的子树
            // h(i)表示当前扩展结点的第i个可选值
            x[t] = h(i);
            if (Constraint(t) && Bound(t))
            {
                // 满足约束函数和边界函数
                BackTrack(t + 1);
            }
        }
    }
}

// 迭代回溯框架
void InterativeBacktract()
{
    int t = 1;
    while (t > 0)
    {
        if (f(n, t) <= g(n, t))
        {
            // 当前扩展结点是否还有未搜索过的子树
            for (int i = f(n, t); i <= g(n, t); i++)
            {
                // 循环遍历当前结点还未搜索过的子树
                x[t] = i;
                if (Constraint(t) && Bound(t))
                {
                    if (Solution(t)) Output(x);
                    else t++;
                }
            }
        }
        else
        {
            // 当前扩展结点没有未搜索过的子树,所以回溯
            t--;
        }
    }
}
// 子集树
void Backtrack(int t)
{
    if (t > n)
    {
        Output(x);
    }
    else
    {
        for (int i = 0;i <= 1;i++)
        {
            x[t] = i;
            if (Constraint(t) && Bound(t))
            {
                Backtrack(t + 1);
            }
        }
    }
}

// 排列树
void Backtrack(int t)
{
    if (t > n)
    {
        Output(x);
    }
    else
    {
        for (int i = t;i <= n;i++)
        {
            // 与当前扩展点之后点交换
            Swap(x[t], x[i]);
            if (Constraint(t) && Bound(t))
            {
                Backtrack(t + 1);
            }
            Swap(x[t], x[i]);
        }
    }
    
}

二、各题目

1.装载问题

/* *************** Loading Problem - BackTrack ************** 
*@Author:     Pihai Sun                                     *
*@University: Qingdao University                            *
*@College:    College of Computer Science and Technology    *
*************************************************************/

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;

typedef long long ll;
const int N = 100;

void scanData();
bool canSolve();
void backTrack(int dep);
void printAns();

// 集装箱数、货船1、2载重, 各集装箱重
int n, c1, c2, w[N];
// 当前最优载重, 最重集装箱重, 集装箱总重
int bestw, maxw, totalw;
int cw, remain;
//  当前最优装载策略, 当前正在搜索的装载策略
bool bestStrategy[N], nowStrategy[N];

int main()
{

    scanData(); // 输入数据
    if (!canSolve())
    {
        // 不可解直接输出
        puts("没有可行解!");
    }
    else
    {
        backTrack(1);
        printAns();
    }

    system("pause");
    return 0;
}

void scanData()
{
    cout << "货船载重: ";
    cin >> c1 >> c2;
    cout << "集装箱数: ";
    cin >> n;
    cout << "各集装箱重: ";
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i];
        maxw = max(maxw, w[i]);
        totalw += w[i];
    }

    // remain表示还未讨论的货物总重量,初值为totalw
    remain = totalw;
}

bool canSolve()
{
    // 当集装箱总重小于货船总容量 且
    // 最大货船容量大于最大集装箱容量时才有解
    return totalw <= c1 + c2 && max(c1, c2) >= maxw;
}

void backTrack(int dep)
{
    if (dep > n)
    {
        // 到达边界,更新答案
        bestw = cw;
        memcpy(bestStrategy, nowStrategy, sizeof(bool) * (n + 1));
        return;
    }

    remain -= w[dep];
    if (cw + w[dep] <= c1)
    {
        // 可行性剪枝
        nowStrategy[dep] = true;
        cw += w[dep];
        backTrack(dep + 1);
        cw -= w[dep];
    }

    if (cw + remain > bestw)
    {
        // 最优性剪枝
        nowStrategy[dep] = false;
        backTrack(dep + 1);
    }
    remain += w[dep];
}

void printAns()
{
    if (totalw - bestw > c2)
    {
        puts("没有可行解!");
    }
    else
    {
        printf("货船1载重: ");
        for (int i = 1; i <= n; i++)
        {
            if (bestStrategy[i])
            {
                cout << w[i] << " ";
            }
        }
        puts("");

        printf("货船2载重: ");
        for (int i = 1; i <= n; i++)
        {
            if (!bestStrategy[i])
            {
                cout << w[i] << " ";
            }
        }
        puts("");
    }
}

2.批处理作业调度

/* *************** 批处理作业调度 - BackTrack ************** 
*@Author:     Pihai Sun                                   *
*@University: Qingdao University                          *
*@College:    College of Computer Science and Technology  *
**********************************************************/

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;

const int N = 100;
const int INF = 0x3f3f3f3f;

// 机器1完成处理的时间, 机器2完成处理的时间, 总耗费时间
int f1, f2[N], f;
// 当前最优解,当前最优调度
int bestf, bestx[N];
// 作业数, 各作业所需时间表, 当前作业调度
int n, M[N][2], x[N];

void Backtrack(int dep);

int main()
{
    // 输入数据
    cout << "作业数: ";
    cin >> n;
    cout << "各作业所需机器1、2处理时间(一行两个用空格间隔的数字)\n";
    for (int i = 1; i <= n; i++)
    {
        cin >> M[i][0] >> M[i][1];
    }

    // 初始化数据
    bestf = INF;
    for (int i = 1; i <= n; i++)
        x[i] = i;
    
    Backtrack(1);

    // 打印答案
    printf("最少耗费时间为: %d\n", bestf);
    puts("最优调度为:");
    for (int i = 1; i <= n; i++)
    {
        printf("%d ", bestx[i]);
    }
    puts("");

    system("pause");
    return 0;
}

void Backtrack(int dep)
{
    if (dep > n)
    {
        // 到达边界,更新答案
        bestf = f;
        memcpy(bestx, x, sizeof(int) * (n + 1));
        return;
    }

    for (int i = dep; i <= n; i++)
    {
        // 第dep个作业是x[i]的情况

        // 上一个作业使用完机器1, 当前作业即可使用机器2
        f1 += M[x[i]][0];
        // 上一个作业用完机器2(f2[dep - 1])
        // 且当前作业且当前作业接受完机器1的处理(f1), 才可使用机器2
        f2[dep] = (f2[dep - 1] > f1 ? f2[dep - 1] : f1) + M[x[i]][1];
        // 当前作业结束的标志是: 当前作业接受完机器2的处理(f2[dep])
        f += f2[dep];

        if (f < bestf)
        {
            // 最优性剪枝, 结果不可能更优
            swap(x[dep], x[i]);
            Backtrack(dep + 1);
            swap(x[dep], x[i]);
        }
        
        // 还原现场
        f1 -= M[x[i]][0];
        f -= f2[dep];
    }
}

3.符号三角形问题

/* *********** Symbolic Triangle(HDU2510) - BackTrack ********** 
*@Author:     Pihai Sun                                        *
*@University: Qingdao University                               *
*@College:    College of Computer Science and Technology       *
***************************************************************/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 30;

int main()
{
    int n;
    int ans[N] = {0, 0, 0, 4, 6, 0, 0, 12, 40, 0, 0, 171, 410, 0, 0,
                  1896, 5160, 0, 0, 32757, 59984, 0, 0, 431095, 822229};
    while (scanf("%d", &n) && n)
    {
        printf("%d %d\n", n, ans[n]);
    }
    system("pause");
    return 0;
}

// *计算打表数据的求解代码
/*
// 记录符号三角形
bool p[N][N];
// 层数, 总符号数的一半(加减符号目标数量), 当前1(负号)的数量
int n, half, countt;
// 当前方案书, 各层答案
int sum, ans[N];

void BackTrack(int t)
{
    // 到此-个数或+个数已超过目标个数(half),可行性剪枝
    if (countt > half || (t * (t - 1) / 2 - countt > half))
        return;
    // 到达边界,更新sum
    if (t > n)
    {
        sum++;
        return;
    }

    for (int i = 0; i < 2; i++)
    {
        // 第一层最右边(第t个)可能增加-或+
        p[1][t] = i;
        countt += i;

        for (int j = 2; j <= t; j++)
        {
            // 向2 ~ t层增加最右边一行(第t - j + 1个)
            // 其值是上方([j - 1, t - j + 1]符号和右上[j - 1, t - j + 2]符号的异或
            p[j][t - j + 1] = p[j - 1][t - j + 1] ^ p[j - 1][t - j + 2];
            countt += p[j][t - j + 1];
        }

        // 下一层
        BackTrack(t + 1);
        // 还原countt的计数
        for (int j = 2; j <= t; j++)
            countt -= p[j][t - j + 1];
        countt -= i;
    }
}

int main()
{
    for (n = 1; n <= 24; n++)
    {
        countt = 0;
        sum = 0;
        if (n * (n + 1) / 2 % 2)
        {
            // 总符号数为奇数,肯定不会满足条件
            ans[n] = 0;
            continue;
        }

        half = n * (n + 1) / 4;
        BackTrack(1);
        ans[n] = sum;
    }

    for (int i = 0; i <= 24; i++)
    {
        printf("%d, ", ans[i]);
    }
    system("pause");
    return 0;
}
*/

4. n后问题

/* ************* N Queen (HDU2553) - BackTrack ************ 
*@Author:     Pihai Sun                                   *
*@University: Qingdao University                          *
*@College:    College of Computer Science and Technology  *
**********************************************************/

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N = 15;

bool Place(int k);
void BackTrack1(int t); // n叉树
void BackTrack2(int t); // 排列树

// 棋盘边长, 当前解数, 记录方案, 搜过的答案
int n, sum, x[N], ans[N];
// 该答案是否被搜过
bool f[N];

int main()
{
    while (scanf("%d", &n) && n)
    {
        if (!f[n])
        {
            // 没求解过的答案才去求解
            sum = 0;
            // 如果使用排列树求法需要初始化x[]
            BackTrack1(1);
            // 记录答案并标记
            ans[n] = sum;
            f[n] = true;
        }

        printf("%d\n", ans[n]);
    }
    system("pause");
    return 0;
}

bool Place(int k)
{
    for (int j = 1; j < k; j++)
    {
        if (abs(k - j) == abs(x[j] - x[k]) || x[j] == x[k])
            return false;
    }

    return true;
}

void BackTrack1(int t)
{
    // * 解空间结构为n叉树
    if (t > n)
    {
        sum++;
        return;
    }

    for (int i = 1; i <= n; i++)
    {
        x[t] = i;
        if (Place(t))
            BackTrack1(t + 1);
    }
}

void BackTrack2(int t)
{
    // * 排列树解法
    // 使用排列树求解前,需要将x[]初始化为x[i] = i
    if (t > n)
    {
        sum++;
        return;
    }

    for (int i = t; i <= n; i++)
    {
        swap(x[t], x[i]);
        if (Place(t))
            BackTrack2(t + 1);
        swap(x[t], x[i]);
    }
}

5. 0-1背包问题

/* ************ Zero-one Pack(P1048) - BackTrack ************ 
*@Author:     Pihai Sun                                     *
*@University: Qingdao University                            *
*@College:    College of Computer Science and Technology    *
*************************************************************/

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N = 150;

struct node
{
    // 物品价值, 物品重量
    int v, w;
};

bool cmp(const node &x, const node &y)
{
    // 物品单位重量价值递减
    return x.v * 1.0 / x.w > y.v * 1.0 / y.w;
}

double bound(int i);
void Backtrack(int i);

// 物品数, 背包容量, 当前最优解
int n, c, bestp;
// 背包剩余容量, 当前存放物品价值
int cw, cp;
// 物品信息
node obj[N];

int main()
{
    // 输入
    cin >> c >> n;
    for (int i = 1; i <= n; i++)
        cin >> obj[i].w >> obj[i].v;

    // 搜索顺序优化, 先放单位价值高的
    sort(obj + 1, obj + 1 + n, cmp);

    Backtrack(1);
    
    cout << bestp;
    system("pause");
    return 0;
}

double bound(int i)
{
    // 按照优先存放单位价值大的原则填满剩余空间
    // 这样得到的最优解大于等于且近似于实际最优解
    int leftw = c - cw;
    int b = cp;

    while (i <= n && obj[i].w <= leftw)
    {
        // 按照单位价值递减顺序填满剩余空间
        // 当所有物品放完, 或下一个物品重量超出剩余空间跳出循环
        leftw -= obj[i].w;
        b += obj[i].v;
        i++;
    }

    if (i <= n)
    {
        // 如果背包还有空间, 填满缝隙
        // (实际上不可以, 在此用于产生大于等于且近似于实际最有解)
        b += obj[i].v * 1.0 / obj[i].w * leftw;
    }
    return b;
}

void Backtrack(int i)
{
    if (i > n)
    {
        // 到达边界, 更新答案
        bestp = cp;
        return;
    }

    if (cw + obj[i].w <= c)
    {
        // 可行性优化: 放入不超重
        cw += obj[i].w;
        cp += obj[i].v;
        Backtrack(i + 1);
        cw -= obj[i].w;
        cp -= obj[i].v;
    }

    // 最优性优化: 不放当前物品, 也有更优的可能性
    if (bestp < bound(i + 1))
        Backtrack(i + 1);
}

6. 最大团问题

/* *********** Maximum Clique(HDU1530) - BackTrack ********** 
*@Author:     Pihai Sun                                     *
*@University: Qingdao University                            *
*@College:    College of Computer Science and Technology    *
*************************************************************/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N = 100;

void scanData();
void BackTrack(int dep);
bool check(int num);

// 图中结点数, 图的邻接矩阵, 最大团结点数, 当前结点数
int n, a[N][N], bestn, cn;
// 最大团, 当前正在讨论中的最大团
bool bestx[N], x[N];

int main()
{
    while (scanf("%d", &n) && n)
    {
        scanData();
        BackTrack(1);
        printf("%d\n", bestn);
    }

    system("pause");
    return 0;
}

void scanData()
{
    bestn = 0;
    cn = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            scanf("%d", &a[i][j]);
        }
    }
}

void BackTrack(int dep)
{
    if (dep > n)
    {
        memcpy(bestx, x, sizeof(bool) * (n + 1));
        bestn = cn;
        return;
    }

    bool ok = true; // 可行性剪枝标记
    for (int i = 1; i < dep; i++)
    {
        if (x[i] && !a[dep][i])
        {
            ok = false;
            break;
        }
    }

    if (ok)
    {
        x[dep] = true;
        cn++;
        BackTrack(dep + 1);
        cn--;
        x[dep] = false;
    }

    if (cn + n - dep > bestn)
    {
        x[dep] = false;
        BackTrack(dep + 1);
    }
}

7. 图的m着色问题

/* ********** Coloring Problem(P2819) - BackTrack *********** 
*@Author:     Pihai Sun                                     *
*@University: Qingdao University                            *
*@College:    College of Computer Science and Technology    *
*************************************************************/

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N = 100;

void scanData();
bool check(int col);
void BakcTrack(int dep);

// 结点数, 边数, 方案数, 染色方案, 颜色数
int n, k, sum, color[N], m;
// 图的邻接矩阵
bool g[N][N];

int main()
{
    scanData();
    BakcTrack(1);
    cout << sum << endl;
    system("pause");
    return 0;
}

void scanData()
{
    cin >> n >> k >> m;
    for (int i = 1; i <= k; i++)
    {
        int s, e;
        cin >> s >> e;
        g[s][e] = true;
        g[e][s] = true;
    }
}

bool check(int dep)
{
    // 检查当前染色是否合法
    for (int i = 1; i < dep; i++)
    {
        // 与相邻点颜色相同,返回false
        if (g[dep][i] && color[dep] == color[i])
            return false;
    }
    return true;
}

void BakcTrack(int dep)
{
    if (dep > n)
    {
        // 到达边界,方案数++
        sum++;
        return;
    }

    for (int i = 1; i <= m; i++)
    {
        color[dep] = i;
        if (check(dep))
        {
            // 当前颜色合法才继续
            BakcTrack(dep + 1);
        }
    }
}

8. 旅行售货商问题

/* ******************** TSP - BackTrack *********************
*@Author:     Pihai Sun                                     *
*@University: Qingdao University                            *
*@College:    College of Computer Science and Technology    *
************************************************************/

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N = 100;
const int INF = 0x3f3f3f3f;

// 结点数, 当前策略, 图, 当前花费
int n, x[N], g[N][N], cc;
// 最优花费, 最优策略
int bestc = INF, bestx[N];

int main()
{
    // Get_Data
    // BackTrack(1)
    // Print_Ans
    system("pause");
    return 0;
}

void BackTrack(int t)
{
    if (t == n)
    {
        // 递归边界
        // 最后一个结点不需选择,剩下的那个就是,但需要满足可行性和最优性
        // 可行性:最优一个结点x[n]与前一个结点x[n - 1]和第一个结点x[1]都有路
        // 最优性:cc + dis(n-1, n) + dis(n, 1) < bestc
        if (g[x[n - 1]][x[n]] && g[x[n]][1] && cc + g[x[n - 1]][x[n]] + g[x[n]][1] < bestc)
        {
            // refresh ans
            memcpy(bestx, x, sizeof(int) * (n + 1));
            bestc = cc + cc + g[x[n - 1]][x[n]] + g[x[n]][1];
        }
    }
    else
    {
        for (int i = t; i <= n;i++)
        {
            // 上一个结点到当前结点有路且满足最优性剪枝
            // 排列树
            if (g[x[t - 1]][x[i]] && (cc + g[x[t - 1]][x[i]]) < bestc)
            {
                swap(x[t], x[i]);
                cc += g[x[t - 1]][x[t]];
                BackTrack(t + 1);
                cc -= g[x[t - 1]][x[i]];
                swap(x[t], x[i]);
            }
        }
    }
}

9. 圆排列问题

/* ************ Circle Arrangement - BackTrack **************
*@Author:     Pihai Sun                                     *
*@University: Qingdao University                            *
*@College:    College of Computer Science and Technology    *
************************************************************/

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long ll;
const int N = 1000;
void BackTrack(int t);
float Center(int t);
void Compute();

// 圆的总数
int n;
// 当前圆排列圆心横坐标, 当前圆排列
float x[N], r[N];
float minans;

int main()
{
    // Get_Data
    // BackTrack(1)
    // Print_Ans
    system("pause");
    return 0;
}

void BackTrack(int t)
{
    if (t > n)
    {
        Compute();
        return;
    }

    for (int i = t; i <= n; i++)
    {
        swap(r[t], r[i]);
        float centerx = Center(t);
        if (centerx + r[t] + r[1] < minans)
        {
            x[t] = centerx;
            BackTrack(t + 1);
        }
        swap(r[t], r[i]);
    }
}

float Center(int t)
{
    // 计算圆在当前圆排列中的横坐标
    float valuex, temp = 0;
    for (int j = 1; j < t; j++)
    {
        valuex = x[j] + 2.0 * sqrt(r[t] * r[j]);
        temp = max(valuex, temp);
    }
    return temp;
}

void Compute()
{
    // 计算当前圆排列的长度
    float low, high;
    low = high = 0;
    for (int i = 1; i <= n; i++)
    {
        low = min(low, x[i] - r[i]);
        high = max(high, x[i] + r[i]);
    }

    minans = min(minans, high - low);
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值