【讨论】【回帖有分】最优组合算法问题!

安馨安旭 2009-07-24 09:36:50
加精
属性1 属性2 属性3
A 100 10 15
B 200 20 35
C 500 5 15
D 1000 3 23
E 300 7 31
F 500 3 18

A、B、C、D、E、F....代表物品名称
属性1、2、3分别代表每个物品的一种属性

现在给定一个要求:从这些物品中取出一定数量的物品,要求它们属性1的总和小于X,属性2的总和小于Y,但是要保证属性3的总和是最大,应该如何取?将最优的结果列举出来,如果有多条最优可以分多条列举。

注:同一个物品可以取多个。

我这个只是一个例子,里面只随便列举了6个物品,实际情况下有300多个物品,希望能得到一个比较快点的算法。

大家踊跃讨论,回帖有分!
解决问题的重新开贴给分!
...全文
732 66 打赏 收藏 转发到动态 举报
写回复
用AI写文章
66 条回复
切换为时间正序
请发表友善的回复…
发表回复
laowang2 2009-08-31
  • 打赏
  • 举报
回复
安馨安旭 2009-08-31
  • 打赏
  • 举报
回复
对不起了,各位,尤其是毛毛,热心帮助我。
上个月接到一个紧急项目出差了,搞了一个月,昨天才回。
稍后我会另外开贴给毛毛分的。对不起了。
毛毛的方法我试过了,效果比我的好了很多,明天再拿实际数据试试。
结贴!
wuminghan 2009-08-01
  • 打赏
  • 举报
回复
囧~有一个不准确但速度很快的办法如果数量在10000+可以考虑
把属性三分别除以属性一,属性二之后用加权平均(根据xy的限制的倒数)算出一个值
按照这个值列表
从大到小依次排
在没达到xy的情况下从大到小依次添加遇到超过xy限制的跳过看下一个直到遍历
还可以做别的优化比如根据遍历后剩余的空间(xy)再更换物品

好吧其实我什么都不懂...咱是来打酱油
米的向日葵 2009-07-31
  • 打赏
  • 举报
回复
毛毛的代码也有点问题,当X,Y一样时会出现负数
安馨安旭 2009-07-30
  • 打赏
  • 举报
回复
我按照你的思路修改了一下,但是还是有重复问题,可能有些地方写得有问题,不过效率确实提高了很多,代码如下:
.h

//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>

//#include <list>
#include <stack>
#include <vector>
using namespace std;

//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TEdit *Edit1;
TEdit *Edit2;
TButton *Button2;
TMemo *Memo1;
void __fastcall Button2Click(TObject *Sender);
private: // User declarations
public: // User declarations
struct FItem
{
AnsiString name; //名称
int Pro1;//属性1
int Pro2;//属性2
int Pro3;//属性3
};
vector<FItem> listscr;//原列表
stack<FItem> listans;//分析列表
// list<FItem>::iterator listitor;//迭代器
TStrings* ComList;//最终组合列表
int MaxSum;//属性3和的最大值

AnsiString sumName;//名称组合之和
int sumX, sumY, sumZ;//缓存当前x、y、z各自的属性和
__fastcall TForm1(TComponent* Owner);
bool __fastcall IsOK(stack<FItem> &list, int X, int Y);//判断是否满足XY条件
void __fastcall DelTop(stack<FItem> &list);//去掉最后入栈的属性累加
void __fastcall setMaxCond();//记录组合
void __fastcall Travel(stack<FItem> &list, int start, int X, int Y);//递归
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif


.cpp

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{//从文件中读取数据
ComList = new TStringList();
TStrings* s_list = new TStringList();
s_list->LoadFromFile(ExtractFilePath(Application->ExeName) + "data.txt");

for (int i = 0; i < s_list->Count; i++)
{
FItem *newitem = new FItem();
AnsiString str = s_list->Strings[i];
int flg = str.Pos("\t");
newitem->name = str.SubString(1, flg-1);
str.Delete(1, flg);

flg = str.Pos("\t");
newitem->Pro1 = StrToInt(str.SubString(1, flg-1));
str.Delete(1, flg);

flg = str.Pos("\t");
newitem->Pro2 = StrToInt(str.SubString(1, flg-1));
str.Delete(1, flg);

newitem->Pro3 = StrToInt(str);
listscr.push_back(*newitem);
}
delete s_list;
}
//---------------------------------------------------------------------------

