问题一:不含有重复元素的全排列问题
如:输入一个数n,输出1~n的全排列
假设,现在有编号为1、2、3的三张扑克牌,以及编号为1、2、3的三个盒子,将这三张扑克牌放在三个纸盒子内,求其所有可能性。
解析:
1、首先,我们先考虑1号盒子,我们约定每到一个纸盒子面前都按数字递增的顺序摆放扑克牌。把1号扑克牌放在1号纸盒内,再依次摆放2、3号扑克牌。于是形成了序列{1,2,3}。
2、接着回收3号纸盒内的扑克牌,尝试新一轮的摆放,此时发现别无他选,于是,进一步回收2号纸盒内的扑克牌,最后形成序列{1,3,2}。
3、重复上述步骤便可得到123的全排列。
代码实现:
for ( int i = 1; i <= n; i++ )
a[step] = i;//将i号扑克牌放在第step个盒子里
a[]表示装有所有小盒子的数组,step表示当前正处于第step号小盒子,i表示扑克牌的序号。
需要注意的是,如果一张扑克牌已被放在另一个盒子内,则不能再放入当前盒子内。
增加一个visit[]数组表示哪些扑克牌已被使用
完善上述代码:
for ( int i = 1; i <= n; i++ )
{
if ( visit[i] == 0 )
{
a[step] = i; //将i号扑克牌放在第step个盒子里
visit[i] = 1; //置1表示第i号扑克牌不在手中
}
}
现在对于step号盒子已处理完,需要考虑step+1号盒子。
由于step+1号盒子与step号盒子处理的方式相同,所以做一个封装即可。
完善代码:
void dfs ( int step )
{
for ( int i = 1; i <= n; i++ )
{
if ( visit[i] ==0 )
{
a[step] = i;
visit[i] = 1;
}
}
}
我们回到文章开头所阐述的放置扑克牌的思路,我们在当前纸盒放完第i号扑克牌后,便立即处理下一个盒子
完善代码:
void dfs( int step ) //step表示当前要处理的盒子
{
for ( int i = 1; i <= n; i++ )
{
if(visit[i] == 0)
{
a[step] = i; //将i号扑克牌放入第step个盒子中
visit[i] = 1; // 置1表示第i号扑克牌不在手中
dfs(step+1); //递归调用
visit[i] = 0; // 非常重要,收回该盒子中的扑克牌才能进行下一次的放置
}
}
}
需要注意,我们只有回收每次尝试放置的扑克牌,才能进行下一次放置。
现在考虑最后一个问题,终止条件是什么?
当我们处理完第n个盒子的时候,就已经得到一个符合条件的序列了。
代码如下:
void dfs(int step) //step表示当前要处理的盒子
{
if(step == n+1)//终止条件
{
//输出排列
for(i = 1; i <= n; i++)
printf("%d", a[i]);
printf("\n");
return;
}
for(int i = 1; i <= n; i++)
{
if(visit[i] == 0)
{
a[step] = i; //将i号扑克牌放入第step个盒子中
visit[i] = 1; // 置1表示第i号扑克牌不在手中
dfs(step+1); //递归调用
visit[i] = 0; // 非常重要,收回该盒子中的扑克牌才能进行下一次尝试。
}
}
}
}
深度搜索的核心在于,在当前步骤要把每种可能性都尝试一遍(用for循环),解决完当前步骤后进入下一步,而下一步的解决方法等同于上一步。
故DFS基本模型为:
void dfs ( int step)
{
//判断结束边界,尝试每一种可能
for (int i = 1; i <= n;i++ )
{
dfs(step+1);//尝试下一步
}
return ;
}
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n;
int a[100], visit[100];
void dfs(int step)
{
//终止条件
if(step == n+1)
{
for(int j=1;j<=n;j++)
{
cout<<a[j]<<"";
}
cout<<endl;
return ;
}
for(int i=1;i<=n;i++)
{
if(visit[i]==0)
{
visit[i]=1;
a[step]=i;
dfs(step+1);
visit[i]=0;
}
}
}
int main()
{
while(cin>>n)
{
memset(visit,0,sizeof(visit));
dfs(1);
}
return 0;
}
问题二:含有重复元素的全排列问题
解决关键:去重
结果:统计全排列中,排列结果不同的序列个数。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
int len, num, visit[100];
char str[100], a[100];
void dfs(int step)
{
int j;
if(step == len)
{
printf("%s\n",a);
num++;
return;
}
for(int i = 0;i < len; i++)
{
if(visit[i]==0)
{
for(j=i+1; j<len; j++)
{
if(visit[j] && str[i] == str[j])//这句命令是怎么做到去重的???
break;
}
if(j == len)
{
visit[i] = 1;
a[step] = str[i];
dfs(step+1);
visit[i] = 0;
}
}
}
}
int main()
{
while(scanf("%s",str)!=EOF)
{
len=strlen(str);//字符串的长度
//先用冒泡对序列进行一下排序,作用是使输出的序列从上往下看有序
for(int i = 0;i < len;i++)
for(int j=i+1;j<len;j++)
{
if(str[i] > str[j])
{
char tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}
}
num = 0;
a[len] = '\0';
dfs(0);//从第一个字符开始搜索
cout<<num<<endl;
}
return 0;
}
待修改下方
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
int len, num, visit[100];
char str[100], a[100];
void dfs(int step)
{
/*终止条件,先输出符合条件的序列,再统计个数*/
if(step == len)
{
printf("%s\n",a);
num++;
return;
}
for(int i = 0;i < len; i++)
{
if(visit[i]==0)
{
int j;
/*去重*/
for(j=i+1; j<len; j++)
{
if(visit[j] && str[i] == str[j])//这个字符已被使用,且重复,便跳过。
/*这里的for循环 因为用冒泡对序列排了序,这个字符若有多个,第一次使用时不会与前方重复,
但用第二个时便与前方的重复了便会与前方的字符相同 想法错
这里的for循环,是从第i字符开始,后边的字符逐个与它比较,只要有与它相同的字符,便跳过,
这样下方的if(j == len)条件便不会成立
*/
break;
}
//
if(j == len)//注意这里的判断条件,只有当j==len时这个数才会被存入,根据上方的判断
{
visit[i] = 1;
a[step] = str[i];
dfs(step+1);
visit[i] = 0;
}
}
}
}
int main()
{
while(scanf("%s",str)!=EOF)
{
len=strlen(str);//字符串的长度
//先用冒泡对序列进行一下排序,作用是使输出的序列从上往下看有序
for(int i = 0;i < len;i++)
for(int j=i+1;j<len;j++)
{
if(str[i] > str[j])
{
char tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}
}
num = 0;
a[len] = '\0';
dfs(0);//从第一个字符开始搜索
cout<<num<<endl;
}
return 0;
}