算法2022 · 简单回溯

旅行の售货员

//售货员の周游路线   P122
//从1出发,各点走一遍后回到起点
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
#define maxx 0x3f3f3f
int c, e;          //【城市个数】【道路数量】
int w1[100], way1; //当前路径  当前总路程
int w2[100], best; //最佳路径  最小总路程
int G[100][100];   //图【出发点】【目标点】
void init()
{
    way1 = 0;
    best = maxx;
    for (int k = 1; k <= c; k++)
        w1[k] = k;
}
void get()
{
    int p1, p2, l; //点1 点2 距离
    printf("输入【城市数】【道路数】:\n");
    scanf("%d%d", &c, &e);
    memset(G, -1, sizeof(G));
    printf("道路情况部署【P1 P2 Len】:\n");
    for (int k = 1; k <= e; ++k)
    {
        scanf("%d%d%d", &p1, &p2, &l);
        G[p1][p2] = G[p2][p1] = l;
    }
    init();
}
void swap(int &a, int &b)
{
    int t = a;
    a = b;
    b = t;
}
void ans()
{
    printf("Min_Way=%d\n", best);
    printf("Way_Back:");
    for (int x = 1; x <= c; x++)
        printf("%d ", w2[x]);
    printf("1\n");
}
bool pd1() //【更小的总路程】
{
    if (G[w1[c - 1]][w1[c]] != -1 && G[w1[c]][w1[1]] != -1 && (way1 + G[w1[c - 1]][w1[c]] + G[w1[c]][w1[1]] < best || best == maxx))
        return true; //到叶路  叶归路 存在 且相加更优化
    else             //或者best还没更新过(防止相加后还比max大)
        return false;
}
bool pd2(int k, int y)
{
    if (G[w1[k - 1]][w1[y]] != -1 && (way1 + G[w1[k - 1]][w1[y]] < best || best == maxx))
        return true; //到y点有路  当前局部比较优秀(剪枝)或 还没启动过
    else
        return false;
}
void Find_way(int k) // i迭代深度
{
    if (k == c && pd1()) //若:探测到底,且为最小距离
    {                    //【最小距离】=当前距离+到叶距离+叶归距离
                         // k-1是因为递归指针刚来,走来位置k的距离还没算
        best = way1 + G[w1[k - 1]][w1[k]] + G[w1[k]][w1[1]];
        for (int x = 1; x <= c; x++) //保存路径
            w2[x] = w1[x];
    }
    else // 还没探测到底,继续探测
    {
        for (int y = k; y <= c; y++) //【交换】当前点和此点之后的点(全排列)
        {
            if (pd2(k, y)) //剪枝判断
            {              // k是【基准点】,y是【试图交换点】
                swap(w1[k], w1[y]);
                way1 += G[w1[k - 1]][w1[k]]; //探测,对【试图交换点】(已易位到基准点)

                Find_way(k + 1); //递归

                way1 -= G[w1[k - 1]][w1[k]]; //回溯
                swap(w1[k], w1[y]);
            }
        }
    }
}

int main()
{
    get();
    init();
    Find_way(2);
    ans();
    return 0;
}

/*
4 6
1 2 30
1 3 6
1 4 4
2 4 10
2 3 5
3 4 20
*/

装载问题

//【5.2装载问题】
#include <iostream>
#include <stdio.h>
using namespace std;
int n, w[274], c1, c2;
int now, best;
bool way1[274], way2[274]; //当前路径,最佳路径
void init()
{
    printf("输入【两船载货容量】:");
    scanf("%d%d", &c1, &c2);
    printf("输入【集装箱个数】:");
    scanf("%d", &n);
    for (int x = 1; x <= n; x++)
        scanf("%d", &w[x]);
    now = best = 0;
    for (int x = 0; x <= n; x++)
        way1[x] = false;
}
void work(int k)
{
    if (k > n) //此时溢出,说明n个选择情况都已完成
    {
        if (now > best)
        {
            best = now;
            for (int x = 1; x <= n; x++)
                way2[x] = way1[x];
        }
        return;
    }
    if (now + w[k] <= c1) // C1船还放得下
    {
        //【装一下玩玩】
        now += w[k];
        way1[k] = true;
        work(k + 1);
        //【回溯拿出来】就当啥也没发生过
        now -= w[k];
        way1[k] = false;
    }
    // C1能放,但是我就是不放第k个,直接往下探索
    work(k + 1);
}
void ans()
{
    int sum = 0;
    printf("货船1号最大载货量=%d\t集装箱编号:", best);
    for (int x = 1; x <= n; x++)
    {
        if (way2[x])
            printf("%d ", x);
        else
            sum += w[x];
    }
    printf("\n剩余集装箱总重:%d\t", sum);
    if (sum > c2)
        printf("大于货船2号载重总量%d\n任务失败!", c2);
    else if (sum < c2)
        printf("小于货船2号载重总量%d\n任务成功!", c2);
    else
        printf("恰好等于货船2号载重总量%d\n有惊无险!", c2);
}