bool __fastcall TForm1::IsOK(stack<FItem> &list, int X, int Y)//判断是否满足XY条件
{
FItem newitem = list.top();
sumX += newitem.Pro1;
sumY += newitem.Pro2;
if(sumX > X || sumY > Y)//不符合情况时立刻减去属性1、2的累加值
{
sumX -= newitem.Pro1;
sumY -= newitem.Pro2;
return false;
}
else//符合情况时将名称和属性3也累加进来
{
sumName += newitem.name; // + "\t"
sumZ += newitem.Pro3;
return true;
}
}
//---------------------------------------------------------------------------

void __fastcall TForm1::DelTop(stack<FItem> &list)
{
FItem newitem = list.top();
// sumName.Delete(sumName.Length() - newitem.name.Length(), newitem.name.Length())
sumName.Delete(sumName.Length(), 1);
sumX -= newitem.Pro1;
sumY -= newitem.Pro2;
sumZ -= newitem.Pro3;
}
//---------------------------------------------------------------------------

void __fastcall TForm1::setMaxCond()
{
if(sumZ > MaxSum)//刷新纪录
{
ComList->Clear();
ComList->Add(sumName);
MaxSum = sumZ;
}
else if(sumZ == MaxSum)//增加并列纪录
ComList->Add(sumName);

}
//---------------------------------------------------------------------------

void __fastcall TForm1::Travel(stack<FItem> &list, int start, int X, int Y)//递归
{
for (int i = start; i < listscr.size(); i++)
{
list.push(listscr[i]);
if(IsOK(list, X, Y))//判断是否满足XY条件
{
Travel(list, i, X, Y); //递归,断续加入更多物品
}
else//只有叠加到极限时才比较记录(目前所有属性都是正数)
{
list.pop();
setMaxCond();
}
if(list.size() > 0)
{
DelTop(list);
list.pop();
}
}
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender)//调用
{
ComList->Clear();
MaxSum = 0;
sumName = "";
sumX = 0;
sumY = 0;
sumZ = 0;
Travel(listans, 0, Edit1->Text.ToInt(), Edit2->Text.ToInt());
Memo1->Lines->Assign(ComList); //打印结果
}
//---------------------------------------------------------------------------


我将所有组合情况打印出来后,发现了很多重复的组合情况,不明白是怎么得到的。

安馨安旭 2009-07-30
  • 打赏
  • 举报
回复
谢谢毛毛!我再理解下你的思想,转成代码。
hemiya 2009-07-30
  • 打赏
  • 举报
回复
安馨安旭 2009-07-30
  • 打赏
  • 举报
回复
谢谢毛毛,我今天比较忙,你的代码我改天看,刚接了个紧急项目。见谅!
Waiting4you 2009-07-30
  • 打赏
  • 举报
回复
我的代码:
#include <iostream>
#include <string>
#include <vector>
#include <iterator>

using namespace std;

struct TObj{
char name;
int prop1;
int prop2;
int prop3;
};

struct TGroup{
vector<int> vecObj; // 物品组合(索引)
int nX; // 属性1剩余点数
int nY; // 属性2剩余点数
int nSumZ; // 属性3总和
};

// 参数:物品数组objlist、物品数组大小n,临时组合grp,新加物品的起始索引i,属性3的最大组合maxgrp
void travel(const TObj *objlist, const int n, TGroup &grp, int i, TGroup &maxgrp)
{
//下面两行输出所有组合,确认是否有重复组合出现
//copy(grp.vecObj.begin(), grp.vecObj.end(), ostream_iterator<int>(cout));
//cout << "\t nX: " << grp.nX << ";nY " << grp.nY << ";nSumZ: " << grp.nSumZ << endl;

for(; i<n; ++i)
{
const TObj & obj = objlist[i];
if(grp.nX >= obj.prop1 && grp.nY >= obj.prop2)// 剩余点数足够
{
// 加入当前物品索引
grp.vecObj.push_back(i);
grp.nX -= obj.prop1;
grp.nY -= obj.prop2;
grp.nSumZ += obj.prop3;
// 抽取最大组合
if(maxgrp.nSumZ < grp.nSumZ) maxgrp = grp;
// 继续尝试
travel(objlist, n, grp, i, maxgrp);
// 恢复
grp.vecObj.pop_back();
grp.nX += obj.prop1;
grp.nY += obj.prop2;
grp.nSumZ -= obj.prop3;
}
}
}

