排列、组合及算法

排列,组合是高中数学中的基本内容之一。我们在程序设计中对具体问题进行

分析,建立数学模型时会经常涉及到这方面的知识。因此,掌握排列、组合及算法

是学习程序设计和掌握其它算法的基础。

    一:排列、组合概念

    定义1:从N个不同元素中,任选M个元素(M≤N),按照一定的顺序排成一队,

叫做从N个元素中取M个元素的一个排列。当M=N时叫做N的全排列。

    定义2:从N个不同元素中,任选M个元素(M≤N)而不考虑其次序时, 称为从N

个元素中取M个元素的一个组合。

    例1:一个小组中有四名同学,选出其中三人到三个车间参加劳动。问:共有

多少种不同的分配方案?

    分析:我们用1,2,3,4表示这四名同学。把三个车间看做分别固定在从左到右

的位置上不动,然后从四名同学中任选三人按从左向右的顺序排成一队,这就对应

一种分配方案。所以这是一个从四个不同元素中任选三个元素的排列问题。共有如

24种排法:(如图11.0所示)

            ┌────────┬────────┬────────┐

           1│               2│               3│               4│

                                                           

      ┌──┼──┐    ┌──┼──┐    ┌──┼──┐    ┌──┼──┐

     2│   3│   4│   1│   3│   4│   1│   2│   3│   1│   2│   3│

                                                 

    ┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐┌┴┐

      ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││ 

      ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││ 

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

                     (图11.0)

    从上到下,各边的标号就是一种排列,从左到右依次为:

    123,124,132,134,142,143,213,214,231,234,241,243,312,314,321,324,341

,342,412,413,421,423,431,432。

    例2:有甲、乙、丙、丁四位同学,从中选出三位同学参加义务劳动. 问:有

多少种不同的选法?

    分析:我们用1,2,3,4表示这四位同学,由于选出的三们同学与顺序无关,所

以这是从四个不同元素中选三个元素的组合问题。共有如下四种不同的选法:

    123,124,134,234。

 

二:求排列、组合方案的基本算法──循环嵌套

    例3: 从1,2,3,4,5这五个数中任选三个,不许重复.问:共能组成多少个不同的

三位数,并把它们打印出来。

    分析:显然这是一个从五个不同元素中任选三个元素的排列问题。我们可以利

用三重循环来解决.循环变量的取值范围为1--5。在内循环体内加入判断,排除取值

两两重复的情况。程序如下:

  program pl;

   var a,b,c,t:integer;

   begin

    t:=0;

    for a:=1 to 5 do

     for b:=1 to 5 do

      for c:=1 to 5 do

       if (a<>b) and (a<>c) and (b<>c) then

        begin

         writeln(a,b,c);                       {打印一种排列方案}

         t:=t+1;                               {t:统计总数}

        end;

    writeln('Total: ',t);

   end.

    例4:有甲、乙、丙、丁、戊、己六位同学,从中任选三位同学做游戏, 问:共

有多少种不同的选法,并把结果打印出来。

    分析:由于选出的三位同学是做游戏与顺序无关,所以这是一个从六个不同元

素中任选三个元素的组合。我们用1,2,3,4,5,6表示这六位同学,选法共有如下20种:

    123,124,125,126,134,135,136,145,146,156,

    234,235,236,245,246,256,345,346,356,456。

从上面的组合生成过程中,可以看出如下规律:

1、最后一位数字可达N(上例中N=6),倒数第二位最大可达N-1,...依次类推,

倒数第K位不得超过N-K+1

2、对于每一种组合,从左向右观察,左边的数字均小于右边的数字。

   根据以上规律,我们同样可利用三重循环来解决这个问题。最外层的循环变量

A,对应最左边一位数字,变化范围为1--4;第二层循环变量B对应中间一位数字,变

化范围为(A+1)--5;内循环变量C对应最右边一位数字,变化范围(B+1)--6。程序

如下:

  program zh;

   var

     a,b,c,t:integer

   begin

    t:=0;

    for a:=1 to 4 do

     for b:=a+1 to 5 do

      for c:=b+1 to 6 do

       begin

        writeln(a,b,c);

        t:=t+1;

       end;

    writeln('Total: ',t);

   end.

 

 三、求排列、组合方案的算法二

    上面介绍的用多重循环产生排列,组合数的方法有两点不足之处。第一,受循

