全排列_逐步生成结果之回溯加递归

1.问题描述:输入一个字符串,输出该字符串的全部全排列集合

2.我们除了使用递归和递推在原来的字符串的基础上添加新的字符之外,还可以使用递归加上回溯的方法来交换字符串中字符的位置从而达到排列字符串的目的,因为数组是公共的数据空间,那么在交换字符串的位置之后那么需要把数组恢复到交换前的位置,那么这就是回溯,假如不进行回溯的话那么经过交换之后的数组将会是乱套的

在循环中递归对比普通的单分支和双分支的递归来说在理解上是比较困难的,单分支递归和双分支递归不需要再循环中嵌套递归来实现,而多分支的递归则需要在循环中嵌套递归来实现,所以需要自己在纸上进行简单例子的推理和分析来理解循环中的递归

3.下面是具体的代码,在循环中通过输出语句来输出相关变量的变化有助于我们理解循环+递归+回溯

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class Main {
    //输出结果非常有利于循环中递归的理解
    static List<String> res = new ArrayList<>();
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.next();
        getPermutation(str);
        //Collections.sort(res);
        System.out.println(res.size());
        for(String strOutput : res){
            System.out.println(strOutput);
        }
    }

    private static List<String> getPermutation(String A) {
        char arr[] = A.toCharArray();
        getPermutationCore(arr, 0);
        return res;
    }

    private static void getPermutationCore(char[] arr, int k) {
        if(k == arr.length){
            //排好了一种情况, 递归的一条之路已经走到底了
            res.add(new String(arr));
        }
        
        //从k为开始的每个字符,都尝试放在新排列的的k这个位置
        for(int i = k; i < arr.length; i++){
            swap(arr, k, i);
            System.out.println(k +" " + i);
            getPermutationCore(arr, k + 1);
            //System.out.println(k +" "+ i+" "+arr[0]+" "+arr[1]+" "+arr[2]);
            //回溯: 这是核心
            swap(arr, k, i);
            System.out.println(arr[0]+" "+arr[1]+" "+arr[2]+" "+arr[3]);   //每当一个递归函数碰到出口返回之后都会输出字符串的结                //果
            //System.out.println(arr[0]+" "+arr[1]+" "+arr[2]);
        }
    }

    //交换位置
    private static void swap(char[] arr, int i, int j) {
        char temp = arr[i];
        arr[i]  = arr[j];
        arr[j]  = temp;    
    }
}
 

下面在控制台输入字符串ABCD,观察控制台的变量 i 和 k 的变化和字符串排列的变化,下面是控制台的输出结果:

ABCD
0 0     i 和 k都等于零,A准备放在第一个位置   交换 i k 两个位置的字符   
1 1     
2 2
3 3     调用完 getPermutationCore(arr, 3),再调用getPermutationCore(arr, 4)因为此时 k = 4碰到出口那么这条支路结束进行层层的返回   (每一次i k 结果都会在碰到递归调用完成之后返回函数的时候才会输出 而且此时已经完成了回溯)
A B C D    输出 getPermutationCore(arr, 3) k = 3 i = 3调用完的结果
A B C D    输出 getPermutationCore(arr, 2) k = 2 i = 2调用完的结果
2 3             进行层层返回到k = 2 i = 2这一层之后发现有兄弟,因为原来调用的时候 i = k = 2
3 3             调用完之后还要进行for循环此时 i = 3 k = 2再进行递归下去
A B D C    输出getPermutationCore(arr, 3) k = 3 i = 3 调用完的结果
A B C D    返回到 k = 2 i = 3的函数调用而且这个时候已经字符串已经回溯了(已经交换回去了)
A B C D    返回到 k = 1 i = 1这一层  这一层已经调用完了
1 2             发现有兄弟那么进行循环然后递归调用此时进入下一轮循环中的k = 1 i = 2 然后递归调用直到碰到递归的出口
2 2             
3 3
A C B D    调用完碰到出口,输出 k = 3 i = 3调用完的结果
A C B D    输出 k = 2 i = 2调用完的结果
2 3             返回到 k = 2 i = 2 的时候发现有兄弟继续递归进入下一轮的for循环
3 3             
A C D B   调用完碰到出口,输出 k = 3 i = 3调用完的结果
A C B D   输出调用完 k = 2 i = 1的结果 
A B C D   返回到 k = 1 i = 2调用的这一层  这一层的调用已经结束
1 3            发现有兄弟进入循环递归下去
2 2
3 3
A D C B
A D C B
2 3
3 3
A D B C   这下面的分析与上面的一样
A D C B
A B C D
A B C D  返回到k = 0 i = 0这一层表示这一层已经全部调用完,此时交换的数组预警完全恢复到原来初始的状态,那么
0 1           下一次需要B尝试放在开始的位置继续递归下去k = 0 i = 1 直到整个大循环已经结束了
1 1
2 2
3 3
B A C D
B A C D
2 3
3 3
B A D C
B A C D
B A C D
1 2
2 2
3 3
B C A D
B C A D
2 3
3 3
B C D A
B C A D
B A C D
1 3
2 2
3 3
B D C A
B D C A
2 3
3 3
B D A C
B D C A
B A C D
A B C D
0 2
1 1
2 2
3 3
C B A D
C B A D
2 3
3 3
C B D A
C B A D
C B A D
1 2
2 2
3 3
C A B D
C A B D
2 3
3 3
C A D B
C A B D
C B A D
1 3
2 2
3 3
C D A B
C D A B
2 3
3 3
C D B A
C D A B
C B A D
A B C D
0 3
1 1
2 2
3 3
D B C A
D B C A
2 3
3 3
D B A C
D B C A
D B C A
1 2
2 2
3 3
D C B A
D C B A
2 3
3 3
D C A B
D C B A
D B C A
1 3
2 2
3 3
D A C B
D A C B
2 3
3 3
D A B C
D A C B
D B C A
A B C D
24
ABCD
ABDC
ACBD
ACDB
ADCB
ADBC
BACD
BADC
BCAD
BCDA
BDCA
BDAC
CBAD
CBDA
CABD
CADB
CDAB
CDBA
DBCA
DBAC
DCBA
DCAB
DACB
DABC

每一在for循环中字符串的第k个字符都尝试放在第一个位置上

每一次调用完递归函数之后然后返回到这一层之后都会交换回去所以最终进行下一次第二个字符想放到第一个位置上的时候该数组还是原来的转台所以不会说数组乱套了

而且递归碰到出口之后都会层层返回,返回到该层之后再执行递归函数这行代码下面的代码

猜你喜欢

转载自blog.csdn.net/qq_39445165/article/details/83214742