请教算法高手!问题解决我给500分!(200分)

  • 主题发起人 主题发起人 cgh1970
  • 开始时间 开始时间
C

cgh1970

Unregistered / Unconfirmed
GUEST, unregistred user!
我要从1~33的数字中任意取6个的数做不重复的排列,不要用循环的那种,那样的话速度很慢,请问有没有更快的算法?谢谢!问题解决我给500分!
 
轉貼:

Delphi中巧用递归实现组合
作者: 评价: 上站日期: 2001-06-29
内容说明:
来源:

--------------------------------------------------------------------------------

  在编程的过程中,我们常常要碰到这样一个问题:有n个数,要从其中取出m(m< =n)个数,列出所有可能的组合。一般来说,使用循环实现,但是逻辑复杂,不容易调试。而使用递归算法,可以大大简化程序。
  使用递归实现一个算法时,首先要对问题进行降级处理,就是对于处理n的问题,要转化为n-1或者n-2,…..的问题。其次,就是要确定递归结束条件。如果没有递归结束条件,程序就无法结束,导致系统资源消耗殆尽。
  首先我们分析一下:对1到n所有的整数求和的问题。传统的循环实现程序如下:
  function sum(n:integer):integer;
  var
   I:integer;
  Begin
   Result:=0;
   For I:=1 to n do
    Result:=result+I;
  End;
  而用递归实现,我们可以得到sum(n)=n+sum(n-1),这就是降级后的结果;如果n=1,那么和肯定为1,不用再进行递归,这就是递归结束的条件。
  所以这个问题可以用以下递归程序实现:
  function sum(n:integer):integer;
  Begin
   If n=1 then
    Result:=1
   Else
    Result:=result+sum(n-1);
  End;

  下面我们对组合问题进行分析。
  要从n(假设这n个数为1到n)个数中取出m个数,我们可以先确定是否有第一个数,如果有1,那么从余下的n-1个数中取出m-1个就可以了;而要是没有1,则需要从余下的n-1个数中选出m个数。这样,我们就把问题进行了降级处理,那么什么时候递归结束呢?如果要从若干个数中取出0个,那么说明不需要再进行选取了,认为递归结束;另外一种情况是,要从p个数中选出p个数,这时确定的选法就是这p个数,所以也可以把这p个数作为结果,然后结束递归。这样,我们就确定了组合问题的降级处理和结束条件。
  我们用过程select来实现组合选数。参数setvailable代表可供选择数的集合,m表示需要从集合setvailable中选取m个元素,setselected表示已经选出来的元素。
  具体实现如下:
  procedure select(setavailable:set of 1..n,m:integer;setselected:set of 1..n);
  begin
   if m=0 then exit
//不需要选择,结束递归
   if CountOfElement(setavailable)=m then
   //从m个数中选出m个,那么把setavailable和setselected的并集作为结果,结束递归
   begin
    puttoresult(setavailable+setselected);
    exit
   end;
   //到此,如果没有结束,则需要对问题进行降级
   select(setavailable-[FirstElement(setavailable)],m-1,setselected+[ FirstElement(setavailable)]);
   //选中集合中的第一个元素,然后从余下的元素中选取m-1个元素
   select(setavailable-[FirstElement(setavailable)],m,setselected);
   //若不选择第一个元素,则从余下的元素中选取m个元素
  end;
  在上面的程序中,FirstElement从一个集合中取出最小的元素,CountOfElement计算一个集合中的元素个数,puttoresult把得到的组合记录下来,这些函数和过程的实现略去。
  至此,组合选数的问题就解决了,从以上的程序中,我们可以看出,对于有些问题,用递归来实现非常简单,而且逻辑清晰。
 
循环就是最快的!!!!
 
递归的效率比循环低。
 
