Linux | c&cpp | Email | github | QQ群:425043908 关注本站

itarticle.cc

您现在的位置是:网站首页 -> 算法逻辑 文章内容

全排列与全组合-itarticl.cc-IT技术类文章记录&分享

发布时间: 8年前算法逻辑 142人已围观返回

全排列

所谓全排列,就是打印出字符串中所有字符的所有排列。例如输入字符串abc,则打印出 a、b、c 所能排列出来的所有字符串 abc、acb、bac、bca、cab 和 cba 。

一般最先想到的方法是暴力循环法,即对于每一位,遍历集合中可能的元素,如果在这一位之前出现过了该元素,跳过该元素。例如对于abc,第一位可以是 a 或 b 或 c 。当第一位为 a 时,第二位再遍历集合,发现 a 不行,因为前面已经出现 a 了,而 b 和 c 可以。当第二位为 b 时 , 再遍历集合,发现 a 和 b 都不行,c 可以。可以用递归或循环来实现,但是复杂度为 O(nn) 。有没有更优雅的解法呢。

首先考虑bac和cba这二个字符串是如何得出的。显然这二个都是abc中的 a 与后面两字符交换得到的。然后可以将abc的第二个字符和第三个字符交换得到acb。同理可以根据bac和cba来得bca和cab。

因此可以知道 全排列就是从第一个数字起每个数分别与它后面的数字交换,也可以得出这种解法每次得到的结果都是正确结果,所以复杂度为 O(n!)。找到这个规律后,递归的代码就很容易写出来了:

#include<stdio.h>

#include<string>

//交换两个字符

void Swap(char *a ,char *b)

{

char temp = *a;

*a = *b;

*b = temp;

}

//递归全排列,start 为全排列开始的下标, length 为str数组的长度

void AllRange(char* str,int start,int length)

{

if(start == length-1)

{

printf("%s\n",str);

}

else

{

for(int i=start;i<=length-1;i++)

{ //从下标为start的数开始,分别与它后面的数字交换

Swap(&str[start],&str[i]);

AllRange(str,start+1,length);

Swap(&str[start],&str[i]);

}

}

}

void Permutation(char* str)

{

if(str == NULL)

return;

AllRange(str,0,strlen(str));

}

void main()

{

char str[] = "abc";

Permutation(str);

}

去重的全排列

为了得到不一样的排列,可能我们最先想到的方法是当遇到和自己相同的就不交换了。如果我们输入的是abb,那么第一个字符与后面的交换后得到 bab、bba。然后abb中,第二个字符和第三个就不用交换了。但是对于bab,它的第二个字符和第三个是不同的,交换后得到bba,和之前的重复了。因此,这种方法不行。

因为abb能得到bab和bba,而bab又能得到bba,那我们能不能第一个bba不求呢? 我们有了这种思路,第一个字符a与第二个字符b交换得到bab,然后考虑第一个字符a与第三个字符b交换,此时由于第三个字符等于第二个字符,所以它们不再交换。再考虑bab,它的第二个与第三个字符交换可以得到bba。此时全排列生成完毕,即abb、bab、bba三个。

这样我们也得到了在全排列中去掉重复的规则:去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。下面给出完整代码:

#include<stdio.h>

#include<string>

//交换两个字符

void Swap(char *a ,char *b)

{

char temp = *a;

*a = *b;

*b = temp;

}

//在 str 数组中,[start,end) 中是否有与 str[end] 元素相同的

bool IsSwap(char* str,int start,int end)

{

for(;start<end;start++)

{

if(str[start] == str[end])

return false;

}

return true;

}

//递归去重全排列,start 为全排列开始的下标, length 为str数组的长度

void AllRange2(char* str,int start,int length)

{

if(start == length-1)

{

printf("%s\n",str);

}

else

{

for(int i=start;i<=length-1;i++)

{

if(IsSwap(str,start,i))

{

Swap(&str[start],&str[i]);

AllRange2(str,start+1,length);

Swap(&str[start],&str[i]);

}

}

}

}

void Permutation(char* str)

{

if(str == NULL)

return;

AllRange2(str,0,strlen(str));

}

void main()

{

char str[] = "abb";

Permutation(str);

}


