递归函数:以层次来想函数递归,以深度来想递归出口。
问题描述:
给一个集合,集合里面元素都不重复,输出这个集合的子集。
例如:
输入:
{1,2,3}
打印:
{1,2,3}
{1,2}
{1,3}
{2,3}
{1}
{2}
{3}
{}//代表空集
思路:
我们用一个等大小的集合,集合中对应的0代表不打印这个元素,集合中对应的1代表打印这个元素。建立两颗深度为3(集合个数)的满二叉树。可以看作哈夫曼编码,每一个码代表打印的一个子集。这样所有的码都代表一个子集。
比如:
{1,1,1}代表{1,2,3}
{1,1,0}代表{1,2}
{1,0,1}代表{1,3}
…
{0,0,0}代表{}
(注意,1代表打印,0代表不打印)
用代码解释会清晰一点,图下面先写出代码,然后再分析代码,这样思路会更清晰。
代码:
void Print_Subset(int* arr, int* brr, int i, int n)
{
if (i == n)
{
for (int j = 0; j < n; j++)
{
if (brr[j] == 1)
{
printf("%2d", arr[j]);
}
}
printf("\n");
}
else
{
brr[i] = 1;
Print_Subset(arr, brr, i + 1, n);
brr[i] = 0;
Print_Subset(arr, brr, i + 1, n);
}
}
int main()
{
int arr[] = { 1,2,3 };
int brr[] = { 0,0,0 };
Print_Subset(arr, brr, 0, sizeof(arr)/sizeof(arr[0]));
return 0;
}
运行结果:
(注意:3下面空两行,上面一个空行是代表空集,后面一个空行就是换行)
分析代码:
对于递归函数,我们一定要按照层次来分析来想代码,按照深度来分析,那是系统调用按照深度调用的。
注意递归出口是i == n,说明我们的树已经建立完成了,就可以根据brr的1或者0决定是否要打印arr的元素。
再注意这几行代码,我们来分析一下:
else
{
brr[i] = 1;
Print_Subset(arr, brr, i + 1, n);
brr[i] = 0;
Print_Subset(arr, brr, i + 1, n);
}
我们说过了,以深度调用分析,那是系统干的事情,我们只要以层次分析,一层分析完分析下一层,这样思路才清晰。下面代码竖着看。
第一层:
第二层(在第一层的基础上把第一层的函数展开),要知道每一次brr[i]负值后,后面都会有个函数需要执行:
第三层(在第二层的基础上把第一层的函数展开):
再到后面展开函数的时候,已经到了递归出口了,即最下面一行的函数递归进去都会打印出来一个子集,如下:
根据{1,1,1}打印{1,2,3}
根据{1,1,0}打印{1,2}
根据{1,0,1}打印{1,3}
根据{1,0,0}打印{1}
根据{0,1,1}打印 {2,3}
…
递归题,如果一段代码里出现两次或者两次以上递归进去函数,那么我们一定要以层次来想函数怎么递归,怎么执行,以深度来想函数递归出口。