随机数
线性同余法
(7)线性同余发生器(Linear Congruence Generator, LCG)
d是初始值【种子】
统一术语:an-1是乘数,c是增量,m是模数
LCG(线性同余发生器)改善
【例9-1】 用线性同余法生成一个随机序列。
计算模型:
选择系统时间作为种子,令b=1194211693L,c=12345L,m=2147483647(2^31-1),则有
系统函数:
srand(time(NULL));
rand()
算法设计与描述 | |
输入:种子值 | |
输出:伪随机序列 | |
//伪随机序列 const unsigned long b = 1194211693L; void mysrand(unsigned long seed) double myrandf() int main() |
代码:
#include<iostream>
#include<math.h>
#include<ctime>
#include<cstdlib>
using namespace std;
//伪随机序列
const unsigned long b = 1194211693L;
const unsigned long c = 12345L;
static unsigned long rand_seed;
void mysrand(unsigned long seed)
{
rand_seed = seed;
}
long myrand()
{
rand_seed = (rand_seed * b + c) % ((1 << 31) - 1);//左移相当于×2
return rand_seed;
}
double myrandf()
{
return (double)rand_seed / ((1 << 31) - 1);
}
int main()
{
int i;
mysrand(time(0));//time(0)是种子数
srand(time(0));//系统函数
for (i = 0; i < 50; i++)
{
myrand();
cout<<"rand_seed:" << rand_seed;//或者是cout<<myrand();
cout <<"\tmyrandf():"<< myrandf();
cout<<"\trand():"<<(double)rand()/RAND_MAX<<endl;
}
return 0;
}
蒙特卡罗算法(Monte Carlo,MC)
它将所求解的问题同一定的概率模型相联系,用计算机实现统计模拟或抽样,以获得问题的解。
时间: 20世纪40年代美国在第二次世界大战
项目:研制原子弹的“曼哈顿计划”
作者:S.M.乌拉姆和J.冯·诺伊曼
命名:数学家冯·诺伊曼用驰名世界的赌城—摩纳哥的Monte Carlo
起源:1777年,法国数学家布丰(Georges Louis Leclere de Buffon,1707—1788)提出用投针实验的方法求圆周率π
统计模拟—蒙特卡罗算(MC)
1.数值计算
【例9-2】 利用随机函数计算圆周率π:
可能得不到正确结果 1.随机数符合概率(正态分布)2.点数足够(样本量足够多)
精度不高
(1) 数学模型:
将n根飞镖随机投向一正方形的靶子,计算落入此正方形的内切圆中的飞镖数目k。
假定飞镖击中方形靶子任一点的概率相等。
设圆的半径为r,圆的面积s_1=πr^2 , 正方形面积s_2=4r^2
由随机序列均匀分布的假设可知落入圆中的飞镖和正方形内的飞镖平均比为:k:n=πr^2:4r^2
由此可知:π=4k/n
取单位圆,取第一象限,则有长方形:0<=x<=1,0<=y<=1;圆形:x2+y2<=1
(2) 算法描述:
Step 1: 设定总点数
Step 2: 取随机数x, y
Step 3: 判断x2+y2<=1,是则圆点数加1
Step 4: 判断所取点数达到总点数,是,计算π值,否,重复执行step 2.
(3) 算法分析:O(n)
代码:
#include<iostream>
#include<ctime>
#include<cstdlib>
using namespace std;
int main()
{
int n;//随机数个数=所有点数
int inside=0;//位于圆中的点数
double x,y;
cout<<"输入随机数的个数:";
cin>>n;
srand((unsigned int)time(NULL)) ; //初始化种子数
for(int i=0;i!=n;i++)
{
x=rand()*1.0/RAND_MAX;
y=rand()*1.0/RAND_MAX;
if(x*x+y*y<=1)
inside++;
}
printf("%6.5lf",4.0*inside/n);
system("PAUSE>NUL");
return 0;
}
【例9-3】
(1) 数学模型
(2) 算法分析:
算法设计与描述 | |
---|---|
输入:投射的点数n | |
输出:面积G | |
void getG() { int n;//随机数个数=所有点数 double x,y,m=0; cout<<"输入随机数的个数:"; cin>>n; srand((unsigned int)time(NULL)) ; //初始化种子数 for(int i=0;i!=n;i++) { x=rand()*1.0/RAND_MAX; y=rand()*1.0/RAND_MAX; if(y<=f(x)) m=m+1; } printf("f(x)面积=%6.5lf",m/n); system("PAUSE>NUL"); } |
PS:
MC算法在一般情况下可以保证对问题的所有实例都以高概率给出正确解,但是通常无法判定一个具体的解是否正确。
【p正确(p-correct)】
设如果一个MC算法对于问题的任一 实例得到正确解释的概率不小于p,p是一个实数,且 1/2≤p<1,称MC算法是p正确的,且称p - 1/2是该算法 的优势。
【一致的(consistent)】
如果对于同一实例,蒙特卡罗算法不会给出2个不同的正确解答。
【偏真】
设MC(x)是解某个判定问题D的蒙特卡罗算法,当MC(x)返回true时解总是正确的,仅当它返回false时有可能产生错误的解,称为偏真,反之为偏假。
偏真-说真就是真,说假可能是假。
偏假-说假就是假,说真可能是真。
【例9-4】素数测试:判断给定整数是否为素数。
梅森Mersenne 2^p-1(p为素数)
算法1:穷举
算法2:取2~n^(1/2)随机数,判断其是否为n的因子
算法3:
(1)Wilson定理:充要条件
证明:取集合 A={1, 2, 3, …… , n-1}: 则 A 构成模 n 完全剩余系。∀ i ∈ A,存在 j ∈ A ,使得: (i*j)≡1(mod n) ,则对于A中不能两两配对的成员x ,考虑 x 2 ≡1(mod n) -> x^ 2 -1≡0(mod n) -> (x-1)(x+1) ≡0(mod n)解得 x≡1(mod n) 或 x≡n-1(mod n)其余两两配对:故而 (n-1)! ≡1*(n-1) ≡-1(mod n)
(2) Fermat小定理:——必要条件,非充分
341 符合 但是合数 2^340 mod 341 =1 ,341=11*31
Fermat小定理是必要条件满足 Fermat 小定理而不是素数的合数叫 Carmichael 数10亿个自然数中素数50847534个, Carmichael数5597,
运用Fermat小定理出错的几率为0.00011
【引理 1 】若 a,b,c 为任意 3 个整数 ,m 为正整数,且 (m,c)=1 ,则当 a·c≡b·c(mod m) 时,有 a≡b(mod m) 。证明: a·c≡b·c(mod m) 可得 ac–bc≡0(mod m) 可得 (a-b)·c≡0(mod m) 。因为 (m,c)=1即 m,c 互质, c 可以约去, a– b≡0(mod m) 可得 a≡b(mod m)【引理 2 】设 m 是一个 整数 且 m>1 , b 是一个 整数 且 (m,b)=1 。如果a1,a2,a3,a4,…am 是模 m 的一个完全剩余系,则b·a1,b·a2,b·a3,b·a4,…b·am 也构成模 m 的一个完全剩余系。证明 : 若存在 2 个整数 b·ai 和 b·aj 同余即 b·ai≡b·aj(mod m)..(i>=1 && j>=1) ,根据引理 1 则有 a[i]≡a[j](mod m) 。根据完全剩余系的 定义 可知这是不可能的,因此不存在 2 个 整数 b·a[i] 和 b·a[j] 同余。
a*a % n = 1(a-1)*(a+1) % n = 0因此 a=1或n-1它是F小定理的一个特例
偏假算法【说假就是假,说真不一定是真】
计算模型:
计算模型
(4)算法设计
int power(unsigned int a,unsigned int b,unsigned int n)
{
unsigned int y=1,m=b,z=a;
while(m>0)
{
while(m%2==0)
{
//此时是偶数
int x=z;
z=z*z%n;
m=m/2;
if( (z==1)&&(x!=1)&&(x!=(n-1)))//判断是否为合数
return 1;
}
m--;
//此时是奇数
y=y*z%n;
}
if(y==1)
return 0;
return 1;
}
int prime(unsigned int n)
{
int i,a;
srand(time(NULL));
for(i=1;i<log(n)/log(10) ;i++)
{
a=rand()%n;
if(power(a,n-1,n))
return 0;
}
}
代码:
#include<iostream>
#include<ctime>
#include<cstdlib>
#include<math.h>
//#include<random>
using namespace std;
//算法1:穷举
int primeQiongJu(int n )
{
//从2到根号n
for(int i=2;i<=sqrt(n);i++)
{
if(n%i==0)return 0;//合数
}
return 1;//素数
}
//算法2:随机数 ——多试几次,会提高正确率
int random01(int x,int y)
{
srand(time(NULL));
int z=rand()%(y-x)+x;
return z;
}
int primeSuiJi(int n)
{
// int d=random(2,sqrt(n));//编译器不支持
int d=random01(2,sqrt(n));//从2-根n中随机取一个数
if(n%d==0)
return 0;//非素数
return 1;
}
//03F费尔玛小定理
int power(unsigned int a,unsigned int b,unsigned int n)
{
//a是随机数 n-1 n
//初始化
unsigned int y=1,m=b,z=a,x ;
//m是指数
while(m>0)
{
while(m%2==0)//偶数
{
x=z;
z=z*z%n;
m=m/2;
//判断 x是否为合数
if( (z==1)&&(x!=1)&&(x!=(n-1)) )
return 1;//合数
}
m--;
y=y*z%n;//
}
//退出时 m<=0
if(y==1)
return 0;//素数
else return 1;//合数
}
int primeF(int n)
{
int a;
//种子数
srand(time(NULL));
//为了提高正确率,根据n的位数
//多运行log10(n)次算法
for(int i=1;i<log10(n);i++)
{
a=rand()%n;// 0-(n-1)
if(power(a,n-1,n))
return 0;//合数
}
return 1;//素数
}
int main()
{
int n=53;
if(primeQiongJu(n))
{
cout<<"01.穷举:"<<n<<"is prime"<<endl;
}
if(primeSuiJi(n))
{
cout<<"02.随机数:"<<n<<"is prime"<<endl;
}
if(primeF(n))
{
cout<<"03.随机数:"<<n<<"is prime"<<endl;
}
return 0;
}
舍伍德算法
【例9-5】设A是含有n个元素的集合,从A中选出第k小的元素,其中1≤k≤n。
计算模型:
算法设计与描述 | |
输入:数列a[n] 和k 值 | |
输出 :第k小元素 | |
#include<iostream> int select(int a[],int left,int right,int k) int main() |
#include<iostream>
#include<ctime>
#include<cstdlib>
using namespace std;
int select(int a[],int left,int right,int k)
{
int i,j,pivot,t;
if(left>=right)//结束递归
return a[left];
i=left;
j=left+rand()%(right-left);
swap(a[i],a[j]);
pivot=a[left];
j=right+1;
while(true)
{
while(a[++i]<pivot);
while(a[--j]>pivot);
if(i>=j)break;
t=a[i];a[i]=a[j];a[j]=t;
}
if(j-left+1==k)return pivot;
a[left]=a[j];
a[j]=pivot;
if(k<(j-left+1))select(a,left,j-1,k);
else select(a,j+1,right,k-j-1+left);
}
int main()
{
int a[]={1,2,3,4,5,6,7,8,9};
int k=3;
cout<<select(a,0,8,3);
}
拉斯维加斯算法
【例9-6】n皇后问题
#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
//n皇后问题
int check(int a[],int n)
{
int i;
for(i=1;i<n;i++)
{
//位于对角线 或者 位于同一列
if(abs(a[i]-a[n])==abs(i-n) || (a[i]==a[n]))
return 0;//该种选择不成立
}
return 1;
}
//
int lv(int ty[])
{
int i;
srand(time(NULL));
for(i=1;i<=8;i++)
{
ty[i]=rand()%8+1;//从1到8
if(!check(ty,i))
return 0;//不成立
}
return 1;
}
void queens()
{
int ty[9]={0};//存储列坐标
int success=0;
int n=0;
int i;
while(!success && n<1000000 )
{
success=lv(ty);
if(success)
{
cout<<"success:"<<n<<endl;
for(i=1;i<=8;i++)
cout<<ty[i]<<"\t";
cout<<endl;
success=0;
}
n++;
for(i=1;i<=8;i++)
{
ty[i]=0;
}
// cout<<endl<<"n="<<n;
}
}
int main()
{
queens();
return 0;
}