TObj g_objs[6]={
{'A',100,10,10},
{'B',200,21,21},
{'C',300,33,35},
{'D',400,42,48},
{'E',500,56,60},
{'F',600,65,81}
};

int main(int argc, char* argv[])
{
// 临时组合初始值
TGroup grp;
grp.nX = 10000;
grp.nY = 1000;
grp.nSumZ = 0;

// 最大组合
TGroup maxgrp=grp;

travel(g_objs, 6, grp, 0, maxgrp);

// 输出结果
for(vector<int>::const_iterator itr=maxgrp.vecObj.begin();
itr!=maxgrp.vecObj.end();
++itr)
{
cout << g_objs[*itr].name << ' ';
}
cout << endl << "属性1总和:" << 10000 - maxgrp.nX << endl;
cout << "属性2总和:" << 1000 - maxgrp.nY << endl;
cout << "属性3总和:" << maxgrp.nSumZ << endl;

// 暂停
cin.get();
return 0;
}
安馨安旭 2009-07-30
  • 打赏
  • 举报
回复
总感觉

else//只有叠加到极限时才比较记录(目前所有属性都是正数)
{
list.pop();
setMaxCond();
}
if(list.size() > 0)
{
DelTop(list);
list.pop();
}

这一部分处理有点问题,但是从逻辑上分析又是对的,有点想不通了。
Waiting4you 2009-07-29
  • 打赏
  • 举报
回复
看了一下你的代码,IsOK可以改进一下
if(sumX > X || sumY > Y)
return false;
最好放到for的外面。

另外,传list的同时可以再传一个当前list的sumX和sumY,加入/减去一个物品时更新一下list的sumX和sumY(只要一次加/减运算即可),免去的每次都从用for从头加到尾,浪费时间。
Waiting4you 2009-07-29
  • 打赏
  • 举报
回复
修改一下原来的伪代码,使用组合方式
travel(stack &s, int start)
{
for(int i=start; i<300; i++)
{
s.push(obj[i]); //加入第i件物品
if(IsOK(s)) // 如果条件符合
{
travel(s, i); // 递归,断续加入更多物品
}
else //只有叠加到极限时才比较记录(前提是属性3没有负数)
{
s.pop();
setMaxCond(s); // 如果s里的属性3之和最大,就记录之
}
s.pop();
}
}

另外,还可以考虑先把物品按属性1排序,然后在循环里先测试属性1之和是否超出,如果超出直接break,后面的就不用测试了。不过优化是个有难度的东西,if的增加会导致CPU预测失败增多,有时反而会减慢速度。
在算法实在优化不了的时候,可以考虑多核优化,比如用TBB库。
安馨安旭 2009-07-29
  • 打赏
  • 举报
回复
前两天没有时间,今天上午按照毛毛的思想写了一下代码,如下:
.h文件

//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>

#include <list>
#include <vector>
using namespace std;

//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TEdit *Edit1;
TEdit *Edit2;
TButton *Button2;
TMemo *Memo1;
void __fastcall Button2Click(TObject *Sender);
private: // User declarations
public: // User declarations
struct FItem
{
AnsiString name; //名称
int Pro1;//属性1
int Pro2;//属性2
int Pro3;//属性3
};
vector<FItem> listscr;//原列表
list<FItem> listans;//分析列表
list<FItem>::iterator listitor;//迭代器
TStrings* ComList;//最终组合列表
int MaxSum;//属性3和的最大值
__fastcall TForm1(TComponent* Owner);
bool __fastcall IsOK(list<FItem> &list, int X, int Y);//判断是否满足XY条件
bool __fastcall Travel(list<FItem> &list, int X, int Y);//递归
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

.cpp文件

//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{//从文件中读取数据
ComList = new TStringList();
TStrings* s_list = new TStringList();
s_list->LoadFromFile(ExtractFilePath(Application->ExeName) + "data.txt");

for (int i = 0; i < s_list->Count; i++)
{
FItem *newitem = new FItem();
AnsiString str = s_list->Strings[i];
int flg = str.Pos("\t");
newitem->name = str.SubString(1, flg-1);
str.Delete(1, flg);

flg = str.Pos("\t");
newitem->Pro1 = StrToInt(str.SubString(1, flg-1));
str.Delete(1, flg);

flg = str.Pos("\t");
newitem->Pro2 = StrToInt(str.SubString(1, flg-1));
str.Delete(1, flg);

newitem->Pro3 = StrToInt(str);
listscr.push_back(*newitem);
}
delete s_list;
}
//---------------------------------------------------------------------------