如果你选择了我的算法,别忘了给我分啊,我现在很穷[:(]
相信这是调用 Random 函数最少的算法了。

procedure TForm1.Button1Click(Sender: TObject);
procedure SwitchValue(var A: Integer
var B: Integer);
begin
A := A xor B;
B := A xor B;
A := A xor B;
end;
var
ResultArray: array[1..33] of Integer;
I: Integer;
Str: string;
begin
// 初始化值,只需要进行一次
for I := 1 to 33 do ResultArray := I;

// 随机选择值,每个值被选中的的概率相等(第一次除外)
for I := 1 to 6 do
SwitchValue(ResultArray, ResultArray[I + 1 + Random(33-I)]);

// 显示结果
for I := 1 to 6 do
Str := Str + IntToStr(ResultArray) + '
';
ShowMessage(Str);
end;
 
对不起,我没有说清楚,我要的是列出所有的从1~33的数字中任意取6个的数做不重复的排列。
 
procedure TForm1.BitBtn1Click(Sender: TObject);
var
iv, i : Integer;
vs : array[0..5] of Integer;
begin
FillChar(vs, SizeOf(vs), 0);
iv := 0;
for i := 0 to 5 do
begin
while True do
begin
iv := Random(33);
if not (iv in [vs[0], vs[1], vs[2], vs[3], vs[4], vs[5]]) then Break;
end;
vs := iv;
end;
ShowMessage(Format('%d,%d,%d,%d,%d,%d',[vs[0], vs[1], vs[2], vs[3], vs[4], vs[5]]));
end;
 
您说的不用循环,是指哪里不用循环呢?一点循环不用...我不知道[:(],
但还是希望下面代码对您有帮助。您运行看看吧。我感觉还是蛮快的!
var
val:integer
//随机数
i:integer;
Myset:set of 1..33 ;//集合范围
begin
Myset:=[];
for i:=1 to 6 do //这里可以用循环吧?是否允许? ^_^
begin
while true do //如果取出来的数在集合中,说明上次已经取过了,要再取一次
begin
Randomize;//初始化随机数生成器
val:=random(32)+1
//取随机数(1..32)
if not(val in Myset) then //如果不在集合中...说明没有重复
begin
showmessage(inttostr(val));//这里可改为 数组名:=val;
Include(Myset,val)
//添加元素到集合中
Break
//跳出循环
end;
end;
end;
end;
 
1、如果不是彩票号码,允许 123456和132456的的组合,一共有33*32*31*30*29*28=797448960种组合,每个都取出来没有意义了。

2、如果是彩票号码,还要进行从小到大的排列,需要6个循环:
for i1:=1 to 28 do begin
for i2:=i1+1 to 29 do begin
for i3:=i2+1 to 30 do begin
for i4:=i3+1 to 31 do begin
for i5:=i4+1 to 32 do begin
for i6:=i5+1 to 33 do begin
s:=inttostr(i1) +' '+ inttostr(i2) +' '+ inttostr(i3) +' '
+ inttostr(i4) +' '+ inttostr(i5) +' '+ inttostr(i6);
end;
end;
end;
end;
end;
end;

只有1107568个数。加个特码总共有1107568*33=36547944个组合
 
to skadon
做循环的话要很长的时间。
大家看一下双色球大赢家,它可以用19秒的时间把1~33个数中任意6个数的组合(共1107568个)全部显示出来,不知道他是用什么方法?
 
1107568个组合不算多,存在文件和数据库中均可
 
上面的代码,不计算s:=inttostr(i1) +' '+ inttostr(i2) +' '+ inttostr(i3) +' '
+ inttostr(i4) +' '+ inttostr(i5) +' '+ inttostr(i6);
只需要10ms。计算s需要17秒。P4 1.4G,256MB,主板400MHz
 
s:=format('%d,%d,%d,%d,%d,%d',[i1,i2,i3,i4,i5,i6]);
 
搞了半天是彩票,误会楼主意思了,呵呵!
skadon的机子好棒啊!好羡慕! (相对我而言,你们谁可怜我,送我条内存?)
 
优化代码:
var
i,i1,i2,i3,i4,i5,i6 :integer;
D:DWORD;
s:string;
sa:array[1..33]of string;
Ts : TStringList;
begin
i:=0;
D:=GetTickCount;
for i:=1 to 33 do
if i<10 then
sa:='0' + inttostr(i) + ' '
else
sa:=inttostr(i) + ' ';
Ts := TStringList.Create ;
for i1:=1 to 28 do begin
for i2:=i1+1 to 29 do begin
for i3:=i2+1 to 30 do begin
for i4:=i3+1 to 31 do begin
for i5:=i4+1 to 32 do begin
for i6:=i5+1 to 33 do begin
i:=i + 1;
Ts.Add(sa[i1] + sa[i2] + sa[i3] + sa[i4] + sa[i5] + sa[i6])

end;
end;
end;
end;
end;
end;
Memo1.Lines.Add(Inttostr(GetTickCount-D))

Ts.SaveToFile('HM.txt');
Ts.Free;
Memo1.Lines.Add(Inttostr(GetTickCount-D))


执行结果
2334
4827
 
建议用递归方法
 
to skadon
请问我如果要从1~33的数字中随机有16个数做任意取6个的数做不重复的排列该怎样做呢?
谢谢!
 
这种递归不得了,我以前用C语言写过,10的排列要做十几分钟(可能是我的电脑比较烂)。是否应该考虑以下用MATLAB。
 
“请问我如果要从1~33的数字中随机有16个数做任意取6个的数做不重复的排列该怎样做呢?”

cgh1970:这个问题我不是很明白,请具体、详细的说明白啊。
 
后退
顶部