int main()
{
    init();
    work(1);
    ans();
    return 0;
}

/*
50 50
3 10 40 40

50 50
3 20 40 40
*/

批处理作业调度

//【批处理作业调度】
#include <stdio.h>
#include <iostream>
#define maxx 0xfffffff
using namespace std;
int n, m[274][2]; //【作业总数】【批改所需用时】0是T1机,1是T2机
int f, bestf;     //【当前·总时间】【最优·总时间】
int f1, f2;       //【机器1·完成工作の时间戳】【机器2·完成工作の时间戳】
//由于T2机器会受到T1机器工作进度の影响,需要存储之前的工作情况
int way1[274], way2[274]; //【当前路径】【最佳路径】
void ans();
void init() //《数据输入》
{
    printf("输入【作业总数】:");
    scanf("%d", &n);
    for (int x = 1; x <= n; x++)
        scanf("%d%d", &m[x][0], &m[x][1]);
    // 0是 T1用时   1是T2用时
    f = f1 = f2 = 0;
    bestf = 274540;
    for (int x = 1; x <= n; x++)
        way1[x] = x; //注意!这里要初始化!!缺失这里会导致异常
}
void swap(int &a, int &b)
{
    int t = a;
    a = b;
    b = t;
}
void work(int k) //《回溯·剪枝》   部署·第k个位置
{
    if (k > n) //完成所有部署
    {
        if (f < bestf) //更优秀的结果
        {
            // printf("巧喑最可爱!");
            bestf = f;
            for (int x = 1; x <= n; x++)
                way2[x] = way1[x];
        }
        return;
    }
    else
    {
        for (int x = k; x <= n; x++) //【交换·全排列】·与·后续所有数字尝试交换
        {

            int t = f2;                               //【F2先锋时间】
            f1 += m[way1[x]][0];                      //【机器T1·完成作业批改】后的时间戳
            f2 = (f2 > f1 ? f2 : f1) + m[way1[x]][1]; //【机器T2·完成作业批改】后の时间戳
            f += f2;                                  //【当前作业T2完成实践】
            //由【之前工作完成の时间点】+【当前作业批改所需用时】
            //受到【机器T1】【前项任务进度】的影响,取最大值
            //要么,机器T1还没改完,还不能开始工作
            //要么,机器T2还没改完之前的作业,暂时不能响应当前的工作请求
            if (f < bestf) //【剪枝】局部如果不够优秀,直接舍弃,不探索子树情况
            {
                swap(way1[k], way1[x]);
                work(k + 1);
                swap(way1[k], way1[x]);
            }
            f -= f2; //【当前作业T2完成时间】
            f2 = t;  //重要!还原f2为先锋时间
            f1 -= m[way1[x]][0];
        }
    }
}
void ans() //《答案汇报》
{
    printf("min_time=%d\n", bestf);
    printf("Way:");
    for (int x = 1; x <= n; x++)
        printf("%d ", way2[x]);
    printf("\n");
}

int main()
{
    init();
    work(1);
    ans();
    return 0;
}
/*
3
2 1
3 1
2 3

*/

n皇后问题Day72

