全排列

全排列算法

问题:

求一个数字序列的全排列,比如【1,2,3】的全排列为
123, 132, 213, 231, 312, 321

天真的方法是采取自上而下的递归方法。我们可以选择第一个元素,然后递归并从其余元素中选择第二个元素,依此类推。但是这种方法很棘手,因为它涉及到递归,堆栈存储和跳过重复值。此外,如果我们坚持要按顺序操作序列(而不产生临时数组),那么很难按字典顺序生成排列。

所以我们选择的是从最低排列开始,然后重复计算就位的下一个排列。

递归算法描述

要对1、2、3、4进行排序,第一个位置上的元素有四种可能:1或2或3或4,假如已经确定了第一个元素为4,剩下的第二个位置上可以是1、2、3,很显然这具有递归结构,如果原始要排列的数组顺序为1、2、3、4,现在只要分别交换1、2,1、3,1、4然后对剩下的3个元素进行递归的排列。
img


void permutation(char* a, int k, int m)
{
	int i, j;
	if (k == m)
	{
		for (i = 0; i <= m; i++)
			cout << a[i];
		cout << endl;
	}
	else
	{
		for (j = k; j <= m; j++)
		{
			swap(a[j], a[k]);
			permutation(a, k + 1, m);
			swap(a[j], a[k]);
		}
	}

}
字典排序法算法描述

0, 1, 2, 5, 3, 3, 0 为例子

将排列后当做一个数,那么求下一个排列时就需要两个数之间的差尽量的小,比如说123132就增加9,而其他的排列所增加的值都是大于9的。所以我们的问题就变成找到下一个排列使得与当前排列的差最小。

​ 那么怎么样才能让上升的值变小呢?如果去对高位(前)的数进行交换,那造成的增量就会比对低位(后)的数进行交换更大,所以应该先从后往前找而不是从前往后找。也因为这个原因,找到了这个第一个降序的邻居x[i],x[i+1]的意思也就可以理解为 所有以 x[0…i]这个前缀开头的情况已经走到了最后一步(因为x[i+1]及之后的都是降序了),所以就需要把x[i]增加才能进入下一步。接下来就是决定x[i]应该增加到多少,或说与谁换。

举例来说,对于 [0,1,2,5,3,3,0] 的情况,从后往前第一个看到的降序邻居是 [2,5] ,这就说明,这个全排列已经走完了 [0,1,2] 开头的所有情况,接下来得要是 [0,1,3] 开头了。这里的 i=2,就说明现在的前缀是 x[0:2]。

找到了之后,到了第三步,那么从最后面一直看到x[i],其实是先升后降的过程。既然是先升后降那就肯定有一个数会比x[i]要来得大。那为了让第 i 位增加得尽可能小,所以就会从后往前找第一个比x[i]大的数。回到刚才的例子,这里x[2]=2应该与x[5]换,其中x[5]=3。就是说,把前缀从 [0,1,2] 进化到 [0,1,3] 是增量最小的,如果增加到 [0,1,5] 就增加得太多了。

在找到了之后,全排列就从 x[0,1,2,3,4 … i] 这个前缀就更新了,也就是说接下来一段时间内,都是以 [0,1,3] 开始的。以 [0,1,3] 开头的第一个排列,应该是之后的所有元素递增的。但因为恰好在上一个状态,也就是 [0,1,2] 开头时的情况,之后的元素是递减的,而将 2 与 3 进行交换亦不会改变之后的元素的递减的性质,所以 把后面的元素从 递减 变成 递增 就只需要 reverse 一下就可以了。

总的来说,给定输入 [0,1,2,5,3,3,0],就会
在第一步时找到第3、4个元素组成的邻居,[0,1,2,5,3,3,0],i=2,x[i]=2, x[i+1]=5;
在第二步时从后往前看到第一个比2大的元素,就是x[5]=3,并与x[2]交换,数组变成[0,1,3,5,3,2,0];
在第三步时将第四个元素及其后的元素反向,成为[0,1,3,0,2,3,5]。

在这里插入图片描述

  • 代码

判断该数组能不能进行全排列

bool hasNext()
{
    for( int i = N; i > 0; i--)
        if( num[i] > num[i-1])
            return true;
    return false;
}

全排列

  1. 找到第一个要交换的数
for( int i = N-1; i >0; i--)
{
    if( num[i] > num[i-1])
    {
        top = i;
        break;
    }
}
  1. 确定第二个要交换的数
int mm = top;//mm为要交换数的下标
    //如果top后面还有比top前面的数(也就是num[top-1)小的话,就先交换那个小的数
    for( int i = top; i < N; i++)
    {
        if( num[i] > num[top-1] && num[i] < num[top])
            mm = i;
    }
  1. 交换两个数
void _swap( int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
_swap(&num[mm],&num[top-1]);
  1. 最后一步,得到最小, 反转后缀
for(int i=0;i<=(top+N-1)/2-top;i++)
        _swap(&num[i+top],&num[N-1-i]);

参考:参考网页

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值