字典序排列
把升序的排列(当然,也可以实现为降序)作为当前排列开始,然后依次计算当前排列的下一个字典序排列。
对当前排列从后向前扫描,找到一对为升序的相邻元素,记为i和j(i < j)。如果不存在这样一对为升序的相邻元素,则所有排列均已找到,算法结束;否则,重新对当前排列从后向前扫描,找到第一个大于i的元素k,交换i和k,然后对从j开始到结束的子序列反转,则此时得到的新排列就为下一个字典序排列。这种方式实现得到的所有排列是按字典序有序的,这也是C++ STL算法next_permutation的思想。算法实现如下:
template <typename T>
void CalcAllPermutation(T perm[], int num)
{
if (num < 1)
return;
while (true) {
int i;
for (i = num - 2; i >= 0; --i) {
if (perm[i] < perm[i + 1])
break;
}
if (i < 0)
break; // 已经找到所有排列
int k;
for (k = num - 1; k > i; --k) {
if (perm[k] > perm[i])
break;
}
swap(perm[i], perm[k]);
reverse(perm + i + 1, perm + num);
}
}
3. 组合算法
3.1 全组合
在此介绍二进制转化法,即,将每个组合与一个二进制数对应起来,枚举二进制的同时,枚举每个组合。如字符串:abcde
00000 <– –> null
00001<– –> e
00010 <– –> d
… …
11111 <– –> abcde
3.2 从n中选m个数
(1) 递归
a. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。
b. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。
下面是递归方法的实现:
/// 求从数组a[1..n]中任选m个元素的所有组合。
/// a[1..n]表示候选集,n为候选集大小,n>=m>0。
/// b[1..M]用来存储当前组合中的元素(这里存储的是元素下标),
/// 常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。
voidcombine( inta[], intn, intm, intb[], constint M )
{
for(inti=n; i>=m; i--) // 注意这里的循环范围
{
b[m-1] = i - 1;
if(m > 1)
combine(a,i-1,m-1,b,M);
else // m == 1, 输出一个组合
{
for(intj=M-1; j>=0; j--)
<SPAN style="WHITE-SPACE: pre"> </SPAN>cout << a[b[j]] << " ";
cout << endl;
}
}
}
洗牌算法:
错误方法
1。随机产生一个1-n的数x,然后让第x张牌和第1张牌互相调换。
2。随机产生一个1-n的数y,然后让第y张牌和第2张牌互相调换。
3。随机产生一个1-n的数z,然后让第z张牌和第i张牌互相调换。(i=3,4,5...54)
这种算法的复杂度为O(N)。
看好像不错,网上也有很多文章使用这种算法,但是其实这是一种错误的方法,因为方法二的所有可能性为N^N,而洗好的牌一种有N!种可能,又因为N^N % N! !=0,所以每种结果的概率是不相同的。
那么如何修正这个问题呢?第i次洗牌不是产生一个1-n的随机数,而是产生一个i-n的随机数,这样可能性结果的可能性就是N!了。就有可能概率相等了。
证明在<<计算机程序设计艺术>>上。
修正方法:
#include <stdio.h>
#include <stdlib.h>
#define swap(a,b) {int t=(a);(a)=(b);(b)=(t);}
#define N 54
int poker[N+1];
void shuffle(){
int i,k;
for(i=1;i<=N;i++)//初始化
poker[i]=i;
for(i=1;i<=N;i++){
k = rand()%(N+1-i)+i;
swap(poker[i],poker[k]); //交换
}
}
int main(){
int i;
srand(time(NULL));
shuffle();
for(i=1;i<=N;i++)
printf("%d ", poker[i]);
printf("\n");
}
转自:http://blog.csdn.net/v_july_v/article/details/6879101