//【n皇后问题】
#include <iostream>
#include <stdio.h>
#include <math.h>
using namespace std;
/*
【控制区部署】假定n=8
向下为x·行,向右为y列
【行锁定】·当前递归层数
【列锁定】·t 标记,共n个;对角总数为2*n-1
【主对角锁定】·x-y(-7~7之间,共15个);更迭为【x-y+n-1】0-->14
【副对角锁定】·x+y(2-16之间,共15个);更迭为【x+y-2】  0-->14
*/
int n, way[233];               //【几个皇后】【皇后分布】
int sum = 0;                   //【解の总数】
bool t[233], tx[466], ty[466]; //【占用标记:列 次对角 主对角】
// tx[x+y-2]  ty[x-y+n-1]
void init()
{
    printf("输入【皇后】总数:");
    scanf("%d", &n);
    for (int x = 0; x < 466; x++)
        tx[x] = ty[x] = false;
    for (int x = 0; x < 233; x++)
        t[x] = false;
}
void ans()
{
    printf("\n第%d个解:\n", sum);
    for (int x = 1; x <= n; x++)
    {
        for (int y = 1; y <= n; y++)
        {
            if (y == way[x])
                printf("★\t");
            else
                printf("☆\t");
        }
        printf("\n\n\n");
    }
    printf("\n");
}
bool pd(int x, int y)
{
    if (!t[y] && !tx[x + y - 2] && !ty[x - y + n - 1])
        return true;
    return false;
}
void take(int x, int y)
{
    t[y] = true;
    tx[x + y - 2] = true;
    ty[x - y + n - 1] = true;
    way[x] = y;
}
void out(int x, int y)
{
    t[y] = false;
    tx[x + y - 2] = false;
    ty[x - y + n - 1] = false;
}

void work(int x) //【坐标·行】
{
    if (x > n)
    {
        sum++;
        ans();
    }
    for (int y = 1; y <= n; y++) //【坐标·列】
    {
        if (pd(x, y)) //如果可以放置
        {
            take(x, y);  //控制
            work(x + 1); //向下递归
            out(x, y);   //释放
        }
    }
}

int main()
{
    init();
    work(1);
    return 0;
}

01背包·回溯建树

//【01背包】·回溯
#include <iostream>
#include <stdio.h>
using namespace std;
int n, c, w[233], v[233];   //【商品数】【背包容量】【商品重量】【商品价值】
int vmax = -1, vnow = 0;    //【最高价值】【当前价值】
bool snow[233], sbest[233]; //【当前选择】【最优选择】
int cnow;                   //【当前背包剩余容量】
void init()
{
    printf("输入【背包容量】【商品总数】:");
    scanf("%d%d", &c, &n);
    printf("输入【商品重量】【商品价值】:\n");
    for (int x = 1; x <= n; x++)
        scanf("%d%d", &w[x], &v[x]);
    for (int x = 1; x <= n; x++)
        snow[x] = false;
    cnow = c;
}
void work(int k)
{
    if (k > n)
    {
        if (vnow > vmax)
        {
            vmax = vnow;
            for (int x = 1; x <= n; x++)
                sbest[x] = snow[x];
        }
        return;
    }
    //【选择】
    if (cnow >= w[k]) //如果放得下
    {
        cnow -= w[k];
        vnow += v[k];
        snow[k] = true; //放入:占用重量,获得价值,标记放入
        work(k + 1);
        cnow += w[k]; //拿出:释放重量,减去价值,标记没放入
        vnow -= v[k];
        snow[k] = false;
    }
    //【不选择】
    work(k + 1);
}
void ans()
{
    printf("max=%d  item:", vmax);
    for (int x = 1; x <= n; x++)
        if (sbest[x])
            printf("%d ", x);
}
int main()
{
    init();
    work(1); //第几个物品
    ans();
    return 0;
}
/*
n物品,每个物品重w,价值v;背包总载重为c;要求价值最大,怎么选?
载重13,5个商品,先重量后价格;ans=28(123)
13 5
4 9
5 10
4 9
3 2
10 24
*/

着色问题(全部方案求解/最少颜料的着色方案)

