全排列的递归与非递归实现

全排列

花了一晚上的时间苦思冥想,终于是彻底理解了字符串全排列了。自己还是太渣了…..

所谓全排列,就是打印出字符串中所有字符的所有排列。例如输入字符串abc,则打印出 a、b、c 所能排列出来的所有字符串 abc、acb、bac、bca、cab 和 cba。根据排列组合的数学公式也可知道给定 n 个字符,全排列一共 n! 项。

递归实现

递归方法的全排列思想挺简单的,就是从第一个字符起,将它与其后面的每个字符
进行交换。每交换一次就算一种新的字符串(不包括重复的字符)。这些字符串的集合就是全排列了。

这句话听起来是有点摸不到头脑,但是回想一下我们自己手动求全排列的过程, 可以分为下面三种情况:

  • 第一位为 a , 则后面就是bc的全排列,一种 a 后接 bc, 一种 a 后接 cb

  • 第一位为 b , 则后面就是ac的全排列,一种 b 后接 ac, 一种 b 后接 ca

  • 第一位为 c , 则后面就是ab的全排列,一种 b 后接 ab, 一种 b 后接 ba

我们的选第一位这个操作,就相当于把a字符和它后面的字符(包含它本身)交换。
* abc 中 a 和 a 交换 => abc (第一位为a)

  • abc 中 a 和 b 交换 => bac (第一位为c)

  • abc 中 a 和 c 交换 => cba (第一位为c)

第一位已经确定为a了,那么怎样选第二位呢? 同样的思路:

  • bc 中 b 和 b 交换 => bc
  • bc 中 b 和 b 交换 => cb

现在第一位已经确定为 a 了,第二位确定为 b ,只剩下个 c ,不用再进行挑选字符了,直接输出abc就得出一种结果。

显然,上面的算法是行得通的,敲代码!

/*
全排列(递归交换)算法
1、将第一个位置分别放置各个不同的元素;
2、对剩余的位置进行全排列(递归);
3、递归出口为只对一个元素进行全排列。
*/

/**
* 将数组第i位和第j位交换
**/
function swap(arr,i,j) {
    if(i!=j) {
        var temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
}

/**
* @index 就是选第index位的字符
**/
function allRange(arr, index) {
    if(index == arr.length-1) {
         console.log(arr)
    } else {
        for(var i = index; i < arr.length; i++) {
            swap(arr, i, index); // 把 index 位的字符与其后面的字符依次交换
            allRange(arr, index + 1) // 对后面未选中的字符做全排列
            swap(arr, i, index); //关键!!!
        }
    }
};

allRange(['a','b','c'], 0)

/*
注:可以看到,在对后面未选中的字符做全排列之后,又重新交换了一次!
    因为递归过程中 swap()函数是会改变数组内元素顺序的。我们的本意是想让 a,b,c分别为头部一次
    如果我们把第二次交换的代码去掉,等递归终止回溯回来的时候,整个字符串已经顺序打乱了
    我们需要再次交换,回归初试状态。
*/

非递归实现

理解并完成上面的递归实现浪费了两个小时,发现上面的思路还不是很直观,有那么一丢丢绕。然后我发现还真有一个相对好理解的思路。
比如几个人排队,除了根据位置选人。也可以根据人选位置啊!拿abc三人排队举例:
* a 来的最早 , 则队伍里只有一人 ‘a’

  • b 过来插队 , 我们把原来的队列表示为 ’ _ a _ ’ 下划线代表空位置,则有两种插队方式 ba 和 ab

  • 现在 c 过来插队 , 假设之前的队伍为 ‘_ a _ b _’, 一共有三种插队方式。

    根据这个插队的思路可以写出下面的代码:

/**
* @queueSet 所有可能队列的集合 例如[[1]],[[1,2],[2,1]]
* @key 待插入的值
**/
function insert(queueSet, key) {
    var newQueueSet = [] // 新的队列集合
    queueSet.forEach(queue => {
        for(var j = 0; j < queue.length + 1; j++){
           // 插队后的新队列
           let newQueue =  queue.slice(0,j).concat(key, queue.slice(j));
           newQueueSet.push(newQueue)
        }
    })
    return newQueueSet ;
}

function permutation(arr){
     var queueSet = [arr[0]];
     for (var i = 1; i < arr.length; i++) {
         queueSet = insert(queueSet,arr[i])
     }
     return queueSet
}

console.log(permutation(['a','b','c','d']))
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值