经典算法排列组和之排列

我们在高中就接触过排列组和

æåç»åå¬å¼

那么我们如何用程序去模拟这个过程,从而达到列出所有排列组合的可能呢?

全排列的实现

1.暴力求解(不可取)

#include <stdio.h>
#include <string.h>
//暴力求解
int main()
{
    char data[] = "abc";
    for(int i = 0; i < strlen(data); i++)
        for(int j = 0; j < strlen(data); j++)
            for(int k = 0; k < strlen(data); k++)
            if(data[i] != data[j] && data[j] != data[k] && data[i] != data[k])
            printf("%c%c%c\n", data[i], data[j], data[k]);

	return 0;
}

看上去还可以的样子,不过这样有几个坏处,如果不想全排列abc了,而是想对abcd进行全排列了,那么我们必须要修改源代码增加一个for循环,而且如果排列的数很多的话,那这个循环也太多了吧。

2.递归求解

看递归实现,由于递归将问题逐级分解,因此相对比较容易理解,但是需要消耗大量的栈空间,如果线程栈空间不够,那么就运行不下去了,而且函数调用开销也比较大。

例如:{1, 2, 3}的全排列为:

123;132;

213;231;

312;321;

共6个,即3!=321=6。

这个是怎么算出来的呢?

首先取一个元素,例如取出了1,那么就还剩下{2, 3}。

然后再从剩下的集合中取出一个元素,例如取出2,那么还剩下{3}。

以此类推,把所有可能的情况取一遍,就是全排列了,如图:

æåç»åç®æ³

è¿éåå¾çæè¿°

知道了这个过程,算法也就写出来了:

将数组看为一个集合,将集合分为两部分:0~s和s~e,其中0~s表示已经选出来的元素,而s~e表示还没有选择的元素。

perm(set, s, e)
{

    顺序从s~e中选出一个元素与s交换(即选出一个元素)
    调用perm(set, s + 1, e)
    直到s>e,即剩余集合已经为空了,输出set
}

c语言代码如下:

#include<stdio.h>
void swap(char *p1,char *p2);
//将所有情况全排列表示出来
void permutation(char a[],int index,int size);

int main()
{
    int n = 3;
    char data[n] ="abc";
    permutation(data,0,n);

    return 0;

}

void swap(char *p1,char *p2)
{

    char t=*p1;
    *p1=*p2;
    *p2=t;

}

void permutation(char a[],int index,int size)
{
    if(index == size-1 ) //index+1也一样
    {
            for(int i=0;i<size;i++)
            printf("%c ",a[i]);
            printf("\n");

    }

    else
    {
        for(int j=index;j<size;j++)
        { 
             swap(&a[j],&a[index]);
             permutation(a,index+1,size);//此处用到递归思想
             swap(&a[j],&a[index]);
           
        }

    }

}

运行上面的代码,可以得到和上面暴力求解一毛一样的结果,且这次如果需要对其他情况进行全排列不需要再修改源代码,且只用了一个循环(虽然用递归效率还不如多个循环^-^),不过至少代码看上去还是很优雅的。

解决全排列的重复问题:

细心的小伙伴肯定会发现,上面的代码其实是有问题的,如果排列的数组中有重复的元素那么结果中也会有重复的排列,这是我们不希望看到的。那么我们如何解决这个问题呢?

要想解决这个问题,我们首先需要知道这个问题是怎么来的,还是参考刚刚的图,我们稍微修改下:

è¿éåå¾çæè¿°

  • 当以第一个c为开头时,我们需要对ac进行全排列,没问题

  • 当以a为开头时,我们需要对cc进行全排列,没问题

  • 当以第二个c为开头时,我们需要对ca进行全排列,这就有问题了,ac和ca的全排列不就一样的嘛,而且开头也一样,这个肯定就会有重复了呀,我们对源码稍加修改下:

  • 添加一个判断函数:

  • int equal(char a[], int left, int right)
    {
        for(int i = left; i < right; i++)
            if(a[i] == a[right])
            return 1;
        return 0;
    }
    
#include<stdio.h>
int equal(char a[], int left, int right);
void swap(char *p1,char *p2);
void permutation(char a[],int index,int size);

int main()
{
    char data[n] ="cac";//
    permutation(data,0,n);

    return 0;

}
int equal(char a[], int left, int right)
{
    for(int i = left; i < right; i++)
        if(a[i] == a[right])
        return 1;
    return 0;
}

void swap(char *p1,char *p2)
{

    char t=*p1;
    *p1=*p2;
    *p2=t;

}

void permutation(char a[],int index,int size)
{
    if(index == size-1 ) //index+1也一样
    {


            for(int i=0;i<size;i++)
            printf("%c ",a[i]);
            printf("\n");

    }

    else
    {
        for(int j=index;j<size;j++)
        {
            if ( equal(a, index, j) )
                continue;
            else
                {
                    swap(&a[j],&a[index]);
                    permutation(a,index+1,size);//此处用到递归思想
                    swap(&a[j],&a[index]);
                };
        }

    }

}

ok,这样运行上面的代码的就不会有重复的问题了,这里可能需要小伙伴们多思考下了,不过还是很简单的。

https://blog.csdn.net/wangshengfeng1986211/article/details/38366709

https://blog.csdn.net/zhaoshuaiwjm/article/details/78189003#%E8%BF%98%E8%AE%B0%E5%BE%97%E6%8E%92%E5%88%97%E7%BB%84%E5%90%88%E5%90%97

猜你喜欢

转载自blog.csdn.net/qq_41282102/article/details/81462413