//【图の着色问题】·最少颜料使用情况
#include <iostream>
#include <stdio.h>
using namespace std;
#define maxx 0xfffffff
bool t[233][233];  //【地图】
int n, m;          //【点边 数】
int c1[233];       //【当前着色策略】什么点,用什么色
int now = 0;       //【当前着色の最大编号】
int sum = 0;       //【着色方案个数】
bool endd = false; //【最优搜索结束标志】
//最差の情况:n个点颜色都不一样
void ans()
{
    printf("\nNum=%d  Strategy=", ++sum);
    for (int x = 1; x <= n; x++)
        printf("%d ", c1[x]);
    printf("Min_Color=%d\n", now);
}
void test()
{
    for (int x = 1; x <= n; x++)
    {
        for (int y = 1; y <= n; y++)
        {
            if (t[x][y])
                printf("1");
            else
                printf("0");
            printf("\t");
        }
        printf("\n\n\n");
    }
}
void init()
{
    printf("输入【点·边 总数】:");
    scanf("%d%d", &n, &m);
    printf("输入%d条边,各边连接的【俩端点】\n", m);
    int a, b;
    for (int x = 0; x < 233; x++)
        for (int y = 0; y < 233; y++)
            t[x][y] = false;
    for (int x = 1; x <= m; x++)
    {
        scanf("%d%d", &a, &b);
        t[a][b] = t[b][a] = true;
    }
    // test();
}
bool pd(int k)
{
    for (int x = 1; x <= n; x++)
        if (t[x][k] && c1[x] == c1[k]) //【相邻点】能走得通,且颜色一样
            return false;
    return true;
}
void work(int k)
{
    if (endd == true)
        return;
    if (k > n) //第一次找到的就是最佳方案
    {
        ans();
        endd = true;
        now = 0;
        return;
    }
    //【当前点第k号】
    for (int x = 1; x <= n; x++) //尝试部署一种颜色
    {
        c1[k] = x; //部署第x种颜色
        if (pd(k)) //有效部署
        {
            if (now < x) //许多编号,选最大的一个
                now = x; //【当前使用的着色编号】
            work(k + 1);
        }
        c1[k] = 0; //部署颜色x撤回
    }
}

int main()
{
    init();
    work(1);
    return 0;
}
/*
5 8
1 3
1 2
1 4
2 4
2 5
2 3
3 4
4 5
*/

/*
【全部求解情况】
//【图の着色问题】·追踪最优方案(其实就是第一个)
#include <iostream>
#include <stdio.h>
using namespace std;
#define maxx 0xfffffff
bool t[233][233]; //【地图】
int n, m;         //【点边 数】
int c1[233];      //【当前着色策略】什么点,用什么色
int sum = 0;      //【着色方案个数】
//最差の情况:n个点颜色都不一样
void ans()
{
    printf("\nNum=%d  Strategy=", ++sum);
    for (int x = 1; x <= n; x++)
        printf("%d ", c1[x]);
}
void test()
{
    for (int x = 1; x <= n; x++)
    {
        for (int y = 1; y <= n; y++)
        {
            if (t[x][y])
                printf("1");
            else
                printf("0");
            printf("\t");
        }
        printf("\n\n\n");
    }
}
void init()
{
    printf("输入【点·边 总数】:");
    scanf("%d%d", &n, &m);
    printf("输入%d条边,各边连接的【俩端点】\n", m);
    int a, b;
    for (int x = 0; x < 233; x++)
        for (int y = 0; y < 233; y++)
            t[x][y] = false;
    for (int x = 1; x <= m; x++)
    {
        scanf("%d%d", &a, &b);
        t[a][b] = t[b][a] = true;
    }
    // test();
}
bool pd(int k)
{
    for (int x = 1; x <= n; x++)
        if (t[x][k] && c1[x] == c1[k]) //【相邻点】能走得通,且颜色一样
            return false;
    return true;
}
void work(int k)
{
    if (k > n) //第一次找到的就是最佳方案
    {
        ans();
        return;
    }
    //【当前点第k号】
    for (int x = 1; x <= n; x++) //尝试部署一种颜色
    {
        c1[k] = x; //部署第x种颜色
        if (pd(k)) //有效部署
            work(k + 1);
        c1[k] = 0; //部署颜色x撤回
    }
}

int main()
{
    init();
    work(1);
    return 0;
}

*/

圆排列问题

