已出连载:转自:http://www.wutianqi.com/?p=1742
1.《随机化算法(1) — 随机数》
最近在看王晓东的《计算机算法设计与分析(第3版) 》,感觉讲的挺不错的。这里先推荐下。
接下来的几章(包括本章),我准备以连载的方式讲出来,主要用到的资料是上面推荐的那本书以及《算法导论》和网上的资源,内容是概率分析与随机算法。文章内大部分内容出自书中,我仅以汇总形式以及个人理解加以补充。如有纰漏,欢迎指出。
概率算法的一个基本特征是对所求解问题的同一实例用同一概率算法求解两次可能得到完全不同的效果。这两次求解问题所需的时间甚至所得到的结果可能会有相当大的差别。一般情况下,可将概率算法大致分为四类:数值概率算法,蒙特卡罗(Monte Carlo)算法,拉斯维加斯(Las Vegas)算法和舍伍德(Sherwood)算法。
随机数在概率算法设计中扮演着十分重要的角色。在现实计算机上无法产生真正的随机数,因此在概率算法中使用的随机数都是一定程度上随机的,即伪随机数。
产生随机数最常用的方法是线性同余法。由线性同余法产生的随机序列a1,a2,…,an满足
1.a0=d
2.an=(b*an-1+c)mod m (n=1,2…….)
其中,b>0, c>=0, d>=m。d称为该随机序列的种子。
一般情况下,取gcd(m, b)=1,因此可取b为一素数。
这是一个随机数类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| const unsigned long maxshort = 65535L;
const unsigned long multiplier = 1194211693L;
const unsigned long adder = 12345L;
class RandomNumber{
private:
// 当前种子
unsigned long randSeed;
public:
// 构造函数,默认值0表示由系统自动产生种子
RandomNumber(unsigned long s = 0);
// 产生0 ~ n-1之间的随机整数
unsigned short Random(unsigned long n);
// 产生[0, 1) 之间的随机实数
double fRandom();
};
// 产生种子
RandomNumber::RandomNumber(unsigned long s)
{
if(s == 0)
randSeed = time(0); //用系统时间产生种子
else
randSeed = s;
}
// 产生0 ~ n-1 之间的随机整数
unsigned short RandomNumber::Random(unsigned long n)
{
randSeed = multiplier * randSeed + adder;
return (unsigned short)((randSeed >> 16) % n);
}
// 产生[0, 1)之间的随机实数
double RandomNumber::fRandom()
{
return Random(maxshort) / double(maxshort);
} |
利用这个随机数类,写一个程序,模拟抛硬币的实验。
抛10次硬币构成一个事件,每次事件记录得到正面的个数。反复模拟这个事件50,000次,然后对这50,000L次进行输出频率图,比较每次事件得到正面次数的比例。
以下是总的代码:
头文件 RandomNumber.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // RandomNumber.h
const unsigned long maxshort = 65535L;
const unsigned long multiplier = 1194211693L;
const unsigned long adder = 12345L;
#ifndef RANDOMNUMBER_H
#define RANDOMNUMBER_H
class RandomNumber{
private:
// 当前种子
unsigned long randSeed;
public:
// 构造函数,默认值0表示由系统自动产生种子
RandomNumber(unsigned long s = 0);
// 产生0 ~ n-1之间的随机整数
unsigned short Random(unsigned long n);
// 产生[0, 1) 之间的随机实数
double fRandom();
};
#endif |
类实现文件RandomNumber.cpp :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| // RandomNumber.cpp
#include "RandomNumber.h"
#include <iostream>
#include <stdlib.h>
#include <time.h>
using namespace std;
// 产生种子
RandomNumber::RandomNumber(unsigned long s)
{
if(s == 0)
randSeed = time(0); //用系统时间产生种子
else
randSeed = s;
}
// 产生0 ~ n-1 之间的随机整数
unsigned short RandomNumber::Random(unsigned long n)
{
randSeed = multiplier * randSeed + adder;
return (unsigned short)((randSeed >> 16) % n);
}
// 产生[0, 1)之间的随机实数
double RandomNumber::fRandom()
{
return Random(maxshort) / double(maxshort);
} |
主文件Main :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| // 主文件main
/*
* Author: Tanky woo
* Blog: www.WuTianQi.com
* Date: 2010.12.7
* 代码来至王晓东《计算机算法设计与分析》
*/
#include "RandomNumber.h"
#include <iostream>
#include <iomanip>
#include <time.h>
using namespace std;
int TossCoins(int numberCoins)
{
// 随机抛硬币
static RandomNumber coinToss;
int i, tosses = 0;
for(i = 0; i < numberCoins; ++i)
tosses += coinToss.Random(2);
return tosses;
}
int main()
{
// 模拟随机抛硬币事件
const int NCOINS = 10;
const long NTOSSES = 50000L;
// heads[i]得到的i次正面的次数
long i, heads[NCOINS+1];
int j, position;
// 初始化数组heads
for(j = 0; j < NCOINS+1; ++j)
heads[j] = 0;
// 重复50,000次模拟事件
for(i = 0; i < NTOSSES; ++i)
heads[TossCoins(NCOINS)] ++;
// 输出频率图
for(i = 0; i <= NCOINS; ++i)
{
position = int (float(heads[i]) / NTOSSES*72);
cout << setw(6) << i << " ";
for(j = 0; j<position-1; ++j)
cout << " ";
cout << "*" << endl;
}
return 0;
} |
输出频率图:
![suijihua](http://wutianqi-blog.b0.upaiyun.com/2010/12/suijihua.gif)
具体可以看看王晓东的《计算机算法设计与分析》第七章。
2.《随机化算法(2) — 数值概率算法》
在这章开篇推荐下chinazhangjie总结的随机算法,因为咱两看的是同一本书,所以大家也可以去参考下他的,总结的很不错。
http://www.cnblogs.com/chinazhangjie/archive/2010/11/11/1874924.html
(顺便再PS一下,小杰也是我论坛的C/C++问题求助板块的版主,C/C++小牛)
这一章我就把书中的一个例子举出来了,感觉虽然很简单,但是很有意思。
用随机投点法计算Pi值
设有一半径为r的圆及其外切四边形。向该正方形随机地投掷n个点。设落入圆内的点数为k。由于所投入的点在正方形上均匀分布,因而所投入的点落入圆内的概率为(Pi*r*r)/(4*r*r)= Pi/4 。所以当n足够大时,k与n之比就逼近这一概率。从而,PI 约等于 (4*k)/n.
如下图:
因为代码里用到了上一章《概率算法(1) — 随机数》里的RandomNumber类,所以大家可以先把前一章看看。
我把这个伪随机类再贴一遍:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| const unsigned long maxshort = 65535L;
const unsigned long multiplier = 1194211693L;
const unsigned long adder = 12345L;
class RandomNumber{
private:
// 当前种子
unsigned long randSeed;
public:
// 构造函数,默认值0表示由系统自动产生种子
RandomNumber(unsigned long s = 0);
// 产生0 ~ n-1之间的随机整数
unsigned short Random(unsigned long n);
// 产生[0, 1) 之间的随机实数
double fRandom();
};
// 产生种子
RandomNumber::RandomNumber(unsigned long s)
{
if(s == 0)
randSeed = time(0); //用系统时间产生种子
else
randSeed = s;
}
// 产生0 ~ n-1 之间的随机整数
unsigned short RandomNumber::Random(unsigned long n)
{
randSeed = multiplier * randSeed + adder;
return (unsigned short)((randSeed >> 16) % n);
}
// 产生[0, 1)之间的随机实数
double RandomNumber::fRandom()
{
return Random(maxshort) / double(maxshort);
} |
主文件Main:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| /*
* Author: Tanky woo
* Blog: www.WuTianQi.com
* Date: 2010.12.8
* 用随机投点法计算Pi值
* 代码来至王晓东《计算机算法设计与分析》
*/
#include "RandomNumber.h"
#include <iostream>
#include <iomanip>
#include <time.h>
using namespace std;
double Darts(long n)
{
// 用随机投点法计算Pi值
static RandomNumber dart;
long k = 0;
for(long i=1; i<=n; ++i)
{
double x = dart.fRandom();
double y = dart.fRandom();
// 在圆内
if((x*x+y*y) <= 1)
++k;
}
return 4 * k / double(n);
}
int main()
{
// 当进行1,000次投点时
cout << Darts(1000) << endl;
// 当进行10,000次投点时
cout << Darts(10000) << endl;
// 当进行100,000次投点时
cout << Darts(100000) << endl;
// 当进行1,000,000次投点时
cout << Darts(1000000) << endl;
// 当进行10,000,000次投点时
cout << Darts(10000000) << endl;
// 当进行100,000,000次投点时
cout << Darts(100000000) << endl;
return 0;
} |
通过代码可以看出,随机投点越多,则越接近与3.1415926…
这个和抛硬币时求硬币正反面概率类似,实验次数越多,则越接近于理论值。
下一章是《随机化算法(3) — 舍伍德(Sherwood)算法》
3.《随机化算法(3) — 舍伍德(Sherwood)算法》
正文:
这一章怎么说呢,我个人感觉不好理解,在网上查了一些资料,没发现有具体对舍伍德算法的介绍。
迄今为止看的最全面的就是王晓东的《计算机算法设计与分析》里讲的了。在网上查的一些资料也基本全和这本书上讲的一样,至于是这本书先写的,还是其他位置先写的,我就不做评论了。
(有时间我把那本书上讲舍伍德的一段给拍下来放到文章里)
书上对舍伍德讲的比较详细,但是不太好理解,一定要多看几遍。
我这里只说下他的基本思想:在一般输入数据的程序里,输入多多少少会影响到算法的计算复杂度。这时可用舍伍德算法消除算法所需计算时间与输入实例间的这种联系。
联系例子,在快速排序中,我们是以第一个元素为基准开始排序时,为了避免这样的情况,可以用舍伍德算法解决,也就是使第一个基准元素是随机的。
事先说明下:以后章节,若在代码中头文件里包含了RandomNumber.h头文件,请查看前面写的《概率算法(1) — 随机数》里的伪随机数类RandomNumber.我以后不再提醒。
这是一个非递归版的舍伍德快速排序算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
| /*
* Author: Tanky woo
* Blog: www.WuTianQi.com
* Date: 2010.12.8
* 舍伍德(Sherwood)算法运用(1) -- 线性时间选择算法
* 代码来至王晓东《计算机算法设计与分析》
*/
#include "RandomNumber.h"
#include <iostream>
#include <iomanip>
#include <time.h>
using namespace std;
const int INF = 9999;
// 交换a, b的值
template <typename Type>
void Swap(Type &a, Type &b)
{
Type temp;
temp = a;
a = b;
b = temp;
}
template <typename Type>
Type select(Type a[], int lt, int rt, int k)
{
// 计算a[lt:rt]中第k小元素
static RandomNumber rnd;
while(true)
{
if(lt > rt)
return a[lt];
int i = lt, j = lt+rnd.Random(rt-lt+1); // 随机选择的划分基准
Swap(a[i], a[j]);
j = rt+1;
Type pivot = a[lt];
//以划分基准为轴作元素交换
while(true)
{
while(a[++i] < pivot);
while(a[--j] > pivot);
if(i >= j)
break;
Swap(a[i], a[j]);
}
if(j - lt + 1 == k)
return pivot;
a[lt] = a[j];
a[j] = pivot;
// 对子数组重复划分过程
if(j - lt + 1 < k)
{
k = k - j + lt - 1;
lt = j + 1;
}
else
rt = j - 1;
}
}
template <typename Type>
Type Select(Type a[], int n, int k)
{
// 计算a[0: n-1]中第k小元素
// 假设a[n]是一个键值无穷大的元素
if(k < 1 || k > n)
cerr << "Wrong!" << endl;
return select(a, 0, n-1, k);
}
int main()
{
int arr[7] = {3, 2, 5, 7, 10, INF};
cout << Select(arr, 6, 4) << endl;
} |
当然,舍伍德算法也不是万能的。有时也会遇到这样的情况,即所给的确定性算法无法直接改造成舍伍德型算法。此时可借助于随机预处理技术,不改变原有的确定性算法,仅对其输入进行随机洗牌,同样可收到舍伍德算法的效果。
以下是随机洗牌算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| /*
* Author: Tanky woo
* Blog: www.WuTianQi.com
* Date: 2010.12.8
* 和舍伍德算法效果类似的一个程序 -- 随机洗牌
* 代码来至王晓东《计算机算法设计与分析》
*/
#include "RandomNumber.h"
#include <iostream>
#include <iomanip>
#include <time.h>
using namespace std;
// 交换a, b的值
template <typename Type>
void Swap(Type &a, Type &b)
{
Type temp;
temp = a;
a = b;
b = temp;
}
template <typename Type>
void Shuffle (Type a[], int len)
{
// 随机洗牌算法
static RandomNumber rnd;
for (int i = 0; i < len; ++i)
{
int j = rnd.Random(len-i) + i;
Swap (a[i], a[j]) ;
}
}
template <typename Type>
void Print (Type a[], int len)
{
for(int i=0; i<len; ++i)
cout << a[i] << " ";
cout << endl;
}
int main()
{
int arr[10];
// 原先次序
for(int i=0; i<10; ++i)
arr[i] = i+1;
Print(arr, 10);
// 第一次洗牌
Shuffle(arr, 10);
Print(arr, 10);
// 第二次洗牌
Shuffle(arr, 10);
Print(arr, 10);
return 0;
} |
原次序与第一洗牌和第二次洗牌后都不一样。
说实话,这一章我也很迷糊,如果有问题,欢迎大家指出。
下一章将是:《随机化算法(4) — 拉斯维加斯(Las Vegas)算法》
4.《随机化算法(4) — 拉斯维加斯(Las Vegas)算法》
正文:
悟性不够,这一章看代码看了快一个上午,才理解。
上一章讲过《概率算法(3) — 舍伍德(Sherwood)算法》,但是他的有点是计算时间复杂性对所有实例而言相对均匀,而其平均时间复杂性没有改变。而拉斯维加斯算法怎么显著改进了算法的有效性。
拉斯维加斯算法的一个显著特征是它所作的随机性决策有可能导致算法找不到所需的解。因此通常用一个bool型函数表示拉斯维加斯算法。
1
2
3
4
5
6
7
8
| void Obstinate(InputType x, OutputType &y)
{
// 反复调用拉斯维加斯算法LV(x, y),直到找到问题的一个解
bool success= false;
while (!success)
success = LV(x,y);
} |
考虑用拉斯维加斯算法解决N皇后问题:
对于n后问题的任何一个解而言,每一个皇后在棋盘上的位置无任何规律,不具有系统性,而更象是随机放置的。由此容易想到下面的拉斯维加斯算法。
在棋盘上相继的各行中随机地放置皇后,并注意使新放置的皇后与已放置的皇后互不攻击,直至n个皇后已相容地放置好,或已没有下一个皇后的可放置位置时为止。注意这里解决的是找到其中一个方法,求不是求出N皇后的全部解。
这里提前说明一下,否则不好理解。
接下来的这个用Las Vegas算法解决N皇后问题,我们采用的是随机放置位置策略和回溯法相结合,具体就是比如八皇后中,前几行选择用随机法放置皇后,剩下的选择用回溯法解决。
这个程序不是很好理解,有的地方我特别说明了是理解程序的关键,大家看时一定要认真了,另外,王晓东的书上先是用单纯的随机法解决,大家可以先去理解书上这个例子。然后再来分析我这个程序。不过那本书上关于这一块错误比较多,大家看时要注意哪些地方他写错了。
拉斯维加斯算法解决N皇后代码:
依然用到了RandomNumber.h头文件,大家可以去看下第一章,我就不贴出来了。
剩下部分的代码:
注意QueensLV()函数是这个程序的精髓所在。
Queen.h头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| #ifndef QUEEN_H
#define QUEEN_H
class Queen
{
friend bool nQueen(int);
private:
bool Place(int k); // 测试皇后k置于第x[k]列的合法性
bool Backtrack(int t); // 解n后问题的回溯法
bool QueensLV(int stopVegas); // 随机放置n个皇后拉斯维加斯算法
int n, *x, *y;
};
#endif |
Queen.cpp文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| #include <iostream>
#include "Queen.h"
#include "RandomNumber.h"
using namespace std;
bool Queen::Place(int k)
{
// 测试皇后k置于第x[k]列的合法性
for(int j = 1; j < k; ++ j)
if((abs(k-j) == abs(x[j]-x[k])) || (x[j]==x[k]))
return false;
return true;
}
bool Queen::Backtrack(int t)
{
// 解n后问题的回溯法
if(t > n)
{
for(int i=1; i<=n; ++i)
y[i] = x[i];
return true;
}
else
for(int i=1; i<=n; ++i)
{
x[t] = i;
if(Place(t) && Backtrack(t+1))
return true;
}
return false;
}
/*
* QueensLV是整个Las Vegas解n皇后的精髓。而且这个函数不好理解,我在这里具体分析下。
* k是第k行,x[k]表示第k行的皇后放在x[k]列
* 下面这两点解析请认真看了,我个人就是卡在这里半天了,但是理解后很简单。
* 标号①处:这里是遍历第k行所有可以放置的列号,用y保存下来,并用count记录有多少个位置可以放置
* 标号②处:这里利用上面保存的可以放置的列,然后随机取其中一列来放置第k行的皇后。这就是Las Vegas的精髓!
*/
// Author: Tanky Woo
// Blog: www.WuTianQi.com
bool Queen::QueensLV(int stopVegas)
{
// 随机放置n个皇后的拉斯维加斯算法
RandomNumber rnd; // 随机数产生器
int k = 1; // 下一个放置的皇后编号
int count = 1;
// 1 <= stopVegas <= n 表示允许随机放置的皇后数
while((k <= stopVegas) && (count > 0))
{
count = 0;
for(int i = 1; i<=n; ++i) // ----------- ①
{
x[k] = i;
if(Place(k))
y[count++] = i;
}
if(count > 0) // -------------②
x[k++] = y[rnd.Random(count)]; // 随机位置
}
return (count > 0); // count > 0表示放置位置成功
} |
Main.cpp主文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| /*
* Author: Tanky woo
* Blog: www.WuTianQi.com
* Date: 2010.12.9
* 拉斯维加斯(Las Vegas)算法解决N皇后问题
* 代码来至王晓东《计算机算法设计与分析》
*/
#include "Queen.h"
#include "RandomNumber.h"
#include <iostream>
using namespace std;
bool nQueen(int n)
{
// 与回溯法结合的解n后问题的拉斯维加斯算法
Queen X;
// 初始化X
X.n = n;
int *p = new int[n+1];
int *q = new int[n+1];
for(int i=0; i<=n; ++i)
{
p[i] = 0;
q[i] = 0;
}
X.y = q;
X.x = p;
// 设置随机放置皇后的个数
int stop = 8;
if(n > 15)
stop = n-15;
bool found = false;
while(! X.QueensLV(stop));
// 算法的回溯搜索部分
if(X.Backtrack(stop+1))
{
for(int i=1; i<=n; ++i)
cout << p[i] << " ";
found = true;
}
cout << endl;
delete [] p;
delete [] q;
return found;
}
int main()
{
nQueen(8);
} |
在8皇后问题中:
1.stopVegas=0表示完全使用回溯法解决问题。因此一定可以得到一组解。
2.stopVegas=8表示完全实用随机法解决问题。因此一定可以得到一组解。
注意书上说解决8皇后问题时,列出了一个不同stopVegas值时,所对应的算法成功率,在stopVegas=8时,他写着是0.1293,我个人认为是错的。接下来我说下自己这么理解的原因:
首先,这个程序为何会造成有失败的情况,那就是因为在随机求出前stopVegas行成立时,不代表后面N-stopVegas行用回溯就可以成立,因为这不是一个彻底的回溯。它是从stopVegas+1行开始计算,回溯不成立最后返回时,也只停留在stopVegas。
而我们在完全随机时,那么它就是反复调用随机位置放置n个皇后的Las Vegas算法,直至放置成功。所以当stopVegas=8时,他的成功率也应该是100%。
另外在stopVegas=3时,成功率等于0.49,趋近于0.5,大家可以试试,基本上每运行两次会有一次回来结果。
如果上面我的理解有错,欢迎大家指出,我的博客(www.WuTianQi.com)。
《随机化算法(5) — 蒙特卡罗(Monte Carlo)算法》。
正文:
蒙特卡罗法(Monte Carlo method)是以概率和统计的理论、方法为基础的一种计算方法,将所求解的问题同一定的概率模型相联系,用电子计算机实现统计模拟或抽样,以获得问题的近似解,故又称统计模拟法或统计试验法。
蒙特卡罗算法在一般情况下可以保证对问题的所有实例都以高概率给出正确解,但是通常无法判定一个具体解是否正确。
设p是一个实数,且1/2 <p <1。如果一个蒙特卡罗算法对于问题的任一实例得到正确解的概率不小于p,则称该蒙特卡罗算法是p正确的,且称p – 1/2是该算法的优势。如果对于同一实例,蒙特卡罗算法不会给出2个不同的正确解答,则称该蒙特卡罗算法是一致的。
有些蒙特卡罗算法除了具有描述问题实例的输入参数外,还具有描述错误解可接受概率的参数。这类算法的计算时间复杂性通常由问题的实例规模以及错误解可接受概率的函数来描述。对于一个一致的p正确蒙特卡罗算法,要提高获得正确解的概率,只要执行该算法若干次,并选择出现频次最高的解即可。
对于一个解所给问题的蒙特卡罗算法MC(x),如果存在问题实例的子集X使得:
(1)当x不属于X时,MC(x)返回的解是正确的;
(2)当x属于X时,正确解是y0,但MC(x)返回的解未必是y0。
称上述算法MC(x)是偏y0的算法。
这一章蒙特卡罗算法我感觉没必要多讲,因为王晓东那本书上讲得比较详细,且概念较多,而且网上关于这个算法的资源很多。如果我再多说就有点画蛇添足的味道了。我在数学中国社区就看到有专门这个板块叫蒙特卡罗算法,大家可以去看看。
在这里,我只给出书上讲的主元素问题:设T[1:n]是一个含有n个元素的数组。当|{i|T[i]=x}|>n/2时,称元素x是数组T的主元素。以下是代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| /*
* Author: Tanky woo
* Blog: www.WuTianQi.com
* Date: 2010.12.9
* 蒙特卡罗(Monte Carlo)算法解决主元素问题
* 代码来至王晓东《计算机算法设计与分析》
*/
#include "RandomNumber.h"
#include <iostream>
#include <cmath>
using namespace std;
RandomNumber rnd;
template <typename Type>
bool Majority(Type *T, int n)
{
// 判定主元素的蒙特卡罗算法
int i = rnd.Random(n) + 1;
Type x = T[i]; // 随机选择数组元素
int k = 0;
for(int j = 0; j < n; ++j)
if(T[j] == x)
++k;
return (k > n/2);
}
template <typename Type>
bool MajorityMC(Type *T, int n, double e)
{
// 重复调用算法Majority
int k = ceil(log(1.0/e) / log(2.0));
for(int i = 1; i <= k; ++i)
if(Majority(T, n))
return true;
return false;
}
int main()
{
int arr[10] = {3, 5, 7, 3, 9, 3, 3, 1, 3, 3};
cout << MajorityMC(arr, 10, 0.1) << endl;
return 0;
} |
有机会我会在网上找一些好的讲蒙特卡罗算法的资料,然后补充到这篇文章里供大家下载。