全排列算法
问题:
求一个数字序列的全排列,比如【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个元素进行递归的排列。
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 为例子
将排列后当做一个数,那么求下一个排列
时就需要两个数之间的差尽量的小,比如说123
到132
就增加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;
}
全排列
- 找到第一个要交换的数
for( int i = N-1; i >0; i--)
{
if( num[i] > num[i-1])
{
top = i;
break;
}
}
- 确定第二个要交换的数
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;
}
- 交换两个数
void _swap( int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
_swap(&num[mm],&num[top-1]);
- 最后一步,得到最小, 反转后缀
for(int i=0;i<=(top+N-1)/2-top;i++)
_swap(&num[i+top],&num[N-1-i]);
参考:参考网页