bool __fastcall TForm1::IsOK(list<FItem> &list, int X, int Y)//判断是否满足XY条件
{
AnsiString sumName = "";
int sumX = 0;
int sumY = 0;
int sumZ = 0;
for (listitor = list.begin(); listitor != list.end(); ++listitor)
{
FItem newitem = *listitor;
sumName += newitem.name; // + "\t"
sumX += newitem.Pro1;
sumY += newitem.Pro2;
sumZ += newitem.Pro3;
if(sumX > X || sumY > Y)
return false;
}
if(sumZ > MaxSum)//刷新纪录
{
ComList->Clear();
ComList->Add(sumName);
MaxSum = sumZ;
}
else if(sumZ == MaxSum)//增加并列纪录
ComList->Add(sumName);
return true;
}
//---------------------------------------------------------------------------

bool __fastcall TForm1::Travel(list<FItem> &list, int X, int Y)//递归
{
for (int i = 0; i < listscr.size(); i++)
{
list.push_back(listscr[i]);
if(IsOK(list, X, Y))//判断是否满足XY条件
Travel(list, X, Y); //递归,断续加入更多物品
list.pop_back();
}
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender)//调用
{
ComList->Clear();
MaxSum = 0;
Travel(listans, Edit1->Text.ToInt(), Edit2->Text.ToInt());
Memo1->Lines->Assign(ComList); //显示结果
}


这里对6个物品的数据进行了调整,因为之前给题目的时候六个物品的数据是乱写的,如果用程序去计算很多情况得到的都是多个B物品的组合,没意义。所以我重新用了有意义的实际数据,可以用来测试:
属性1 属性2 属性3
A 100 10 10
B 200 21 21
C 300 33 35
D 400 42 48
E 500 56 60
F 600 65 81

下午测试了一下,可以得出结果,但是有两个缺点:
1.速度慢,如果X和Y值比较大时或者物品比较多时,计算速度就很慢,这个可能也是因为排列的原因。我目前只用了6个物品的数据测试,将X设置为10000,Y设置为200,程序基本上就不能停止了。因为需要尝试组合的情况太多了。如果真正运用到300个物品上,估计计算时间不可想象。
2.得出的结论有重复情况,如果正确结果应该ACC,那么现实的结果就会是:ACC、CAC、CCA三种情况,实际上他们是一个含义。这说明我的递归是采用的排列的方式,如果是采用组合的方式,估计分析的情况就要少很多,程序的速度可能也会快很多。

我的算法水平比较烂,只会写出这个样子,还希望高手帮我优化一下 ,或者提出更好的思路!谢谢!
米的向日葵 2009-07-29
  • 打赏
  • 举报
回复
mark先
上海浪子 2009-07-28
  • 打赏
  • 举报
回复
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
LvMaserati 2009-07-28
  • 打赏
  • 举报
回复
hao wen ti
Waiting4you 2009-07-27
  • 打赏
  • 举报
回复
可以用迭代法,可能不是最优的,但保证可以找到正确结果,伪代码:

// 用堆栈stack来保存物品的组合

bool IsOK(const stack& ); //判断 属性1之和<X and 属性2之和<Y

travel(stack &s)
{
for(int i=0; i<300; i++)
{
s.push(obj[i]); //加入第i件物品
if(IsOK(s)) // 如果条件符合
{
setMaxCond(s); // 如果s里的属性3之和最大,就记录之
travel(s); // 递归,断续加入更多物品
}
s.pop(); //测试完成后取出前面加入的第i件物品(换下一件)
}
}
ydyn1988 2009-07-27
  • 打赏
  • 举报
回复
thinking...
beikeer1986 2009-07-27
  • 打赏
  • 举报
回复
有点难度,好好学习一下
安馨安旭 2009-07-27
  • 打赏
  • 举报
回复
我网上搜索了下,lingo语言好像是专门用来数学建模的,处理算法方面很有用,而且很方便,我该如何将其转化为C++语言?或者提供给C++使用?
加载更多回复(43)

13,825

社区成员

发帖
与我相关
我的任务
社区描述
C++ Builder相关内容讨论区
社区管理员
  • 基础类社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