算法思维(递归)训练:输出字符串字符的全排列

题目

设计一个算法,输出一个字符串字符的全排列。 比如,String = “ABC” 输出是
ABC
ACB
BAC
BCA
CBA
CAB

思路

可能我们的第一直觉是,这就是一个选择问题,第一个字符3选1,第二个字符2选1,第三个字符没得选,三层循环可快速暴力求解。可以用嵌套循环来生成新的字符串。

但是这个字符串的长度是不定的,我们没有一种语法可以指定循环的层数。

如果层数根据运行情况来定,唯有递归。

使用递归必须抽象出一个局面x,当下处理一部分使得局面简化为y,递归求解局面y,以此类推,直到问题足够简单可显然求解,然后层层返回。

抽象递归时,我们可以想象我有一个下属,下属又有一个下属,所有这些人都能解决同样的问题。最高层领导只需将局面简化一点点,然后委派下属去处理这个简化后的类似的局面即可,然后就是等结果。

在此题中,我们可以这样考虑:

    目标:输出全排列(字符数组)
        我来搞定第一个字符
        剩下的事,下级来处理剩下的部分
        处理到最后一个字符时,就可以输出这个重新排列过的数组了

但很快你会发现,搞定第一个字符有很多种选择,上面的推理需要改写成:

    目标:输出全排列(字符数组)
        我来搞定第一个字符,这件事我会重复N次,每次选字符串中的一个
            一旦选定(把某个字符挪到首位)
            剩下的事,下级来处理剩下的部分
            因为下次我要再选一个,所以先恢复到挪位之前

那么什么时候输出呢?
这个函数每调用一次,都是上级排好了前面的字符,我来处理后续字符,如果我处理的是最后一个字符,因为上级已经排好前面的,我整体输出全部字符即可。

伪码

// data:上级给任务时字符数组,index:上级已经排好了0~index-1的字符,要我处理index~length-1这段
f(char[] data,int index)
    // 现在我试着从index开始找一个字符来作为首位,我要尝试length-indexfor(int i=index;i<length;i++)
        swap(data,i,index);// 把i位置的字符交换到index这里来,我的工作就完成了
        // 下面请直接下级来接招
        f(data,index+1);
        // 下级排好了,我即将选定下一个字符作为首位,必须先恢复一下
        swap(data,i,index);

    // 如果上级们已经处理好全部字符,我就输出
    if(index==length)
        println(data);

Java代码

package org.lanqiao.algo.recursion;

/**
 * 输出字符串所有字符的全排列
 * */
public class StrPermutation {

  static int n;

  public static void main(String[] args) {
    String s = "ABCDE";
    permutation(s.toCharArray(), 0);
    System.out.println("---" + n + "---"); // n=长度的阶乘,可以用这个数字验证算法的正确性
  }


  //index代表0~index-1都已确认排列,[index,n-1]待排列
  private static void permutation(char[] arr, int index) {
    //至于什么时候输出,要考虑清楚
    //考虑:只有所有位置上的字符都确认即index到达末尾,才算是排好一种情况
    if (index == arr.length) {
      n++;
      System.out.println(String.valueOf(arr));
    }
    //现在index之前的字符都已就位,把之后的每个字符都交换到index这个位置
    for (int k = index; k < arr.length; k++) {
      //尝试交换
      swap(arr, index, k);
      //交换之后index这个位置就定好了,接下来的事就是递归去解决index+1位置开始的全排列就好了
      permutation(arr, index + 1);
      // 前面我们尝试交换,把全排列求出来了,交换时只尝试了一个字符,因此for循环继续之前要换回来,继续尝试交换下一个字符
      swap(arr, index, k);
    }
  }

  private static void swap(char[] arr, int index, int k) {
    if(k==index)
      return; // 不必交换了吧
    char tmp = arr[k];
    arr[k] = arr[index];
    arr[index] = tmp;
  }
}

小结

最终代码其实比较简短,这是递归的特点:代码简单,思路绕弯。考验抽象思维和想象力。
使用递归,必须找到问题分解后子问题的相似性,换句话说,你必须找到某种分解结构,按照分解链路可以将问题简化直至可直接得出结论。

发布了127 篇原创文章 · 获赞 97 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/zhengwei223/article/details/78627003
今日推荐