排列,组合是高中数学中的基本内容之一。我们在程序设计中对具体问题进行
分析,建 立数学模型时会经常涉及到这方面的知识。因此,掌握排列、组合及算法
是学习程 序设计和掌握其它算法的基础。
一:排列、组合概念
定义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
发