//【圆排列问题】n个圆,底边都相切于同一矩形,要求矩形的狂宽度最小
#include <iostream>
#include <stdio.h>
#include <math.h>
using namespace std;
#define maxx 0xfffffff
int n;                  //【圆の个数】
float r[2745], x[2745]; //【圆の排列·当前】 【横坐标情况·当前】
float minn = maxx;      //【最小の长度·全局】
float best[2745];       //【最好の排列方式】
void init()
{
    printf("输入【圆の数量】:");
    scanf("%d", &n);
    for (int t = 1; t <= n; t++)
        x[t] = 0;
    for (int x = 1; x <= n; x++)
        scanf("%f", &r[x]);
}
void swap(float *a, float *b)
{
    float c = *a;
    *a = *b;
    *b = c;
}
float X(int k) //返回第t个圆的横坐标x
{
    float t = 0;                //【t最长の长度】【v临时の长度】 之前共k-1个圆
    for (int i = 1; i < k; i++) //【极端情况】当前k有可能与之前任意一个圆i相切
    {
        float v = x[i] + 2 * sqrt(r[i] * r[k]);
        if (v > t)
            t = v;
    }
    return t;
}
void test(float a)
{
    printf("\n【最佳情况】%f\n", a);
    printf("\n 横坐标·半径 情况汇报:\n");
    for (int i = 1; i <= n; i++)
        printf("%f ", x[i]);
    printf("\n");
    for (int i = 1; i <= n; i++)
        printf("%f ", r[i]);
    printf("\n");
}
void all() //计算当前全区长度
{
    float high = 0, low = 0;
    for (int i = 1; i <= n; i++)
    {
        if (x[i] - r[i] < low)
            low = x[i] - r[i];
        if (x[i] + r[i] > high)
            high = x[i] + r[i];
    }
    if (high - low < minn)
    {
        minn = high - low;
        for (int i = 1; i <= n; i++)
        {
            best[i] = r[i];
        }
        // test(minn);
    }
}
void work(int k)
{
    if (k > n)
        all();
    else
    {
        for (int i = k; i <= n; i++)
        {
            swap(r[i], r[k]);
            float cx = X(k);             //【全排列】部署完成第k个位置
            if (cx + r[1] + r[k] < minn) //【剪枝】局部相对最优
            {                            //有效部署!
                x[k] = cx;
                work(k + 1);
            }
            swap(r[i], r[k]);
        }
    }
}
void ans()
{
    printf("Min=%.4f\nBest_Array:", minn);
    for (int i = 1; i <= n; i++)
    {
        printf("%.2f ", best[i]);
    }
}

int main()
{
    init();
    work(1);
    ans();
    return 0;
}
//注意极端情况
/*
4
50 0.1 100 20

*/

注意!极端情况,思想来自:五分钟解决圆排列问题_羊书change的博客-CSDN博客_圆形排列给定n个大小不等的圆c1,c2,…,cn,现要将这n个圆排进一个矩形框中,且要求各圆与矩形框的底边相切。圆排列问题要求从n个圆的所有排列中找出有最小长度的圆排列。例如,当n=3,且所给的3个圆的半径分别为1,1,2时,这3个圆的最小长度的圆排列如图所示。其最小长度为2+4sqrt(2)center函数:center计算圆在当前圆排列中的横坐标,由x^2 = sqrt((r1+r2)^2-(r...https://blog.csdn.net/yangshucheng2018/article/details/105158130?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164826751116780271953146%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164826751116780271953146&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-105158130.142%5Ev5%5Econtrol,143%5Ev6%5Eregister&utm_term=%E5%9C%86%E6%8E%92%E5%88%97%E9%97%AE%E9%A2%98%0A++++++++++++++++++++++++++&spm=1018.2226.3001.4187例如:100  、 0.1  、 50、   20

        这种情况下13相切,2和1相切;对3号x坐标判断会产生异常。

这个决定了函数x中,需要覆盖所有相切可能性(t可能与t之前任意一个圆形相切)

决定了函数all中,总长度定界需要遍历每一个点看左右界限

而不是只是看最左侧或者最右侧情况

【总体思想】:对圆进行全排列

函数X:返回【第t个圆·当前部署】的横坐标

        t是最长x的情况,i遍历【k之前の所有圆】·假设与他相切

        【第t个圆の横坐标】=【第i个圆の横坐标】+相切时的纵向情况

 函数ALL:在全部部署完成后,对总长度进行一个判断

        遍历所有点,根据当前点的x和r给出左右边界,是否能【延伸左右边界】

极端情况举例 0.1     200  0.1  0.2,这时候左右边界是200决定,而非左右的0.1

 左右0.1在200下面的空隙处,起不到作用

【局部剪枝】本身目的是:【当前排列总长度<min就继续】

        但具体实践中使用的却是【最左侧】【最右侧】,似乎是个无奈之举

        因为如果真去判断当前排列总长度(参考all),代价太大

        这里使用的是一个【折中的方法】

(这里似乎没有考虑到极端情况的发生,只是【最左侧】和【最右侧(当前)】进行一个长度计算,比当前min要小,就继续)

        其实仔细思考可以发现,假如0.1 200 0.1 0.1这种数据进去,如果有内嵌情况

        d一定是变小的,是可以被这个剪枝覆盖进去的,不会丢失情况

        只是可能会多产生几次计算(剪枝不彻底)

这确实是无奈之举,因为相较于精确判断要支付的代价
这样多计算几个叶子结点的全排列情况也许更合算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

影月丶暮风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值