环嵌套层次的限制(一般不能超过八层)。第二,必须给出n,m的确切值,即选出元

素的个数必须在编程前确定,因为他决定了循环嵌套的层次。但有些问题却往往是n,

m的值在程序运行中才从键盘输入的。此时就无法用上述方法编程。下面我们介绍

产生排列,组合数的另一种方法。它是在深入分析排列组合数产生规律的基础上,编

程实现的。

    首先,让我们回过头来观察一下例1中排列数产生的规律:

    123,124,132,134,142,143

    213,214,231,234,241,243

    312,314,321,324,341,342

    412,413,421,423,431,432。

第一个排列数123是按自然数顺序产生。 然后最高位递增,当达到最大值N时,退一

,考虑倒数第二位递增1,其后各位仍由小到大逐个递增(当然整个过程中不许有重

复数字出现),当倒数第二位达到最大值N时,再退一位考虑...。下面先给出实现此

算法的程序框架。

        ┌──────────┐

              初始化:      

          输入N,M=?,T=1                (m≤n)

          a[1],...a[n]=0   

        └──────────┘

    / repeat

         a[t]=a[t]+1

      ┌──────────┐

         if a[t]>n then  

          a[t]=0,t=t-1   

           goto 100      

      └──────────┘

       ┌──────────┐

         for i=1 to t-1   

          if a[t]=a[i] then│   (排除重复情况}

            goto 100      

       └──────────┘

       ┌──────────┐

         if t<m then      

          t=t+1, a[t]=0    │(若是组合,改为a[t]=a[t-1])

          goto 100        

       └──────────┘

       ┌──────────┐

          if t=m then     

           打印一种排法   

       └──────────┘

    \ 100: until t=0            

 

   按上述程序框架编写的源程序如下:

  program pailie(input,output);

   var

     a:array[1..20]of integer;

     i,j,t,r,k,n,m,step:integer;

   begin

      writeln('input n,m (n>m) ');

      readln(n,m);

      t:=1;step:=0;

   for j:=1 to m do

      a[j]:=0;

  /repeat

   a[t]:=a[t]+1;

 │ if a[t]>n then

   t:=t-1

 │ else

   begin

     r:=0;

    for i:=1 to t-1 do

     if a[t]=a[i] then r:=100;

      if r<>100 then

       begin

        if t=m then

      /  begin

        step:=step+1;

         write(step:4,') ');     {打印输出}

         for k:=1 to t do

          write(a[k]);

         writeln;

      /   end;

        if t<m then

         begin

          t:=t+1;a[t]:=0;  {将a[t]:=0改为a[t]:=a[t-1],则为组合}

         end;

       end;

   end;

  / until t=0;

end.

 

2:某工厂录取三个职工,分配到三个工种,每个工种各分配一人, 经过技

术测定,三个工人担任某项工作的生产率如表11.1。问:这三个工人如何分配才能

使做出的总贡献最大?

 

 

 

 

        ┌──┬──┬───┬───┐

              甲│   已 │   丙 │

        ├──┼──┼───┼───┤

          1 │  3 │   5     2 

        ├──┼──┼───┼───┤

          2 │  4 │   3     3 

        ├──┼──┼───┼───┤

          3 │  2 │   4     3 

        └──┴──┴───┴───┘

    分析:我们用变是A、B、C表示甲,已,丙三名工人,变量的值表示所担任的工种.

:A=1,B=3,C=2表示工人A做1号工种,工人B做3号工种,工人C做2号工种。A、B、

C的取值范围均为1--3,且两两不重复,即同一工种不能被两人担任。A、B、C 的一

组不重复取值就为一种分配方案。我们可利用三重循环来生成所有可能的分配方案,

从中选出效益最大的一种。为了减少排序时间,程序中将初始最大效率D=0,然后