全组合

如果不是求字符的所有排列,而是求字符的所有组合应该怎么办呢?还是输入三个字符 a、b、c,则它们的组合有a b c ab ac bc abc。当然我们还是可以借鉴全排列的思路,利用问题分解的思路,最终用递归解决。对于长度小于32位的这里介绍一种比较巧妙的思路 —— 基于位图。

假设原有元素 n 个,则最终组合结果是 2n−1 个。我们可以用位操作方法:假设元素原本有:a,b,c 三个,则 1 表示取该元素,0 表示不取。故取a则是001,取ab则是011。所以一共三位,每个位上有两个选择 0 和 1。而000没有意义,所以是2n−1个结果。

这些结果的位图值都是 1,2…2^n-1。所以从值 1 到值 2n−1 依次输出结果:

001,010,011,100,101,110,111 。对应输出组合结果为:a,b,ab,c,ac,bc,abc。

因此可以循环 1~2^n-1,然后输出对应代表的组合即可。有代码如下:

#include<stdio.h>

#include<string.h>

void Combination(char *str)

{

if(str == NULL)

return ;

int len = strlen(str);

int n = 1<<len;

for(int i=1;i<n;i++) //从 1 循环到 2^len -1

{

for(int j=0;j<len;j++)

{

int temp = i;

if(temp & (1<<j)) //对应位上为1,则输出对应的字符

{

printf("%c",*(str+j));

}

}

printf("\n");

}

}

void main()

{

char str[] = "abc";

Combination(str);

}


非递归方法实现

#include <stdio.h>

#include <stdlib.h>


//从n个元素的数组a中,取m个元素的组合

bool zuhe(int a[],int n,int m)

{

//p[x]=y 取到的第x个元素,是a中的第y个元素

int index,i,*p;

p=(int*)malloc(sizeof(int)*m);

if(p==NULL)

return false;


index=0;

p[index]=0;//取第一个元素

while(true)

{

if(p[index]>=n)

{//取到底了,回退

if(index==0)

{//各种情况取完了,不能再回退了

break;

}

index--;//回退到前一个

p[index]++;//替换元素

}

else if(index==m-1)

{//取够了,输出

for(i=0;i<m;i++)

{

printf("%d,",a[p[i]]);

}

printf("/n");

p[index]++; //替换元素

}

else

{//多取一个元素

index++;

p[index]=p[index-1]+1;

}

}

free(p);

return true;

}




//对n个元素的数组a,进行全排列

bool pailie(char a[],int n)

{//p[x]=y 取到的第x个元素,是a中的第y个元素

int i,j,temp,*p;

p=(int*)malloc(sizeof(int)*n);

if(p==NULL)

{

return false;

}

for(i=0;i<n;i++)

{//初始排列

p[i]=i;

}

while(true)

{//循环m=n!次

//输出一种排列情况

for(i=0;i<n;i++)

{

printf("%c",a[p[i]]);

}

printf("/n");

//从后向前查找,看有没有后面的数大于前面的数的情况,若有则停在后一个数的位置。

for(i=n-1;i>0 && p[i]<p[i-1];i--);

//若没有后面的数大于前面的数的情况,说明已经到了最后一个排列,返回

if(i==0) break;

//从后查到i,查找大于p[i - 1]的最小的数,记入j

for(j=n-1;j>i && p[j]<p[i-1];j--);

//交换p[i-1]和p[j]

temp=p[i-1];p[i-1]=p[j];p[j]=temp;

//倒置p[i]到p[n-1]

for(i=i,j=n-1;i<j;i++,j--)

{//交换p[c]和p[d]

temp=p[i];p[i]=p[j];p[j]=temp;

}

}

free(p);

return true;

}



int main()


{

int a[]={1,2,3,4,5};

zuhe(a,5,3);

//pailie(a,3);//排列

return 0;

}

发布时间: 8年前算法逻辑142人已围观返回回到顶端

很赞哦! (1)

文章评论

  • 请先说点什么
    热门评论
    141人参与,0条评论

站点信息

  • 建站时间:2016-04-01
  • 文章统计:728条
  • 文章评论:82条
  • QQ群二维码:扫描二维码,互相交流