zoukankan      html  css  js  c++  java
  • 约束条件下的优化问题

    约束条件下的优化问题

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

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

             有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循环里面,那么会越界,导致错误,并且放在里面没有任何意义。所以,我们需要将其放在外面,直接结束递归调用。

  • 相关阅读:
    与众不同 windows phone (50)
    与众不同 windows phone (49)
    重新想象 Windows 8.1 Store Apps (93)
    重新想象 Windows 8.1 Store Apps 系列文章索引
    重新想象 Windows 8.1 Store Apps (92)
    重新想象 Windows 8.1 Store Apps (91)
    重新想象 Windows 8.1 Store Apps (90)
    重新想象 Windows 8.1 Store Apps (89)
    重新想象 Windows 8.1 Store Apps (88)
    重新想象 Windows 8.1 Store Apps (87)
  • 原文地址:https://www.cnblogs.com/unixfy/p/3491644.html
Copyright © 2011-2022 走看看