看如下一个例子,代码是C++的,并有分析,看懂没问题:通常我们希望检查n 个不同元素的所有排列方式以确定一个最佳的排列。比如, a,b 和c 的排列方式有:a b c, a c b, b a c, b c a, cab 和c b a。n 个元素的排列方式共有n !种。 由于采用非递归的C + +函数来输出n 个元素的所有排列方式很困难,所以可以开发一个递 归函数来实现。令E= {e1 , ..., en }表示n 个元素的集合,我们的目标是生成该集合的所有排列方 式。令Ei 为E中移去元素i 以后所获得的集合,perm (X) 表示集合X 中元素的排列方式,ei . p e r m (X)表示在perm (X) 中的每个排列方式的前面均加上ei 以后所得到的排列方式。例如,如果 E= {a, b, c},那么E1= {b, c},perm (E1 ) = ( b c, c b),e1 .perm (E1) = (a b c, a c b)。 对于递归的基本部分,采用n = 1。当只有一个元素时,只可能产生一种排列方式,所以 perm (E) = ( e),其中e 是E 中的唯一元素。当n > 1时,perm (E) = e1 .perm (E1 ) +e2 .p e r m (E2 ) +e3.perm (E3) + ⋯ +en .perm (En )。这种递归定义形式是采用n 个perm (X) 来定义perm (E), 其 中每个X 包含n- 1个元素。至此,一个完整的递归定义所需要的基本部分和递归部分都已完成。当n= 3并且E=(a, b, c)时,按照前面的递归定义可得perm (E) =a.perm ( {b, c} ) +b.perm ( {a, c} ) +c.perm ( {a, b} )。同样,按照递归定义有perm ( {b, c} ) =b.perm ( {c} ) +c.perm ( {b}), 所以 a.perm ( {b, c} ) = ab.perm ( {c} ) + ac.perm ( {b}) = a b . c + ac.b = (a b c, a c b)。同理可得 b.perm ( {a, c}) = ba.perm ( {c}) + bc.perm ( {a}) = b a . c + b c . a = (b a c, b c a),c.perm ( {a, b}) = ca.perm ( {b}) + cb.perm ( {a}) = c a . b + c b . a = (c a b, c b a)。所以perm (E) = (a b c, a c b, b a c, b c a, c a b, c b a)。 注意a.perm ( {b, c} )实际上包含两个排列方式:abc 和a c b,a 是它们的前缀,perm ( {b, c} ) 是它们的后缀。同样地,ac.perm ( {b}) 表示前缀为a c、后缀为perm ( {b}) 的排列方式。 程序1 - 1 0把上述perm (E) 的递归定义转变成一个C++ 函数,这段代码输出所有前缀为l i s t [ 0: k-1], 后缀为l i s t [ k:m] 的排列方式。调用Perm(list, 0, n-1) 将得到list[0: n-1] 的所有n! 个排列方 式,在该调用中,k=0, m= n - 1,因此排列方式的前缀为空,后缀为list[0: n-1] 产生的所有排列 方式。当k =m 时,仅有一个后缀l i s t [ m ],因此list[0: m] 即是所要产生的输出。当k<m时,先 用list[k] 与l i s t [ k:m] 中的每个元素进行交换,然后产生list[k+1: m] 的所有排列方式,并用它 作为list[0: k] 的后缀。S w a p是一个inline 函数,它被用来交换两个变量的值,其定义见程序1 - 11。P e r m的正确性可用归纳法来证明。 程序1-10 使用递归函数生成排列 template<class T> void Perm(T list[], int k, int m) { / /生成list [k:m ]的所有排列方式 int i; if (k == m) {//输出一个排列方式 for (i = 0; i <= m; i++) cout << list [i]; cout << endl; } else // list[k:m ]有多个排列方式 // 递归地产生这些排列方式 for (i=k; i <= m; i++) { Swap (list[k], list[i]); Perm (list, k+1, m); Swap (list [k], list [i]); } } 程序1 - 11 交换两个值 template <class T> inline void Swap(T& a, T& b) {// 交换a和b T temp = a; a = b; b = temp; }
谢谢上面的回复,谁能写一段DELPHI的代码解决上面的问题?高手请进啊!
利用集合,很容易的。Delphi2005下通过:不过数据量很大呵,不知有什么用。 program Test;{$APPTYPE CONSOLE}type TSet = Set of 1..200;var m, n: Integer; d: array [1..200] of Integer;procedure WriteOut; var i: Integer; begin for i:=1 to n do Write(d[i]:5); Writeln; end;procedure Search(Index: Integer; s: TSet); var i: Integer; begin if Index > n then begin WriteOut; Exit; end; for i := 1 to m do if i in s then begin d[Index] := i; Search(Index+1, s - [i]); end; end;var i: Integer; s: TSet; begin Readln(m, n); s := []; for i := 1 to m do s := s + [i]; Search(1, s); Readln; end.
在编程的过程中,我们常常要碰到这样一个问题:有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把得到的组合记录下来,这些函数和过程的实现略去。 至此,组合选数的问题就解决了,从以上的程序中,我们可以看出,对于有些问题,用递归来实现非常简单,而且逻辑清晰。
我用递归写的一个函数 把k个数按顺序存在NumArray数组里,取R个数的组合(顺序排列) function ComData(K:ShortInt;R:shortInt):Boolean; var i,j:integer; begin for i:=K downto R do begin CombArray[R]:=NumArray[i]; if R>1 then ComData(i-1,R-1) else begin //输出CombArray数组中的r个数 for j=1 to r do 输出CombArray[j]到文本文件 end; Result:=true; end;
a,b 和c 的排列方式有:a b c, a c b, b a c, b c a, cab 和c b a。n 个元素的排列方式共有n !种。
由于采用非递归的C + +函数来输出n 个元素的所有排列方式很困难,所以可以开发一个递
归函数来实现。令E= {e1 , ..., en }表示n 个元素的集合,我们的目标是生成该集合的所有排列方
式。令Ei 为E中移去元素i 以后所获得的集合,perm (X) 表示集合X 中元素的排列方式,ei . p e r m
(X)表示在perm (X) 中的每个排列方式的前面均加上ei 以后所得到的排列方式。例如,如果
E= {a, b, c},那么E1= {b, c},perm (E1 ) = ( b c, c b),e1 .perm (E1) = (a b c, a c b)。
对于递归的基本部分,采用n = 1。当只有一个元素时,只可能产生一种排列方式,所以
perm (E) = ( e),其中e 是E 中的唯一元素。当n > 1时,perm (E) = e1 .perm (E1 ) +e2 .p e r m
(E2 ) +e3.perm (E3) + ⋯ +en .perm (En )。这种递归定义形式是采用n 个perm (X) 来定义perm (E), 其
中每个X 包含n- 1个元素。至此,一个完整的递归定义所需要的基本部分和递归部分都已完成。当n= 3并且E=(a, b, c)时,按照前面的递归定义可得perm (E) =a.perm ( {b, c} ) +b.perm ( {a,
c} ) +c.perm ( {a, b} )。同样,按照递归定义有perm ( {b, c} ) =b.perm ( {c} ) +c.perm ( {b}), 所以
a.perm ( {b, c} ) = ab.perm ( {c} ) + ac.perm ( {b}) = a b . c + ac.b = (a b c, a c b)。同理可得
b.perm ( {a, c}) = ba.perm ( {c}) + bc.perm ( {a}) = b a . c + b c . a = (b a c, b c a),c.perm ( {a, b}) =
ca.perm ( {b}) + cb.perm ( {a}) = c a . b + c b . a = (c a b, c b a)。所以perm (E) = (a b c, a c b, b a c, b c a,
c a b, c b a)。
注意a.perm ( {b, c} )实际上包含两个排列方式:abc 和a c b,a 是它们的前缀,perm ( {b, c} )
是它们的后缀。同样地,ac.perm ( {b}) 表示前缀为a c、后缀为perm ( {b}) 的排列方式。
程序1 - 1 0把上述perm (E) 的递归定义转变成一个C++ 函数,这段代码输出所有前缀为l i s t [ 0:
k-1], 后缀为l i s t [ k:m] 的排列方式。调用Perm(list, 0, n-1) 将得到list[0: n-1] 的所有n! 个排列方
式,在该调用中,k=0, m= n - 1,因此排列方式的前缀为空,后缀为list[0: n-1] 产生的所有排列
方式。当k =m 时,仅有一个后缀l i s t [ m ],因此list[0: m] 即是所要产生的输出。当k<m时,先
用list[k] 与l i s t [ k:m] 中的每个元素进行交换,然后产生list[k+1: m] 的所有排列方式,并用它
作为list[0: k] 的后缀。S w a p是一个inline 函数,它被用来交换两个变量的值,其定义见程序1 -
11。P e r m的正确性可用归纳法来证明。
程序1-10 使用递归函数生成排列
template<class T>
void Perm(T list[], int k, int m)
{ / /生成list [k:m ]的所有排列方式
int i;
if (k == m) {//输出一个排列方式
for (i = 0; i <= m; i++)
cout << list [i];
cout << endl;
}
else // list[k:m ]有多个排列方式
// 递归地产生这些排列方式
for (i=k; i <= m; i++) {
Swap (list[k], list[i]);
Perm (list, k+1, m);
Swap (list [k], list [i]);
}
}
程序1 - 11 交换两个值
template <class T>
inline void Swap(T& a, T& b)
{// 交换a和b
T temp = a; a = b; b = temp;
}
program Test;{$APPTYPE CONSOLE}type
TSet = Set of 1..200;var
m, n: Integer;
d: array [1..200] of Integer;procedure WriteOut;
var
i: Integer;
begin
for i:=1 to n do
Write(d[i]:5);
Writeln;
end;procedure Search(Index: Integer; s: TSet);
var
i: Integer;
begin
if Index > n then
begin
WriteOut;
Exit;
end; for i := 1 to m do
if i in s then
begin
d[Index] := i;
Search(Index+1, s - [i]);
end;
end;var
i: Integer;
s: TSet;
begin
Readln(m, n);
s := [];
for i := 1 to m do
s := s + [i];
Search(1, s);
Readln;
end.
你好,我是这样想的,正好15个数,可不可以用16进制来解决
15选5最小不重复的是
十六进制 十进制
12345 对应 74565
最大是
FEDCB 对应 1043915从74565开始,循环加1直到1043915,判断里面如果有重复的数字就去掉,如74566、745BB
如果没有重复数字,则将每一位16进制数转换成10进制,如7B56A 换成 07 11 05 06 10最后在文本文件中输出如下结果:
01 02 03 04 05
01 02 03 04 06
...
15 14 13 12 11帮我再写一下上面的代码好吗,谢谢,另你楼上写的代码里面有数组,还有递归,有些难,不好看懂。
拜托再帮我写一下!
主要解答者: aiirii 提交人: aiirii
感谢:
审核者: hthunter 社区对应贴子: 查看
A : 我想求这样一个算法:关于组合问题,有n个数,从中取出m个(m<n),不考虑数字的顺序,求一算法将所有的组合输出,谁知道?高分相送
---------------------------------------------------------------
http://www.cx66.com/cxgzs/program/delphi/267.htm
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把得到的组合记录下来,这些函数和过程的实现略去。
至此,组合选数的问题就解决了,从以上的程序中,我们可以看出,对于有些问题,用递归来实现非常简单,而且逻辑清晰。
把k个数按顺序存在NumArray数组里,取R个数的组合(顺序排列)
function ComData(K:ShortInt;R:shortInt):Boolean;
var
i,j:integer;
begin
for i:=K downto R do
begin
CombArray[R]:=NumArray[i];
if R>1 then
ComData(i-1,R-1)
else
begin
//输出CombArray数组中的r个数
for j=1 to r do
输出CombArray[j]到文本文件
end;
Result:=true;
end;