- /********
- *给你一个数n,输出1到n的全排列
- *深度优先搜索
- ********/
- #include <stdio.h>
- #include <stdlib.h>
- int book[10], a[10], n;
- void dfs(int step)
- {
- int i;
- if(step == n+1)//当你在第n+1步的时候,说明前n部已经排好了。
- {
- for(i = 1; i <= n; i++)
- printf("%d ", a[i]);
- printf("\n");
- return ;//返回之前的一步;
- }
- for(i = 1; i <= n; i++)//按照1,2,3.。。的方式一一尝试。
- {
- if(book[i]==0)//判断扑克牌i是不是还在手里
- {
- a[step]=i;//将i牌放在第step个盒子里。
- book[i]=1;//表示扑克牌不再第step个盒子里
- dfs(step+1);//继续下一步。
- book[i]=0;//将刚才尝试的扑克收回,才能进行下一步的尝试。
- }
- }
- return ;//结束搜索。
- }
- int main()
- {
- while(~scanf("%d", &n))
- dfs(1);
- return 0;
- }
-------------------------------------------------------分割线---------------------------------------------------------------
全排列:
全排列是将一组数按一定顺序进行排列,如果这组数有n个,那么全排列数为n!个。
从集合中依次选出每一个元素,作为排列的第一个元素,然后对剩余的元素进行全排列,如此递归处理,从而得到所有元素的全排列。
以对字符串abc进行全排列为例,我们可以这么做:以abc为例
固定a,求后面bc的排列:abc,acb,求好后,a和b交换,得到bac
固定b,求后面ac的排列:bac,bca,求好后,c放到第一位置,得到cba
固定c,求后面ba的排列:cba,cab。
这个思想和回溯法比较吻合。
代码可如下编写所示
- // 回溯法搜索全排列树
- #include<stdio.h>
- #define M 20
- int n;
- int a[M];
- int cnt = 0;// 记录全排列个数
- void swap(int *a, int *b)//交换a,b
- {
- char t;
- t = *a;
- *a = *b;
- *b = t;
- }
- void dfs(int cur)
- {
- int i;
- if(cur == n)// 找到 输出全排列
- {
- ++cnt;
- for(i=0; i<n; i++)
- printf("%d ", a[i]);
- printf("\n");
- }
- else
- {
- // 将集合中的所有元素分别与第一个交换,这样就总是在
- // 处理剩下元素的全排列(即用递归)
- for(i=cur; i<n; i++)
- {
- swap(&a[cur], &a[i]);
- dfs(cur+1);
- swap(&a[cur], &a[i]);//回溯
- }
- }
- }
- int main()
- {
- while(scanf("%d", &n) != EOF)
- {
- for(int i=0; i<n; i++)
- a[i] = i+1;// 假设集合S为:1 2 3 ... n
- cnt = 0;
- dfs(0);
- printf("count:%d\n", cnt);
- }
- return 0;
- }
或者利用一个vis数组标识每个元素是否已经被访问,代码如下:
- #include <stdio.h>
- int a[10];
- bool vis[10];
- int n;//排列个数 n
- void dfs(int dep) //打印所有的全排列,穷举每一种方案
- {
- if(dep == n)
- {
- for(int i = 0; i < n; i++)
- {
- printf("%d ",a[i]);
- }
- printf("\n");
- return ;
- }
- for(int i = 0; i < n; i++)// 找一个最小的未标记的数字,保证了字典序最小
- {
- if(!vis[i])
- {
- a[dep] = i+1;
- vis[i] = true;// 找到了就标记掉,继续下一层
- dfs(dep + 1);
- vis[i] = false;
- }
- }
- }
- int main()
- {
- while(scanf("%d",&n) != EOF)
- {
- dfs(0);
- }
- return 0;
- }
子集构造:
从n个元素的集合S中找出S满足某种性质的子集时,相应解空间树称为子集树。
求n个元素集合的子集,如A = {1, 2, 3}则A集合的子集有: P(A) = {{1,2,3}, {1,2}, {1,3},{1},{2,3},{2},{3},{}}
采用位向量法,构造一个位向量vis[], vis[i] = 1 表示i在子集A中。
代码如下:
- #include<stdio.h>
- #define M 20
- int n;
- int a[M];
- int vis[M];
- int cnt = 0;// 记录子集个数
- void dfs(int cur)
- {
- int i;
- if(cur == n)// 找到 输出所有子集
- {
- ++cnt;
- int flag =1;
- for(i=0; i<n; i++)
- if(vis[i])
- {
- printf("%d ", a[i]);
- flag = 0;
- }
- if(flag)//子集中的空集
- printf("φ");
- printf("\n");
- }
- else
- {
- for(i=1; i>=0; --i)//vis 中分为 选or不选即 1,0
- {
- vis[cur] = i;
- dfs(cur+1);
- }
- }
- }
- int main()
- {
- while(scanf("%d", &n) != EOF)
- {
- for(int i=0; i<n; i++)
- a[i] = i+1;// 假设集合S为:1 2 3 ... n
- cnt = 0;
- dfs(0);
- printf("count:%d\n", cnt);
- }
- return 0;
- }
此外可以采用采用增量构造法,
代码如下:
- #include<stdio.h>
- #define M 20
- int n;
- int a[M];
- int vis[M];
- int cnt = 0;// 记录子集个数
- void subset(int cur, int A[])
- {
- int i;
- ++cnt;
- if(0 == cur)//子集中的空集
- printf("φ");
- for(i=0; i<cur; ++i)// 打印当前集合
- {
- printf("%d ", A[i]);
- }
- printf("\n");
- int min = cur ? A[cur-1]:0;//确定当前元素的最小可能值
- for(i=min; i<n; ++i)
- {
- A[cur] = a[i];
- subset(cur+1, A);//递归构造
- }
- }
- int main()
- {
- while(scanf("%d", &n) != EOF)
- {
- for(int i=0; i<n; i++)
- a[i] = i+1;// 假设集合S为:1 2 3 ... n
- cnt = 0;
- int A[M]={0};
- subset(0, A);
- printf("count:%d\n", cnt);
- }
- return 0;
- }