【问题标题】:Number Partition Algorithm Generator in Delphi XE8Delphi XE8中的数字分区算法生成器
【发布时间】:2016-06-19 06:04:27
【问题描述】:

如何在Delphi XE8中制作高效且最简单的算法来输出数字列表NPartitions

例如N=4,结果(假设在TListBox中列出):

4
3 + 1
2 + 2
2 + 1 + 1
1 + 1 + 1 + 1 

我尝试了一些东西,决定使用动态数组:

var
  IntegerArray: array of Integer;

数一数,二数,三数,...

这是在TListBox 中输入动态数组:

procedure TMForm.AddItem;
var
  Temp: String;
  I: Integer;
  II: Integer;

begin

  Temp:= '';
  for II:= 0 to Length(IntegerArray)-1 do
  begin

    for I := 0 to (IntegerArray[(Length(IntegerArray)-II)-1]-1) do
    begin
      Temp:= Temp+IntToStr(Length(IntegerArray)-II-1);
      Temp:= Temp+'+';
    end;
  end;

  delete(Temp,length(Temp),1);
  ListBox1.Items.Add(Temp);
end;

并开始编写算法(目前有效,但仅使用数字 1,2 和 3 来写入分区),但似乎我需要重写它以使用递归(因此它将使用所有可用的数字来写入分区),这就是我的问题;这里如何使用递归?

function TMForm.Calculate(MyInt: Integer): Integer;
var
  I: Integer;

begin
  ListBox1.Clear;
  GlobalInt:= MyInt;
  Result:= 0;

  SetLength(IntegerArray, 0);
  SetLength(IntegerArray, (MyInt+1));
  IntegerArray[1]:= MyInt;
  AddItem;
  Result:= Result+1;
  //
  if MyInt>1 then
  begin

    repeat  
      IntegerArray[1]:= IntegerArray[1]-2;
      IntegerArray[2]:= IntegerArray[2]+1;
      AddItem;
      Result:= Result+1;

    until ((IntegerArray[1]/2) < 1 );

    if MyInt>2 then
    repeat
      IntegerArray[3]:= IntegerArray[3]+1;
      IntegerArray[1]:= MyInt-IntegerArray[3]*3;
      IntegerArray[2]:= 0;
      AddItem;
      Result:= Result+1;

      if NOT ((IntegerArray[1]/2) < 1) then
      repeat
        IntegerArray[1]:= IntegerArray[1]-2;
        IntegerArray[2]:= IntegerArray[2]+1;
        AddItem;
        Result:= Result+1;
      until ((IntegerArray[1]/2) <=1 );

      IntegerArray[1]:= MyInt-IntegerArray[3]*3;
      IntegerArray[2]:= 0;
    until ((IntegerArray[1]/3) < 1 );

    //if MyInt>3 then...


  end;

  Edit1.Text:= IntToStr(Result);
end;

运行当前程序的例子:

更新

设法使它像这样工作:

procedure TMForm.Calculate(MyInt: Integer);
var
  I: Integer;

begin
  ListBox1.Clear;
  GlobalInt:= MyInt;
  ItemCount:= 0;

  SetLength(IntegerArray, 0);
  SetLength(IntegerArray, (MyInt+1));
  IntegerArray[1]:= MyInt;
  AddItem;
  ItemCount:= ItemCount+1;
  //
  if MyInt>1 then
  Step2;

  if MyInt>2 then
  for I := 3 to MyInt do
  Steps(I);

  Edit1.Text:= IntToStr(ItemCount);
end;

procedure TMForm.Steps(n: Integer);
var
  I,II: Integer;

begin
  if not ((IntegerArray[1]/n) < 1 ) then
  repeat
    IntegerArray[n]:= IntegerArray[n]+1;
    //
    IntegerArray[1]:= GlobalInt;
    for I:= 3 to GlobalInt do IntegerArray[1]:= IntegerArray[1]-IntegerArray[I]*I;
    //
    AddItem;
    ItemCount:= ItemCount+1;
    Step2;

    if n>3 then
    for II := 3 to (n-1) do
    begin
      Steps(II);
    end;

  until ((IntegerArray[1]/n) < 1 );
  //
  IntegerArray[n]:= 0;
  IntegerArray[1]:= GlobalInt;
  for I:= 3 to GlobalInt do IntegerArray[1]:= IntegerArray[1]-IntegerArray[I]*I;
end;

procedure TMForm.SpinBox1Change(Sender: TObject);
begin
  SpinBox2.Value:= SpinBox1.Value;
end;

procedure TMForm.Step2;
var
  I: Integer;
begin
    if NOT ((IntegerArray[1]/2) < 1) then
    repeat
      IntegerArray[1]:= IntegerArray[1]-2;
      IntegerArray[2]:= IntegerArray[2]+1;
      AddItem;
      ItemCount:= ItemCount+1;

    until ((IntegerArray[1]/2) < 1 );

  IntegerArray[2]:= 0;
  IntegerArray[1]:= GlobalInt;
  for I:= 3 to GlobalInt do IntegerArray[1]:= IntegerArray[1]-IntegerArray[I]*I;
end;

procedure TMForm.FormCreate(Sender: TObject);
begin
  //
end;

但显然,我需要一些优化。

