全排列算法分析

来源:互联网 发布:数控编程培训班 编辑:程序博客网 时间:2024/05/19 19:32

问题:将给定的几个数字进行全排列。如:1,2,3 则这三个数字的全排列有:123,132,213,231,312,321。这6种情况。

那么我们如何用程序实现呢?


1、问题分析。

要解决这个全排列问题分三步,第一步从3个数中选择1个数共有三种方法,第二步从2个数中选出1个数有2种方法,第三步从1个数中选出1个数共有1种方法。也就是A(3,3)=3*2*1 = 6种方法。


2、代码实现

通过上面的分析可知这是一个分步乘法的问题,每步之间都有关系(第一步选择的结果会影响第二步的选择)。所以可以采用循环嵌套的方法来实现。代码如下:

void Fun(int A[],int len = 3){
    if(A==NULL || len != 3)
        return ;
    for(int i = 0; i < len; i++){//第一步有3种选择
        for(int j = 0; j < len; j++){//第二步有2种选择
        if(j == i) continue;
            for(int k = 0; k < len; k++){//第三步有1种选择
                if(k==j || k==i) continue;
                cout << A[i]<<A[j]<<A[k] << endl;//输出结果
            }
        }
    }
}

这种方法可以打印出1,2,3这三个数的全排列。而且很明显他也只能打印1,2,3的全排列。那么其他的数怎么办?如果是1234怎么办?按照原来的思路在加上一条for语句和几个判断就够了。但是如果是n个数呢?所以我们应该修改代码。使他能处理所有的数据。


3、代码优化

上面的问题是不能确定程序中到底要使用几个for循环。那么如何使我们的程序能够根据输入的数据来确定要执行几次for循环呢?我们可以想到使用递归。函数中包含了for循环语句。那么我们递归的调用函数,就可以达到多次调用for循环的目的。也就可以处理n个数的全排列。既然用函数递归代替了循环嵌套那么我们又要传递给函数什么参数呢?分析上面的代码可知在执行第二步时,使用了第一步的i(如: if(j == i) continue; )。也就是说我们要将每步的选择传递给下一步。于是写出如下代码:

bool IsExist(int A[],int findindex,int find[],int len){//查找是否前面的步骤已经选择了这个数值
    if(A == NULL || findindex < 0 || find == NULL || len < 1) return false;
    //findindex不在find里面
    while(len){
        if(find[len-1] == findindex) return true;
        len--;
    }
    return false;
}


void Show(int A[],int Find[],int len){ //打印已经选出来的排列
    if(A == NULL || Find == NULL || len < 1) return ;
    int index = 0;
    while(len--){
        cout << A[Find[index++]];
    }
}


void Fun(int A[],int lenA,int find[],int curlen){ //curlen是find[]数组的当前有效长度。初始值为0
    if(curlen == lenA){//已经选出来一个排列了
        Show(A,find,curlen); //显示已经选出来数字
        cout << endl;
        return ;
    }
    for(int i = 0; i < lenA; i++){//i是数组元素下标
        if(IsExist(A,i,find,curlen)) continue;//如果当前步骤选择的数字在 第1步...当前步-1 已经选过了就跳过。
        find[curlen++] = i;//将当前步选择数字的下标保存起来并传给下一个步骤。
        Fun(A,lenA,find,curlen);//进行下一步的选择。
        curlen--;//函数退出时要减一
    }
}

上面的方法就可以实现打印n个数的全排列了。但实现起来代码复杂了。我们可以换一种思路。


4、方法优化

上面的方法是将每步选择的数据传递给下一步。那么下一步再得到这个信息时不能直接使用。还需要在解析一下才能使用(如:if(IsExist(A,i,find,curlen)) continue;  )。这样我们就要写解析的步骤。如果我们将每步选择数据后的结果传递给下一步,程序实现起来就会方便很多。那么每步选择后的结果是什么?就是已经选择出来的数据和剩下没确定的数据。例如:第一步选择了1  那么就把 1(已经确定的数据)     23(待定的数据)传给第二步当参数。这样第二步就可以直接操作23了,而不用去判断当前选择的数据在前面的步骤是否已经选择了。代码如下:

bool IsSame(string temp,int index){//判断当前选择的数据在当前步中是否选择过了(去重复的)
    int i = index - 1;
    while(i>=0){
        if(temp[index] == temp[i]) return true;
        i--;
    }
    return false;
}


void Fun(string Dest,string Src){ //Dest是已经确定的数据  Src是待确定的数据
    if(Src.size() == 0){//待确定数据为0 也就是全部都确定了
        cout << Dest << endl;//输出 换行
        return ;
    }
    for(int i=0; i < Src.size(); i++){//当前步中可以选择的数据
        if(IsSame(Src,i)) continue;  //去重复
        string Temp = Src.substr(0,i)+Src.substr(i+1); //当前步选择后的结果(供给下一步的待处理数据)
        Fun(Dest+Src[i],Temp);//执行下一步操作
    }
}

上面的代码实现了n个数的全排列,并且实现了去重复。如何实现的去重复?在我当前步选择数据时我要判断我现在选择的数是否已经选过了(if(IsSame(Src,i)) continue;)。如果这个数据在当前步中已经选择了就跳过这个数据,选择下一个数据。



1 0
原创粉丝点击