• 约束条件下的优化问题


    约束条件下的优化问题

             给定一系列训练,每个训练有以下几个属性:

    名字 能量消耗 可以被选的阶段个数 阶段阶段……

             有x个阶段,每个阶段又有如下以下两个限制:

             1.每个阶段中最多不能包含超过n个训练,也就是说训练个数小于等于n;

             2.每个阶段里的能量消耗总和不得大于m。

             在这种约束条件下,求得所有阶段能耗消耗总和最大的组合。每个训练最多可以被选择一次,可以不被选择。

             这个问题咋一看有点类似于背包问题,每个训练最多可以被选择一个,有点像是01背包问题,并且也有限制条件,比如每个阶段不得大于n个训练,能耗之和不得大于m,但最终也想求得所有阶段能耗最大的组合。

             背包问题有很多种,诸如01背包、完全背包、多重背包、混合背包、分组背包、多维背包等等。背包问题可以用动态规划(DP)来解决,有关背包问题的求解可以谷歌百度之,另外有篇背包问题的教程可以学习一下《背包问题九讲》。

             上面这个问题有点类似于01背包、分组背包、多维背包方面的问题。但是由于博主还未对背包问题以及动态规划进行完整的研究和学习。所以,这里我们不采用按照背包问题的解法来试图求解该问题。

             这里,我们是运用穷举的方法来解决该问题。

             首先我们来看一下之前我们曾研究过的一个问题。有n组数,每一组中又有Mi个数,每组数中的个数有可能互不相同。比如有以下几组数:

    1 2 3

    4 5

    6 7 8 9

             我们想从每组数中选择一个数,组成一个序列,问有多少种组合,每种组合分别是什么。

             如果,我们预先知道了有几组数,那么可以预先用循环来解决,有几组数,就用几个循环来解决。

             但是,如果这些组的个数是未知的该怎么办呢。显然写好的n个循环是不适用的,因为写好的程序是死的,而给定的几组数是获得,组数一变,原来的程序就不适用了。

             我们之前给出了一个递归解法:《从每组中依次选择一个元素》。我们的递归结束条件是当走到了最后一组时结束,递归调用条件是选择当前组中的一个后,再从后面的组中选择数。而循环是针对当前组内所有元素进行遍历。每个组中都要选择一个数,得到的每个结果序列中,都要有且只有每个组中的一个数。

             通过对选数的分析,我们可以看出选数问题和这个问题有点类似,只不过是这个问题有几个约束条件:

             1.每个阶段的训练个数小于等于n

             2.每个阶段的训练总能耗小于等于m

             3.每个训练有指定可以被选的阶段,并不是每个阶段都能选择任意个训练

             另外,每个训练可以不被选择,或最多被选择一次。还有就是我们想到是的所有阶段总的能耗最大的结果。

             我们将每个训练看做每组数,而训练中的元素就是该训练可以被选择的阶段名或索引,另外还包括不被选择的情形。得到的结果相当于选数得到的序列。

             只不过,在选择的时候,我们需要依次检测上述三个约束条件,是的结果都能满足这三个约束条件。另外,我们还要考虑每个训练被选择和不被选择的情况。最后,到得到一个结果后,我们还要检测该结果是否是最优结果,需要将最后结果保存。

             下面我们依照程序进行讲解。

             我们求解该问题的函数式solt函数,该函数存在两个递归调用。首先,针对第一个约束条件,我们会检测当前阶段的联系个数是否小于n。针对第二个约束条件,检测如果将该训练加入到该阶段,那么该阶段的能耗总和是否小于等于m。另外,在检测前面两个条件之前,我们会根据训练可以被选择的阶段所以,获取其可以被选择的阶段索引,这个对应于第三个约束条件。

             另外,如果可以被选择,那么我们会将该训练加入到该阶段,进行递归调用,递归调用完之后,我们还要将其退出,继续检测下一个阶段。

             由于,每个训练可能存在不被选择的情形,所以,我们不管是否能不能被选进去检测阶段,都要不选择该训练的前提下进行递归调用,直接检测下一个训练。

             所以,循环内部有两个递归调用。而选数问题中,我们不用检测约束条件,直接选中。也就是说,选中的情形占100%。而这个问题中,符合条件被选中这种情形占50%。另外,符合条件也可以不选中,所以不选中还有50%。另外是不符合条件就不选中还有50%。所以递归调用的个数是150%,被选中是50%。而选数递归调用是100%,被选中是100%。

             循环是针对当前训练下,遍历其可以被选中的阶段。递归调用条件时处理完(选中和不选中)后,调用下一个训练的递归调用。递归终止条件是,当检测完所有的训练后,就终止。

             终止时,需要检测得到的结果是否是最优结果,如果是最优的,那么情况所有原来的,并保存该最优结果;如果和当前最优的一样好,那么检测该新的结果是否与已保存的最优结果重复,如果重复,则直接返回,如果不重复,则将其也保存到结果集中。之所以存在重复的结果是因为,当检测到后面是,后面的训练不管入选到哪个阶段都会破坏约束条件,所以前面已选的结果就会存在重复性。这就需要我们进行检测,以防止最优结果中有重复情形。

             下面我们给出该问题的一个样例,有数据文件:

             n和m由用户指定。我们给句给定的训练数据信息和n、m约束得到最优的结果。下面给出程序实现。具体相关细节可以查看程序代码和注释说明。

    // 约束最优
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAX (20 + 1)
    #define NUM (10  + 1)
    
    // exercise结构体
    typedef struct exercise
    {
        char name[MAX];   // 名称
        int  enexp;       // 能耗
        int  n_phase;     // 适用于phase的个数
        int  phases[NUM]; // phase的索引
    
        int  flag; // 是否已经被选择,0表示未被选择,1表示已选择,没用到
    
    } Exercise;
    
    // phase结构体
    typedef struct phase
    {
        int indexes[NUM]; // 保存exercise的索引
        int n_exercise;   // exercise的数目
        int totalenexp;   // 该phase的体能消耗和
    } Phase;
    
    // 读取文件
    Exercise* readfile(char* filename, int* n_exercise)
    {
        int       i  = 0, j = 0, eng = 0, n = 0, tmp = 0;
        Exercise* rt = NULL;
        FILE*     fp = NULL;
        char      name[MAX];
    
        fp = fopen(filename, "r");
        if (fp == NULL)
        {
            fprintf(stderr, "Open file error!
    ");
            exit(1);
        }
    
        if (fscanf(fp, "%d", n_exercise) == EOF)
        {
            fprintf(stderr, "Error in reading file!
    ");
            exit(1);
        }
    
        rt = (Exercise*)malloc(*n_exercise * sizeof (Exercise));
        if (rt == NULL)
        {
            fprintf(stderr, "Error in allocating the memory!
    ");
            exit(1);
        }
    
        for (i = 0; i < *n_exercise; ++i)
        {
            fscanf(fp, "%s %d %d", name, &eng, &n);
            strcpy(rt[i].name, name);
            rt[i].enexp = eng;
            rt[i].n_phase = n;
            
            rt[i].flag = 0; // 初始化flag
    
            for (j = 0; j < n; ++j)
            {
                fscanf(fp, "%d", &tmp);
                rt[i].phases[j] = tmp;
            }
        }
    
        // 读取完毕,关闭文件
        fclose(fp);
    
        // 返回rt
        return rt;
    }
    
    // 初始化phases
    void initphases(Phase* ph, int n)
    {
        int i = 0;
        for (i = 1; i <= n; ++i)
        {
            ph[i].n_exercise = 0;
            ph[i].totalenexp = 0;
        }
    }
    
    // 打印一个结果
    void printphases(Phase* ph, int n, Exercise* rt)
    {
        int i = 0, j = 0;
        int idx = 0;
        int total = 0;
    
        for (i = 1; i <= n; ++i)
        {
            printf("PHASE %d
    ", i);
            total = 0;
            for (j = 0; j < ph[i].n_exercise; ++j)
            {
                idx = ph[i].indexes[j];
                printf("	%s	%d
    ", rt[idx].name, rt[idx].enexp);
                total += rt[idx].enexp;
            }
            printf("Total:%d
    ", total);
        }
        printf("
    ");
    }
    
    // 计算总的能量消耗
    int totoalenergy(Phase* ph, int n, Exercise* rt)
    {
        int i = 0, j = 0;
        int idx = 0;
        int total = 0;
    
        for (i = 1; i <= n; ++i)
        {
            for (j = 0; j < ph[i].n_exercise; ++j)
            {
                idx    = ph[i].indexes[j];
                total += rt[idx].enexp;
            }
        }
        return total;
    }
    
    // 打印所有exercises
    void printexercise(Exercise* rt, int n)
    {
        int i = 0, j = 0;
        for (i = 0; i < n; ++i)
        {
            printf("%s	%d	%d	", rt[i].name, rt[i].enexp, rt[i].n_phase);
            for (j = 0; j < rt[i].n_phase; ++j)
            {
                printf("%d	", rt[i].phases[j]);
            }
            printf("%d
    ", rt[i].flag);
        }
        printf("
    ");
    }
    
    // 判断结果是否一致
    int issame(Phase ph1[5], Phase ph2[5], int n)
    {
        int i = 0, j = 0;
        int idx1 = 0, idx2 = 0;
    
        for (i = 1; i <= n; ++i)
        {
            if (ph1[i].n_exercise != ph2[i].n_exercise)
            {
                return 0;
            }
            for (j = 0; j < ph1[i].n_exercise; ++j)
            {
                idx1 = ph1[i].indexes[j];
                idx2 = ph2[i].indexes[j];
                if (idx1 != idx2)
                {
                    return 0;
                }
            }
        }
        return 1;
    }
    
    // 求解所有的可能结果
    void solt(Exercise* rt, int num1, int pos, Phase* ph, int num2, int n, int m, int* maxtotal, Phase ph2[100][5], int* count)
    {
        int i = 0, j = 0;
        int index = 0;
        int total = 0;
    
        if (pos >= num1) // 递归终止条件
        {
            total = totoalenergy(ph, num2, rt);
            if (total > *maxtotal) // 如果得到的新结果比原来最好结果都好,则删除原来的所有结果,并将新结果保存
            {
                *maxtotal = total;
                for (j = 1; j <= num2; ++j)
                {
                    ph2[0][j] = ph[j];
                }
                *count = 1;
            }
            else if (total == *maxtotal) // 如果得到的新结果与原来的新结果一样
            {
                for (j = 0; j < *count; ++j) // 首先检测是否与原来的结果存在重复
                {
                    if (issame(ph2[j], ph, num2))
                    {
                        return;
                    }
                }
    
                for (j = 1; j <= num2; ++j) // 若不重复,则将新结果保存
                {
                    ph2[*count][j] = ph[j];
                }
                ++*count;
            }
    
            return;
        }
    
        for (i = 0; pos < num1 && i < rt[pos].n_phase; ++i) // 顺序遍历每个exercise可以参加的phase
        {
            index = rt[pos].phases[i];
            if (ph[index].n_exercise < n && ph[index].totalenexp + rt[pos].enexp <= m) // 如果符合条件,则加入到phase中
            {
                ph[index].indexes[ph[index].n_exercise++] = pos;
                ph[index].totalenexp += rt[pos].enexp;
    
                solt(rt, num1, pos + 1, ph, num2, n, m, maxtotal, ph2, count);
    
                --ph[index].n_exercise;
                ph[index].totalenexp -= rt[pos].enexp;
            }
            solt(rt, num1, pos + 1, ph, num2, n, m, maxtotal, ph2, count); // 不管符不符合条件,都不加入到phase中
        }
    }
    
    // 打印结果
    void printmax(Phase maxph[100][5], int pos, int n, Exercise* rt)
    {
        int i = 0;
    
        for (i = 0; i < pos; ++i)
        {
            printphases(maxph[i], n, rt);
        }
    }
    
    int main(int argc, char* argv[])
    {
        Exercise* rt   = NULL;
        Phase     ph[4 + 1], ph2[100][4 + 1];
        Phase     maxphs[100][4 + 1];
        int       count = 0;
        int       maxtotoal = -1;
        int       num1 = 0;
        int       n = 0, m = 0;
        int       i = 0;
    
        if (argc != 4)
        {
            fprintf(stderr, "Error in passing the arguments!
    ");
            exit(1);
        }
        n = atoi(argv[1]);
        m = atoi(argv[2]);
    
        rt = readfile(argv[3], &num1);
    
        initphases(ph, 4);
    
        solt(rt, num1, 0, ph, 4, n, m, &maxtotoal, ph2, &count);
    
        printf("%d %d
    ", count, maxtotoal);
    
        for (i = 0; i < count; ++i)
        {
            printf("%d:
    ", i + 1);
            printphases(ph2[i], 4, rt);
            printf("
    ");
        }
    
        printf("%d %d
    ", count, maxtotoal);
        //printphases(ph, 4, rt);
    
        return 0;
    }

           对选数的重写

             下面我们给出选数问题的重写,与《从每组中依次选择一个元素》的不同在于,我们修改了索引,递归结束条件不是到达最后一个终止,而是将所有的都处理完后终止。

             具体代码如下:

    // 选数重写
    #include <iostream>
    #include <vector>
    using namespace std;
    
    // 参数n和total可以省略
    // 因为n=src.size()
    // total=dst.size()
    void SelectNumber(const vector<vector<int> >& src, int n, int pos, vector<vector<int> >& dst, int& total, vector<int>& stk)
    {
        if (pos >= n)
        {
            dst.push_back(stk);
            ++total;
            return;
        }
    
        for (auto i = 0; i != src[pos].size(); ++i)
        {
            //if (pos >= n)
            //{
            //    dst.push_back(stk);
            //}
            //else
            {
                stk.push_back(src[pos][i]);
                SelectNumber(src, n, pos + 1, dst, total, stk);
                stk.pop_back();
            }
        }
    }
    
    int main()
    {
        vector<vector<int> > src;
        vector<int> tmp;
    
        tmp.push_back(1);
        tmp.push_back(2);
        tmp.push_back(3);
        src.push_back(tmp);
        tmp.clear();
    
        tmp.push_back(4);
        tmp.push_back(5);
        src.push_back(tmp);
        tmp.clear();
    
        tmp.push_back(6);
        tmp.push_back(7);
        tmp.push_back(8);
        tmp.push_back(9);
        src.push_back(tmp);
        tmp.clear();
    
        vector<vector<int> > dst;
        int total = 0;
    
        SelectNumber(src, src.size(), 0, dst, total, tmp);
    
        for (auto i = 0; i != src.size(); ++i)
        {
            for (auto j = 0; j != src[i].size(); ++j)
            {
                cout << src[i][j] << ' ';
            }
            cout << endl;
        }
        cout << endl;
        
        for (auto i = 0; i != dst.size(); ++i)
        {
            for (auto j = 0; j != dst[i].size(); ++j)
            {
                cout << dst[i][j] << ' ';
            }
            cout << endl;
        }
        cout << endl;
        cout << total << endl;
        cout << endl;
    
        return 0;
    }

             递归结束条件改为检查完所有的组,其返回位置改为了for循环外面,因为原来的检测到最后一个后,还需要处理最后一个,所以需要在循环内部。而检测完后,如果放在for循环里面,那么会越界,导致错误,并且放在里面没有任何意义。所以,我们需要将其放在外面,直接结束递归调用。

  • 相关阅读:
    WPF添加ResourceDictionary后【The Property "Resource" can only be set once】问题
    WPF中获取匿名(Anonymous)对象的键值方法(例如DataGrid绑定List<无名元素>时)
    安装Win10到移动硬盘的利器:WTGA
    xcodebuild 能在模拟器上运行测试啦
    Jenkins Mac slave 遇到 git: 'credential-osxkeychain' is not a git command. 错误
    远程调试UWP遇到新错误Could not generate the root folder for app package ......
    开始学习python
    文件打包
    统计 某个目录下 所有的文件的行数
    根据进程名称获取进程id
  • 原文地址:https://www.cnblogs.com/unixfy/p/3491644.html
Copyright © 2020-2023  润新知