求解一最优分割算法,是不是太多的开发工具让大家不再专注于算法研究了...... (300分)

  • 主题发起人 主题发起人 redchild
  • 开始时间 开始时间
设一批基本钢管长为L
要分割为一批长度如下的钢管
长度为N1,数量为Q1
长度为N2,数量为Q2
.................
长度为Nx,数量为Qx
N1....Nx中任意一个数不会大于L
穷举法:
设总共所需最少根数为M,则最优化结果将分割得到的钢管分成M组,设组钢管
长度值的和分别为L1,L2...Lm
则有:
Lj=∑Aij*Ni (i=1,2..X,j=1,2..M,A=0,1,2,..Qi,0<Lj<=L,∑Aij=Qi)
i j
M的最大可能为:M_max=∑Qi (i=1,2..X)
M的最小可能为:M_min=∑Qi*Ai div L

穷举的参数为:
M:从M_min到M_max;
Aij: Ai1=(0,1,..Qi);
Ai2=(0,1,..(Qi-Ai1));
.....................
Aim=(0,1....(Qi-Ai1-Ai2-.....-Ai(m-1)) )
找到满足要求的 M 即停止。
i
穷举的次数:T<M*∏Qi!

有了点想法就写了写,不知错了没有
 
问题在于运算规模和具体的实现.
这样的问题,性能很重要的!
 
这个是当然,其实需要穷举的参数空间并没有那么大,M_max,和Aij都可以很大幅度
的压缩,那样穷举的总次数会急剧减小。我在想,能不能将问题略为转化一下呢?
前提:因为这个问题的解空间是可穷举的 M∈(M_min,M_max),M_min,M_max同上,
如果M_max非常大,我们认为相应增长的代价是值得的,
转化:在上面的前提下,我们穷举M,然后将问题转化为,能否用M根长为L的钢管
分割成相要的结果(只求M则只需给出,YES or NO,如果是实际问题还需要
知道怎样分割)
然后:从M_min向M_max循环判断,找到的一个M就是所求根数。

我认为给出题目中当Ni,Qi,X,L等因素发生变化时,对算法的稳定性要求很高,需
要有些手段进行预处理一下。当然,如果只求近似解则可能要简单的多,如果你即
要求精确解,还要求性能,还要求稳定性,有点难吧!
 
当然有难度了,其实还有更复杂的,我都不好意思贴上来了
 
这个问题到现在还在呀?
 
我觉得这问题,想一下子搞出来,实是不易,不如一步一步来,鼓励大家多提思路,
再针对几个可行的思路,分成几组进行改进,机会总在探索和尝试中获得嘛,比如
你和LeeChange那段对话就很好哦
 
M∈(M_min,M_max)??
M_min和M_max计算就是有问题的,m的值域在2^2^2...成几何级数增长,不是你们想的那么简单。
所以说是NP问题。
讨论可以结束,对于np问题,我们能有局部最优解就可以了:)
 
市面上有实现类似功能的软件,不知他们用的是不是贪婪算法,其实还有更复杂的问题,
那就是平方板的分割,基件和子件的都是各不同大小的,我就遇到这样的客户,业务员在
签单时没把客户要求弄清,后来开发到试用期后,客户再提出来,我一估算,太可怕了,只
好把单退了!我想,要求全局最优解,并不比"电脑围棋"简单多少,只不过电脑围棋的参
是随机可变的,但一样的问题就是运算量大得连现有最先进的PC都无法让人在感性上
承受它的性能!但我想要的是大家的讨论,可以在局部最优解上得到可以让人承受的解!
 
To darkiss:
可能是我想的不对,我对算法这些不是很熟,但依题意所说:N1,N2.。。Nx皆小
于L,最坏的情况就是每一段Ni长度的钢管都各自用去长为L的钢管,所用的最多的
根数 M_max不是等于:Q1+Q2+.....Qx吗?
 
2 beyondair:
不是这样的,而是几何次方和运算量成长,很大的!如果只是Q1+Q2+...+Qx的话,就犯
不着这么多的人来讨论了!
 
