全排列:
【引子】
给定一个普通的递归函数(Swap为交换函数,交换俩个数据。这里不做过多的说明。)
void fun(int *arr,int k,int m)
{
if(k==m){}
else
{
for(int i = k; i < m; i++)
{
Swap(arr[k],arr[i]);
fun(arr,k+1,m);
Swap(arr[k],arr[i]);
}
}
}
int main()
{
int arr[3]={1,2,3};
fun(arr,0,3);
return 0;
}
根据代码可以知道,我们按照arr的变化画出整个程序的执行的过程。
层次分析递归程序:1,第一层按照第零层括号内的元素使第一个和之后每个元素(包括它自己)进行交换。(把交换过后的左边的第一个元素移出括号)。例如2{1,3}就是{1,2,3},第一个元素1和第二个元素2进行交换,结果:{2,1,3},把2移出括号得到最终结果:2{1,3}.
2,第二层使按照第一层括号里的元素第一个和之后每一个元素进行交换得到的。(把交换过后的左边的第一个元素移出括号)。例如:1,3{2},就是将1{2,3}:2和3交换,然后将最左边的元素移动出括号得到1,3{2}.
【书上给的给的定义】
【最终代码】:
void perm(int *arr, int k, int m)
{
if (k == m)
{
for (int i = 0; i <= m; i++)
printf("%d ", arr[i]);
printf("\n");
}
else
{
for (int i = k; i < m; i++)//层次分析for循环比较好理解
{
Swap(arr[k], arr[i]);//和之后的每个元素交换(包括它自己)
perm(arr, k + 1, m);
Swap(arr[k], arr[i]);//恢复原来的顺序。
}
}
}
求子集:
【引子】
void fun(int i ,int n)
{
if(i>=n){}
else
{
fun(i+1,n);
fun(i+1,n);
}
}
一个特别简单的递归程序,我们可以根据抓住i变量的值来绘制出递归程序的执行过程如图:
那么我们求子集和这个程序的区别在哪里呢?这棵树就是所谓的子集树。
当我们定义遍历树的路径时候,向左为1,向右为0那么我们可以得到这颗树的所有叶子节点的路径为:
111,110,101,100,011,010,001,000
假设我们的要求{321}的子集:
321,32,31,3,21,2,1,#(空集)
我们可以看到规律
以遍历所有叶子节点路径为标准,对应的元素为1打印该元素,为0不打印该元素。
我们就可以根据遍历结果打印所有的子集。
void getSubset(int i, int n,bool *flag)
{
if (i >= n)
{
for (; i >= 0; i--)
{
if (flag[i - 1] == true)
printf("%d ", i);
}
printf("\n");
}
else
{
flag[i] = true;
getSubset(i + 1, n, flag);
flag[i] = false;
getSubset(i + 1, n, flag);
}
}
这里需要注意,我们的flag就是代表了遍历的路径结果。它的大小为n。
bool flag[3];
getSubset(0, 3, flag);
【结果】:和我们分析的结果一致。
但是这个程序只可以打印0~n的子集。不通用,我们写出通用的求一个数组的子集的函数。
【代码】:
void getSubset_arr(int *arr, int i, int n, bool *flag)
{
if (i == n)
{
for (; i > 0; i--)
{
if (flag[i - 1] == true)
{
printf("%d ", arr[i - 1]);
}
}
printf("\n");
}
else
{
flag[i] = true;
getSubset_arr(arr, i + 1, n, flag);
flag[i] = false;
getSubset_arr(arr, i + 1, n, flag);
}
}
【测试案例】:
bool flag[3];
int arr[3] = { 1,2,3 };
getSubset_arr(arr, 0, 3, flag);
【结果】: