Blog1:字符串全排列算法

字符串全排列算法

问题描述

给出一个字符串(可能包含字母和数字)给出该字符串中字符的所有非重复排列

例子

输入:abc
输出:abc, acb, bac, bca, cab, cba

分析

1.典型的递归方法

  • 递归问题分解
    假设求解长度为n字符串的全排列,将字符串划分为首字符和剩余的n-1个字符串,将首字符与字符串中每个字符进行交换,加上每次交换后剩余长度n-1个字符串的全排列。
    对于n-1个字符串的全排列,又可以继续分解为首字符n-2个字符串的全排列,接下来按照相同步骤进行递归分解。
  • 递归终止条件
    按照上述递归问题的分解,当字符串中只剩下最后一个字符时,不需要进行交换,此时递归达到终止条件。

实例分析

下面对上述例子字符串“abc”进行分析,此时字符串长度为3,首字符为‘a’。
第一层递归:
1 ‘a’与第一个字符交换,即与本身交换,字符串不变为“abc”,首字符依旧为‘a’,问题分解为求长度为2的字符串“bc”的全排列。
第二层递归
1.1 ‘b’与第一个字符交换,即与本身交换,字符串不变为“bc”,首字符依旧为‘b’,问题分解为求长度为1的字符串“c”的全排列。
第三层递归
1.1.1 此时字符串长度为1,达到递归终止条件,输出当前排列“abc”。
跳出第三层递归
第二层递归
1.2 ‘b’与第二个字符‘c’交换,字符串变为“cb”,首字符为’c’,问题分解为求长度为1的字符串“b”的全排列。
第三层递归
1.2.1 此时字符串长度为1,达到递归终止条件,输出当前排列“acb“。
跳出第三层递归
1.2.2 将序列还原成原来样子“bc”,如果不这样的话字符串顺序会混乱,无法依次交换首字符和后面的字符。
跳出第二层递归
2 ‘a’与第二个字符交换,字符串不变为“bac”,首字符为‘b’,问题分解为求长度为2的字符串“ac”的全排列。
后面的可以同理得出…

存在问题

此时当存在相同的字符进行交换时,会出现重复排列,去除重复排列时只需将交换条件设置为不相等时才交换。
如果需要对输出结果完全按照字母顺序排序,则可以使用set存储结果,直接解决了排序和去除重复两个问题。
非完全按照字母排序代码如下:

 vector<string> Permutation(string str) {
        vector<string> vec;
        if(str.size()!=0)
            core(str,str.begin());
        return vec;
    }
void core(string &str, string::iterator strIt)
    {
        if (strIt ==str.end() )//递归结束条件
            vec.push_back(str);            
        else
        {
            for (string::iterator tmpIt = strIt; tmpIt != str.end(); ++tmpIt)
            {
                if(tmpIt!=strIt&&*tmpIt==*strIt)//去除重复字符交换,产生重复序列
                    continue;
                //首字母与后面字母交换
                char tmp = *tmpIt;
                *tmpIt = *strIt;
                *strIt = tmp;

                core(str,  strIt + 1);//进行递归

                //恢复交换前顺序
                tmp = *tmpIt;
                *tmpIt = *strIt;
                *strIt = tmp;
            }
        }

    }

2.字典序方法(非递归)

每个排列可看作成一个字符串,要求前后两个字符串之间有尽可能长的共同前缀,避免了递归,更为高效,STL 中next_permutation、prev_permutation等函数就使用了这种算法,具体讲解和代码可以参考《STL源码解析》P380~383,这里就不赘述了。
但是这种算法依然无法避免出现重复序列问题,为了解决这种问题可以使用set解决,去掉重复项。

参考博客

字符串全排列算法
字典序全排列算法

点赞