不知道为什么,这道题目做不出来,心里总是不踏实,脑子里总有一个心事在
那里,如果毕业后老板就让我写一个这样的东西,我能回答说,不会吗?
要找到一个完美的组合,还是有难度的,而且不是唯一的。
经过一个一段时间的考虑,写了一个自己比较满意的算法,
效率是非常高的,至于空间就大了一点了。因为很难把意想
表达出来,所以还是把它贴出来了,肯定还有值得改进的地方
(如:Bakgood()这个过程),只是给大家做一个参考,也许rechild还是不满意,
希望你能把找到的那个 GLarray[][] 数组贴出来,我再来改进算法。
我用不同GLarray数组,做了不少测试,分解过程还是可以的。

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Memo2: TMemo;
procedure Button1Click(Sender: TObject);
procedure pro(sender:tobject;L:integer);
function IsFirst(sender: Tobject;currentPos:integer):boolean;
//判断当前位置是不是要分割的最长的一个。
function prvIsNull(sender:Tobject;Pos:integer):boolean;
procedure screenCtoB(sender:Tobject);
procedure CtoB(sender:Tobject;Pos,size:integer);
procedure Bakgood(sender:Tobject);
function proBYushu():integer;
procedure recover(i:integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
type
TGLrecored=record
GLleng:integer;
GLcount:integer;
end;
var
L:integer;
//这个L就是你问题中的那个L
GLarray:array[1..10] of TGLrecored;
//我们假设所有的钢管的类型,数量都放在这个数组中;
resultarray :array of array of integer;
a,b,Bak:array[1..10]of integer;
//a,b为临时数组,bak 为上
minL,Lcount,BakLcountPos,BakYuShu:integer;
AItag:array[1..10]of boolean;
c:array[1..50,1..10]of integer;
//我们把每次找到的最佳组合放在C中。最后筛选出一个我们认为比较好的放在B中。因为也不一定是正确的组合
cCount:integer;
//c数组中的有效行的计数器
implementation
{$R *.DFM}
procedure TForm1.Bakgood(sender: Tobject);
var
BYuShu,i,j,k,s1,s2,n:integer;
begin
screenctob(sender);
i:=10;
j:=10;
while (bak=0) and (i>0) do
dec(i);
while (b[j]=0) and (j>0)do
dec(j);
if (i=0) or (j>=i) then
begin

n:=10;
//N为要分割钢管的种类的大小。
BYushu:=probyushu;
//for i:=1 to 10do
// if b<>0 then
BYuShu:=BYuShu-Glarray.GLleng*b;
if BYuShu=BakYuShu then
begin
for i:=1 to 10do
if b<>0 then
break;
for j:=1 to 10do
if Bak[j]<>0 then
break;

if i=j then
begin
s1:=0;
s2:=0;
while (s1=s2) and (n<>0)do
begin
s1:=0;
s2:=0;
for k:=1 to n div 2do
begin
s1:=s1+bak[k];
s2:=s2+b[k];
end;
n:=n div 2;
end;
if s1>s2 then
begin
bak:=b;
BakLcountPos:=Lcount;
end;

end;

if i>j then
begin
Bak:=b;
BakLcountPos:=Lcount;
end;
end;
if BYuShu<BakYuShu then
begin
bak:=b;
BakYuShu:=BYuShu;
BakLcountPos:=Lcount;
end;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var i,j,k:integer;
Scount:integer;//longword;
s:string;
begin
GLarray[1].GLleng:=5;
GLarray[1].GLcount:=12;
GLarray[2].GLleng:= 11;
GLarray[2].GLcount:=12;
GLarray[3].GLleng:=24;
GLarray[3].GLcount:=12;
GLarray[4].GLleng:=27;
GLarray[4].GLcount:=12;
GLarray[5].GLleng:=29;
GLarray[5].GLcount:=12;
GLarray[6].GLleng:=33;
GLarray[6].GLcount:=12;
GLarray[7].GLleng:=35;
GLarray[7].GLcount:=12;
GLarray[8].GLleng:=36;
GLarray[8].GLcount:=12;
GLarray[9].GLleng:=47;
GLarray[9].GLcount:=0;
GLarray[10].GLleng:=53;
GLarray[10].GLcount:=0;
{必须要先对GLayyay这个数组按GLleng排序,我们假设已经排过了。}
L:=100;
//这个L就是你问题中的那个L
scount:=0;
for j:=1 to 10do
scount:=scount+GLarray[j].GLleng*glarray[j].GLcount;
k:=scount div L *2;
setlength(resultarray,k,11);
BakYuShu:=L;
LCount:=0;
j:=1;
while j<>0do
begin
inc(LCount);
minL:=L;//GLarray[1].GLleng;
for j:=1 to 10do
begin
AItag[j]:=true;
a[j]:=0;
end ;
cCount:=0;
pro(sender,L);
//开始分割一个L型号的管子 。结果放在C数组中
screenCtoB(sender);
//筛选C数组中的组合->B中 。
i:=1;
j:=0;
while i<=10do
begin
resultarray[LCount]:=b;
GLarray.GLcount:=GLarray.GLcount-b;
j:=j+GLarray.GLcount;
inc(i);
end;

if (probyushu>GLarray[1].GLleng) and (j>0)then
begin
recover(Lcount);
Lcount:=BakLcountPos;
j:=0;
for i:=1 to 10do
begin
resultarray[LCount]:=bak;
GLarray.GLcount:=GLarray.GLcount-bak;
j:=j+GLarray.GLcount;
end;
end;
end;
for i:=1 to Lcountdo
begin
for j:=1 to 10do
begin
s:=s+inttostr(resultarray[j])+' ';
end;
memo2.Lines.Add(s);
s:='';
end;
ShowMessage('经过计算至少需要:'+inttostr(LCount)+'个L型号的管子');
resultarray:=nil;
end;

procedure TForm1.CtoB(sender: Tobject;
Pos,Size: integer);
//把C数组中的Pos行放入B中
var
i:integer;
begin
for i:=1 to Sizedo
b:=c[pos];
end;

function TForm1.IsFirst(sender: Tobject;currentPos:integer): boolean;
var
i :integer;
begin
result:=false;
i:=10;
while i>currentPosdo
if a<>0 then
break
else
dec(i);
if i=currentPos then
result:=true;
end;

procedure TForm1.pro(sender: tobject;
L: integer);
var
i,j,k:integer;
begin
i:=10;
while ((i>0) and (GLarray.GLcount=0)) or (not (AItag)) or (L<GLarray.GLleng) do
dec(i);
if i>0 then
begin
a:=L div Glarray.GLleng;
if a>GLarray.GLcount then
a:=GLarray.GLcount;
j:=a;
while j>=0do
begin
if (j=0) and (IsFirst(sender,i)) and (not prvISnull(sender,i)) then
break;
AItag:=false;
a:=j;
if L-GLarray.GLleng*j=Minl then
begin
if cCount<50 then
begin
for k:=1 to 10do
c[cCount+1][k]:=a[k];
inc(cCount);
end;
end;

if L-GLarray.GLleng*j<Minl then
begin
if cCount>0 then
bakgood(sender);
for k:=1 to 10do
c[1][k]:=a[k];
cCount:=1;
Minl:=L-GLarray.GLleng*j;
end;

IF i<>1 then
pro(sender,L-GLarray.GLleng*j);
dec(j);
for k:=1 to ido
AItag[k]:=true;
end;
end;
end;
function TForm1.proBYushu: integer;
var
i,n:integer;
begin
n:=L;
for i:=1 to 10do
if b<>0 then
n:=n-Glarray.GLleng*b;
result:=n;
end;

function TForm1.prvISnull(sender: Tobject;
Pos: integer): boolean;
var
i:integer;
begin
result:=false;
i:=Pos-1;
while i>=1do
begin
if c[1]<>0 then
break;
dec(i);
end;
if i=0 then
result:=true;
end;

procedure TForm1.recover(i: integer);
var
j,k:integer;
begin

for j:=BakLcountPos to ido
for k:=1 to 10do
if resultarray[j][k]<>0 then
GLarray[k].GLcount:=Glarray[k].GLcount+resultarray[j][k];
BakYuShu:=100;
end;

procedure TForm1.screenCtoB(sender: Tobject);
var
i,j,k,p,s1,s2,n:integer;
begin
p:=0;
n:=10;
for i:=1 to cCountdo
begin

for j:=1 to 10do
if c[j]<>0 then
break;
if j=p then
begin
s1:=0;
s2:=0;
while (s1=s2) and (n<>0)do
begin
s1:=0;
s2:=0;
for k:=1 to n div 2do
begin
s1:=s1+c[k];
s2:=s2+b[k];
end;
n:=n div 2;
end;
if s1<s2 then
Ctob(sender,i,10);
end;
if j>p then
begin
CtoB(sender,i,10);
p:=j;
end;
end;

end;

end.
 
我觉得用穷举法或用回溯法都可以,将所有组合都举出来,然后比较一下,
得出最小的值,取的料就最少。
不过如果数量多的话,运行起来会很慢。解决慢的问题就交给你了。
 
呵呵,现在还有这么多同道关心算法呢。对于这个问题我也非常感兴趣,说说自己的看法。
首先,看到这个问题的时候我相信最优解是存在的,所以最优算法肯定存在,大不了穷举
嘛,只是性能上有影响。我的想法如下:
1、需要领最少的料==>边角料最少
2、目标长度优先数=F(L MOD Length) {F应该为一个经验函数,优先数越小越不好用}
3、在目标中找到总优先数最小的组合方式
4、按照3上找到的组合进行加工,直到这种组合方式会产生多于目标数量的某种管子则重复3
这些想法我没有试验过,感觉对于随机的目标要求能做一定的优化,但肯定不会是最优。
如果大家又什么好的算法,请通知我哦。gh@hereinfo.com
 
上面第三步应该是F(优先数,边角料)最小,F 为经验函数,不好意思了。^_^
 
修正两个BUG :
在Button1Click 过程中:
k:=scount div L *2 ;
改为:k:=scount div L *3
if (probyushu>GLarray[1].GLleng) and (j>0) then
    改为:if (probyushu>GLarray[1].GLleng) and (j>0) and (BakLcountPos>0) then
 
如果钢管换成板材;长度换成矩形面积,怎么办?
 
这是典型的整数线性规划问题,请看一篇论文:
一、实用求解算法
线材下料问题可描述为:在一批长度为L的毛坯上,下m种长度为l1、l2、…、lm的部件分别为b1、b2、…、bm根,料头为c、料缝宽度为d前提下,如何下料最省料?
下面以原材料消耗的总根数最少为目标函数来建立数学模型。设共有n种切割方式,aij为第j种切割出的第i种部件的数量,按第j种切割方式下料的原材料根数为xj,则其数学模型为线性整数规则模型:
   min∑Xj (j=1 to n) 
s.t ∑aijχj≥bi,i∈[1,m] (j=1 to n)
Xj≥0且为整数,j∈[1,n]
   由于线性整数规划模型不好求解,所以先将上述模型的整数约束去掉,变为线性规划,令:
   A=(Aij)m×n∈Rm×n;
   X=(χ1,χ2,Λ,χn)T∈Rn;
   b=(b1,b2,Λ,bm)T∈Rm,则可写成矩阵形式;
   min∑Xj (j=1 to n)
   s.t {AX≥b, i∈[1,m]
{xj≥0, j∈[1,n]
式中:χj—决策变量,用第j种切割方式下料的原材料的根数;
aij—第j种切割方式切割出的第i种部件的数量;
bi—第i种部件的需求量;
m—套裁下料的部件种类总数;
n—切割方式总数。
求解上述模型的简明方法是先找出所有的可行下料方案,再优化组合。然而,塑料门窗线材需下料的部件种类较多,并且L与li的比值较大,因此,可切割方式的数量可能会很多,这给求解线性规划问题带来了困难。对可行下料方案进行优选是解决可行下料方案数量巨大问题的有效手段。文献报道的可行下料方案优选方法有子优下料方法、余量百分率控制法、分支一定界方法、列生成方法等,其中以列生成方法最为流行。本文运用列生成方法,用改进的对偶单纯形法求解线性规则问题,用启发式搜索算法求解背包问题,用Gomory割平面法求解整数规划问题,从而实现了可行下料方案的优选。下面仅给出简要的步骤:
(1)先构造m个可行切割方式,由这m个切割方式构成一个矩阵A=(aij)m×N,并保证AX≥b有可行解,令N≤m。
(2)用改进对偶单纯形法求解关于A阵的线性规划最优解,并计算关于最优解的影子价格向量CBB-1。
(3)求解一维背包问题
max∑-CBB-1A.j  (j=1 to m)
s.t {A.j=(a1j,a2j,A,amj)T∈Rm 代表可行的切割方式
{0≤aij≤bi,且为整数
若切割方式向量能使1-maxΣ(CBB-1A.j)≥0,其中j∈[m+N+1,n],则问题得到最优解,转(5);否则转(4)。
(4)A阵增加一列,把新的切割方式向量嵌入A阵作为A阵的第N+1列,令N?N+1,转(2)。
(5)判断解向量是否为整数解。是:则转(6);否:则用Gomory割平面法求解关于A阵的整数线性规则,转(6)。
(6)输出下料结果。
 
二、实例分析
表1为某塑料厂承接的一住宅楼门窗的线材部件规格及下料数量。设定毛坯长度均为6000mm,料头及料缝均为4mm。
本文算法编制的计算机程序的优化下料和人工下料(人工下料采用从大到小边角料利用法,即先将所需的各种线材部件从大到小排序,先下够大的部件,余料若还能下小部件,就进行利用,再依次下够其它部件,剩下的边角料就当废料处理)的材料利用率对比结果见表2。
由表1、表2可以看出,当零部件的数量较多或毛坯长度和部件长度之比较大时,优化下料的材料利用率明显高于人工料的材料利用率。
限于篇幅,这里仅给出2种具有代表性的材料的优化下料方案:
(1)BR60
共需毛坯217根,切割方式共有4种:
第1种切割方式为:940mm的部件下2根、1470mm的部件下3根,有84根材料按该种切割方式下料;
第2种切割方式为:940mm的部件下1根、2670mm的部件下2根,有56根材料按该种切割方式下料;
第3种切割方式为:1470mm的部件下4根,有21根材料按该种切割方式下料;
第4种切割方式为:1440mm的部件下4根,有56根材料按该种切割方式下料;
(2)钢 衬
共需毛坯465根,切割方式共12种:
第1种切割方式为:1380mm的部件下1根、1440mm的部件下2根、1740mm的部件下1根,有112根材料按该种切割方式下料;
第2种切割方式为:880mm的部件下1根、910mm的部件下1根、1230mm的部件下2根、1740mm的部件下1根,有108根材料按该种切割方式下料;
第3种切割方式为:630mm的部件下3根、2040mm的部件下2根,有49根材料按该种切割方式下料;
第4种切割方式为:1230mm的部件下2根、1440mm的部件下1根、2040mm的部件下1根,有4根材料按该种切割方式下料;
第5种切割方式为:630mm的部件下1根、880mm的部件下6根,有18根材料按该种切割方式下料;
第6种切割方式为:630mm的部件下1根、2640mm的部件下2根,有56根材料按该种切割方式下料;
第7种切割方式为:880mm的部件下1根、910mm的部件下1根、2040mm的部件下2根,有4根材料按该种切割方式下料;
第8种切割方式为:630mm的部件下1根、1740mm的部件下3根,有1根材料按该种切割方式下料;
第9种切割方式为:1440mm的部件下4根,有110根材料按该种切割方式下料;
第10种切割方式为:630mm的部件下2根、880mm的部件下3根、2040mm的部件下1根,有1根材料按该种切割方式下料;
第11种切割方式为:880mm的部件下1根、1440mm的部件下2根、2040mm的部件下1根,有1根材料按该种切割方式下料;
第12种切割方式为:1440mm的部件下2根、1740mm的部件下1根,有1根材料按该种切割方式下料。
(文中图表详见《门窗幕墙与设备》月刊2001年第11期第36--38页)
 
原文中所提的表未见到,还提到了考虑下料时的料缝和料头问题,也就是下料时需要消耗一定长度,截断数量不同,消耗也不同,应该很专业了吧?
记得某本运筹学教材还有个简单些的例子。
代码就请张辉明大侠继续吧:)
看似简单的问题,其实考虑周全了够写篇硕士论文的,难怪!
 
后退
顶部