这类问题通常需要输出可能的所有组合。我对回溯算法解题过程的理解是:将所有可能的组合形成一个树/森林,遍历其所有叶节点,输出根节点到这个叶节点这条路径所代表的组合。
例如:找出从自然数1~n中任取r个数的所有组合。
不妨设n=5,r=3,用a[3]输出可能的组合。则所有可能的组合为:
1 2 3,1 2 4,1 2 5,1 3 4,1 3 5,1 4 5;
2 3 4,2 3 5,2 4 5;
3 4 5;
用树来表示:
1 | 2 | 3 i=0
| |
2 3 4 | 3 4 | 4 i=1
| |
3 4 5 4 5 5 | 4 5 5 | 5 i=2
第i=0层:数字填入a[0],一开始为1,逐渐递加到3 3+(imax-i)=n,imax=r-1。
第i=1层:数字填入a[1],一开始为a[0]+1,逐渐增加到4
第i=2曾:数字填入a[2],一开始为a[1]+1,逐渐增加到5
int n=5,r=3; //假设
int i=0; //从第0层开始
int a[r]; //用a[r]储存、输出这个组合
a[0]=1; //这个组合的第一个数从1开始
do{//一直循环直到到达最后一个叶节点为止
if(a[i]<=n-r+1+i){ //还可以向后
if(i==r-1){ //此时到达最后一层
//输出a[0]~a[2],即输出这个组合
a[i]++; //访问下一个叶节点
}
else{ //没有到达最后一层的话,向下访问
i++;
a[i]=a[i-1]+1;
}
}
else{ //不可以再向后了
if(i==0){ //深度优先后移,此时最后一个节点也无法后移时,结束
return;
}
i--; //上一个节点后移
a[i]++;
}
}
while(1);
扩展到所有变量n,r的代码:
#include <stdio.h>
#define MAX 100
int a[MAX];
void comb(int n, int r)
{
int i, j;
i= 0;
a[i]= 1;
do{
if(a[i]-i<=n-r+1) //还可以向前试探
{
if(i==r-1) //找到一个组合
{
for(j=0; j<r; j++) //输出一个组合
printf("%4d", a[j]);
printf("\n");
a[i]++;
continue;
}
i++; //向前试探
a[i]= a[i-1]+1;
}
else //开始回溯
{
if(i==0) //找完全部解
return; //所有解都找到了才返回,结束函数
a[--i]++;
}
}while(1);
}
void main()
{
int x, y;
printf("请规定截止数和几个数组合(用,分割的整数):");
scanf("%d,%d",&x,&y);
printf("自然数1~%d中%d个数的任意组合有:\n", x, y);
comb(x, y);
}