以此来逐一比较求解。

    程序如下:

    program effi;

     var i,j,k,n,m,l:integer;

         d,r1,r2,r3,t:real;

         r:array[1..3,1..3] of real;

     procedure init;                   {输入效率表}

      var i,j,k:integer;

      begin

       for i:=1 to 3 do

        for j:=1 to 3 do

         read(r[i,j]);

      end;

     procedure prt;                    {打印输出}

      begin

       writeln('worker',' ':8,'type',' ':8,'effi':8);

       writeln('a',n:15,r1:15);

       writeln('b',m:15,r2:15);

       writeln('c',l:15,r3:15);

      end;

     begin                             {主程序}

      d:=0;

      for i:=1 to 3 do

       for j:=1 to 3 do

        for k:=1 to 3 do

         if (i<>j) and (j<>k) and (i<>k) then

          begin

           t:=r[1,i]+r[2,j]+r[3,k];

           if d<t then

            begin

             d:=t;n:=i;m:=j;l:=k;

             r1:=r[1,i]+r[2,j]+r[3,k];

            end;

          end;

      prt;

     end.

    上例中,工人人数及工种数和效益表都是题中给定的(工人3人,工种也是3种)。

若工人人数,工种及效率表都不事先给定,而是要求程序运行后从键盘输入,那么

题目难度将增大,见下例。

    例3:N个工人,分配到M个工种干活(M≤N),每个工种只能分配一人。N,

M及每个工人从事各工种的效率按下列格式从键盘输入:

     n=?   4            (工人总数)

     m=?   4            (工种总数)

     g1:   3  4  2  5   (工人1从事各工种的效率)

     g2:   6  2  1  3   (工人2从事各工种的效率)

     g3:   5  4  4  2   (工人3从事各工种的效率)

     g4:   1  3  2  4   (工人4从事各工种的效率)

试问:如何分配才能使总效益最大?

    分析:由于N,M的值是程序运行后,方从键盘输入的,所以无法利用多重循环

来设计程序。我们可以这样来考虑:用1,2,3...N表示N名工人,从中选出M 个按照

工种的顺序排列。这样每一种排法就对应了一种分配方案。所以他们可以利用上一

节介绍的排列组合算法二,生成从N个元素中任选M个元素的全部排列,逐个判断求

出效益最大的一种分配方案。

    程序如下:

program pailie(input,output);

var

 a,aa:array[1..20] of integer;

 b:array[1..100,1..100] of integer;

 i,j,t,r,k,n,m,h,step,step1,step2:integer;

begin

  write('input n=');

  readln(n);            {输入工人人数}

  write('input m=');  

  readln(m);            {输入工种数}

  t:=1;step:=0;

  for j:=1 to n do      {输入效率表}

   begin

    a[j]:=0;

    write('g',j,'  ');

    for h:=1 to m do

     read(b[j,h]);

   end;

 

 repeat                {以下产生n个中取m个的排列}

  a[t]:=a[t]+1;

  if a[t]>n then

   t:=t-1

  else

   begin

    r:=0;

    for i:=1 to t-1 do

     if a[t]=a[i] then r:=100;  {r=100是重复标志}

     if r<>100 then

      begin

       if t=m then   {t=m表示完成一种排列}

        begin

         step1:=0;

         for h:=1 to m do

          step1:=step1+b[h,a[h]];  {计算效率之和}

         if step1>step then    {保存当前效率最大时分配方案}

          begin

           for step2:=1 to m do

            aa[step2]:=a[step2];

           step:=step1

          end;

        end;

       if t<m then begin t:=t+1;a[t]:=0;  end;

     end;

    end;

   until t=0;

   for step2:=1 to m  do     {打印输出}

    write('G(',step2,',',aa[step2],')');

   writeln('=',step);

end.

    请同学们认真将此程序与上一节介绍的排列组合算法二程序对照一下,从而可以

体会到上一节所介绍的排列、组合及算法的重要性。

 

 

排列的递归算法

 

    列出所有从数字1到数字n的连续自然数的排列,要求所产生的任一数字序列中不允许

出现重复的数字

    输入:n(1<=n<=9)

    输出:

    程序如下:

        procdure  find(k:integer);

            begin

              if   k > n

                   then  输出a[1]—a[n]的值

                   else

                       for  i:=1 to n do

                          begin

                             if i的值与a[1]--a[k-1]无重复

                                then

                                    begin

                                       a[k]:=i

                                        找下一个路口

                                       end

                          end

           end

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值