【问题讨论】:

    标签: delphi math numbers firemonkey delphi-xe8


    【解决方案1】:

    你是对的,最简单的实现是递归的。

    有一些优化的可能性(对于较大的值,存储较小值的分区并一次又一次地使用它们会很好),但我认为对于大 N 值,结果列表大小对于输出来说太大了

    //N is number for partitions, M is maximum part value 
    //(used here to avoid permutation repeats like 3 1 and 1 3)
    procedure Partitions(N, M: integer; s: string);
    var
      i: integer;
    begin
      if N = 0 then
        Memo1.Lines.Add(s)
      else
        for i := Min(M, N) downto 1 do
          Partitions(N - i, i, s + IntToStr(i) + ' ');
    end;
    
    begin
      Partitions(7, 7, '');
    

    给出输出

    7 
    6 1 
    5 2 
    5 1 1 
    4 3 
    4 2 1 
    4 1 1 1 
    3 3 1 
    3 2 2 
    3 2 1 1 
    3 1 1 1 1 
    2 2 2 1 
    2 2 1 1 1 
    2 1 1 1 1 1 
    1 1 1 1 1 1 1 
    

    【讨论】:

    • 谢谢!是的,我后来注意到结果列表确实太大了,那我怎么能用最简单的方法只计算分区的数量而不是整个列表呢?
    • 要计算分区数,可以使用动态规划。一种方法是记忆 - 制作一个表(二维数组),在递归期间填充它的条目,或者使用表中的就绪值(如果存在)
    • @Matta 直接添加到 TMemo 很慢,您可以使用一些临时字符串变量 txt 代替,当整个分区完成后将其复制到 Memo1.Text=txtMemo1.Lines.Add(txt)。由于递归和堆/堆栈垃圾处理使用的大量字符串重新分配,这也很慢。相反,您可以使用某种形式的整数列表列表...将其预分配到某个合理的大小(紧贴N),这应该会大大加快速度。或者用空格预分配你的字符串,并在不重新分配的情况下逐步插入它们
    • @MBo 一段不错的代码 (+1) 我将它移植到 VCL/C++ 并带有我上面提到的字符串输出,如下所示: AnsiString Partitions(int n,int m= 0,AnsiString s="") { if (!m) m=n; if (!n) { if (s=="") return ""; s[s.Length()]='\r';返回 s+"\n"; } 诠释我; AnsiString txt="";我=米;如果 (i>n) i=n; for (;i;i--) txt+=分区(n-i,i,s+AnsiString(i)+'+');返回.txt; }
    • @Matta 如果您只想要分区的数量,那么只需从递归中删除字符串参数,而是添加到一些整数变量(可以在递归尾部之外以加快速度)。那么即使你不改变算法,由于大量的情人堆/堆栈垃圾,这会更快得多
    【解决方案2】:

    从您的链接中可以看到:Fast Algorithms for Generating Integer Partitions

    在那里实现建议的最快算法(ZS1 和 ZS2)如下所示: (注意,这里没有递归!)

    procedure PartitionsZS1(n: Integer);
    var
      x: TArray<Integer>;
      i,r,h,t,m: Integer;
    begin
      SetLength(x,n+1);
      for i := 1 to n do x[i] := 1;
      x[1] := n;
      m := 1;
      h := 1;
      WriteLn(x[1]);
      while (x[1] <> 1) do begin
        if (x[h] = 2) then begin
          m := m + 1;
          x[h] := 1;
          h := h - 1;
        end
        else begin
          r := x[h] - 1;
          t := m - h + 1;
          x[h] := r;
          while (t >= r) do begin
            h := h + 1;
            x[h] := r;
            t := t - r;
          end;
          if (t = 0) then
            m := h
          else begin
            m := h + 1;
            if (t > 1) then begin
              h := h + 1;
              x[h] := t;
            end;
          end;
        end;
        for i := 1 to m do Write(x[i]);
        WriteLn;
      end;
    end;
    
    procedure PartitionsZS2(n: Integer);
    var
      x: TArray<Integer>;
      i,j,r,h,m: Integer;
    begin
      SetLength(x,n+1);
      for i := 1 to n do x[i] := 1;
      for i := 1 to n do Write(x[i]);
      WriteLn;
      x[0] := -1;
      x[1] := 2;
      h := 1;
      m := n - 1;
      for i := 1 to m do Write(x[i]);
      WriteLn;
      while (x[1] <> n) do begin
        if (m-h > 1) then begin
           h := h + 1;
           x[h] := 2;
           m := m - 1;
        end
        else begin
          j := m - 2;
          while (x[j] = x[m - 1]) do begin
            x[j] := 1;
            j := j - 1;
          end;
          h := j + 1;
          x[h] := x[m - 1] + 1;
          r := x[m] + x[m - 1]*(m-h-1);
          x[m] := 1;
          if (m - h) > 1 then
            x[m-1] := 1;
          m := h + r - 1;
        end;
        for i := 1 to m do Write(x[i]);
        WriteLn;
      end;
    end;
    

    program Project61;
    
    {$APPTYPE CONSOLE}
    
    begin
      PartitionsZS1(7);
      WriteLn;
      PartitionsZS2(7);
    end.
    

    输出:

    7
    61
    52
    511
    43
    421
    4111
    331
    322
    3211
    31111
    2221
    22111
    211111
    1111111
    
    1111111
    211111
    22111
    2221
    31111
    3211
    322
    331
    4111
    421
    43
    511
    52
    61
    7
    

    【讨论】:

    • 丢失了一些分区(52 421 等)
    • @MBo,看起来是这样。我会看看我是否在某个地方误解了。不过现在还做不到。
    • @MBo,好的问题已解决。出版物中缺少括号。
    • @Matta,就像 MBo 说的,如果你把数据放入一个二维整数矩阵而不是使用字符串,它会提高速度。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-10
    • 1970-01-01
    • 2014-01-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多