一、概率算法的分类
- 数值概率算法
常用于数值问题的求解。
这类算法所得到的往往是问题的近似解。
近似解的精度随着时间的增加而不断增加。
在很多情况下,求解精确解不可能或不必要,此法可得相当满意的解。 - 舍伍德(Sherwood)算法
虽然在某些步骤引入随机选择,但该算法总能求得问题的一个解,且所求得的解总是正确的。
当一个确定性算法在最坏情况下的计算复杂性与其平均情况下的计算复杂性有较大差别时,可在确定性算法中引入随机性将它改造成一个舍伍德算法,消除或减少问题的好坏实例间的差别。
精髓:消除最坏情形与特定实例之间的关联性。 - 拉斯维加斯(Las Vegas)算法
该算法不会得到不正确的解。
一旦用拉斯维加斯找到一个解,这个解一定是正确解。
但有时该算法找不到解。
找到解的概率随它所用时间的增加而提高。
对所求解的任一实例,用同一拉斯维加斯算法求解足够多次,可使求解失败概率任意小。 - 蒙特卡罗(Monte Carlo)算法
蒙特卡罗算法用于求问题的准确解。
有些问题近似解没有意义,如“y/n”的判定问题、 求一个整数的因子等。
用蒙特卡罗算法总能求得一个解,但这个解未必是正确的。
求得正确解的概率随它所用的计算时间增加而提高。
一般情况下,无法有效判定所得解是否肯定正确。
二、数值概率算法
求圆周率、求定积分、解非线性方程组
例如:
function darts(n)
k←0
for i= 1 to n
x ← uniform (0, 1)//按均匀分布产生0和1之间的随机数
y ← uniform (0, 1)
if 𝐱 𝟐 + 𝐲 𝟐 ≤ 𝟏 then k ← k+1 endif
endfor
return 𝟒𝐤/n
三、舍伍德型概率算法
很多算法对于不同的输入实例,运行时间差别很大。此时,可采用舍伍德型概率算法来消除算法的时间复杂度与输入实例间的依赖关系。
舍伍德型概率算法的有两种应用方式:
- 在确定性算法的某些步骤引入随机因素,将确定性算法改造成舍伍德型概率算法;
- 借助于随机预处理技术,不改变原有的确定性算法,仅对输入实例进行随机处理(称为洗牌),然后再执行确 定性算法。
舍伍德型概率算法设法消除了算法的不同输入实例对算法时间性能的影响,对于任何输入实例,舍伍德型概率算法能够以较高的概率与原有的确定性算法在平均情况下的时间复杂度相同。
一个线性时间的洗牌算法,实现对输入实例进行随机处理。
void RandomShuffle(int r[ ], int n)
{
int i, j, k = n/2, temp;
for (i = 0; i < k; i++)
{
j = Random(0, n-1); //随机选择一个元素
temp = r[i]; r[i] = r[j]; r[j] = temp; //交换r[i]和r[j]
}
}
快速排序的舍伍德型概率算法:
在快速排序算法执行一次划分之前引入随机选择
- 在一次划分之前,在待排序序列中随机确定一个元素作为划分元素,并与第一个元素交换,则一次划分后得到期望均衡的两个子序列。
- 在执行快速排序之前调用洗牌函数 RandomShuffle,将待排序序列随机排列。
void RandQuickSort(int r[ ], int low, int high)
{
int i, k, temp;
if (low < high)
{
i = Random(low, high); //随机选择r[i]作为轴值
temp = r[low]; r[low] = r[i]; r[i] = temp;
k = Partition(r, low, high);
RandQuickSort(r, low, k-1);
RandQuickSort(r, k+1, high);
}
}
二叉查找树的舍伍德型概率算法:
舍伍德型概率算法采用洗牌方法
- 在插入每一个结点时,在查找集合中随机选定一个元素;
- 在执行构造算法之前调用洗牌函数 RandomShuffle,将查找集合随机排列。
struct BiNode{
int data;
BiNode *lchild, *rchild;
};
BiNode *InsertBST(BiNode *root, BiNode *s) {
if (root == NULL) root = s;
else if (s->data < root->data)
root->lchild = InsertBST(root->lchild, s);
else
root->rchild = InsertBST(root->rchild, s);
return root;
}
BiNode *Creat(BiNode *root, int r[ ], int n){
int i, j, temp; BiNode *s = NULL;
for (i = 0; i < n/2; i++) //执行洗牌操作
{
j = rand( ) % n;
temp = r[i]; r[i] = r[j]; r[j] = temp;
}
for (i = 0; i < n; i++)
{
s = new BiNode; s->data = r[i];
s->lchild = s->rchild = NULL;
root = InsertBST(root, s);
}
return root;
}
四、拉斯维加斯型概率算法
拉斯维加斯型(Las Vegas)概率算法对同一个输入实例反复多次运行算法,直至运行成功,获得问题的解。
如果运行失败,在相同的输入实例上再次运行算法。
拉斯维加斯型概率算法的基本特征:
- 拉斯维加斯型概率算法的随机性选择有可能导致算法找不到问题的解,即算法运行一次,或者得到一个正确的解,或者无解。
- 只要出现失败的概率不占多数,当算法运行失败时,在相同的输入实例上再次运行概率算法,就又有成功的可能。
设 p(x) 是对输入实例 x 调用拉斯维加斯型概率算法获得问题的一个解的概率,则一个正确的拉斯维加斯型概率算法应该对于所有的输入实例 x 均有p(x) > 0。
在更强的意义下,存在一个正的常数 δ,使得对于所有的输入实例 x 均有p(x)>δ。
由于 p(x)>δ,所以,只要对算法运行的次数足够多,对任何输入实例 x,拉斯维加斯型概率算法总能找到问题的一个解。
拉斯维加斯型概率算法找到正确解的概率随着运行次数的增加而提高。
八皇后的拉斯维加斯型概率算法:
在棋盘的各行中随机地放置皇后,并使新放置的皇后与已放置的皇后互不攻击,直至八个皇后均已相容地放置好,或下一个皇后没有可放置的位置。
int Queue(int x[ ], int n) {
int i, j, k, count = 0;
for (i = 0; i < n; ){
j = Random(0, n-1); // 注意数组下标从0开始
x[i] = j; count++;
for (k = 0; k < i; k++) // 检测约束条件
if (x[i] == x[k] || abs(i - k) == abs(x[i] - x[k])) break;
if (k == i) { count = 0; i++; } // 不发生冲突,摆放下一个
else if (count == n) return 0; // 无法摆放皇后i
}
return 1;
}
如果将上述随机放置策略与回溯法相结合,则会获得更好的效果。
- 先在棋盘的若干行随机地放置相容的皇后,其他皇后用回溯法继续放置,直至找到一个解或宣告失败。
- 在棋盘中随机放置的皇后越多,回溯法搜索所需的时间就越少,但失败的概率也就越大。
整数因子划分问题的拉斯维加斯型概率算法:
如果 n 是一个合数,则 n 必有一个非平凡因子 m(即m≠1且m≠n),使得 m 可以整除 n。
给定一个合数 n,求 n 的一个非平凡因子的问题称为整数因子划分问题。
在开始时选取 0~n-1 范围内的随机数,然后递归地由下面的公式产生无穷序列 x1, x2 … xk…。
对于 i=2^k,以及 2^k< j <= 2^(k+1),算法计算出 xj -xi 与 n 的最大公因子 d=gcd(xj -xi , n)。
如果 d 是 n 的非平凡因子,则实现对 n 的一次分割,算法输出 n 的因子d。
void Pollard(int n)
{ // 求整数n因子分割的拉斯维加斯算法
RandomNumber rnd;
int i=1;
int x=rnd.Random(n); // 产生随机整数, 刚开始随机指定的
int y=x; int k=2;
while (true) {
i++;
x=(x*x-1)%n; // 无穷序列 x1 ... xk
int d=gcd(y-x, n); // 求 n 的非平凡因子
if ((d>1) && (d<n)) cout<<d<<endl;
if (i==k) {y=x; k*=2;}
}
}
五、蒙特卡罗型概率算法
- 蒙特卡罗型概率算法用于求问题的准确解。
- 蒙特卡罗型概率算法偶尔会出错,但无论任何输入实例,总能以很高的概率找到一个正确解。
- 蒙特卡罗型概率算法总是给出解,但是,这个解偶尔可能是不正确的,一般情况下,也无法有效地判定得到的解是否正确。
- 蒙特卡罗型概率算法求得正确解的概率依赖于算法的运行次数,算法运行的次数越多,得到正确解的概率就越高。
设 p 是一个实数,且 1/2<p<1。
- 如果一个蒙特卡罗型概率算法对于问题的任一输入实例得到正确解的概率不小于 p,则称该蒙特卡罗型概率算法是 p正确的。
- 如果对于同一输入实例,蒙特卡罗型概率算法不会给出两个不同的正确解,则称该蒙特卡罗型概率算法是一致的。
- 如果重复运行一致的 p正确的蒙特卡罗型概率算法,每一次运行都独立地进行随机选择,就可以使产生不正确解的概率变得任意小。
主元素问题的蒙特卡罗型概率算法:
int MajorityMC(int A[ ], int n)
{
int i, j, count = 0;
i = Random(0, n-1); // 随机选择一个数组元素
for (j = 0; j < n; j++)
if (A[j] == A[i]) count++;
if (count > n/2)
return A[i]; // A[i]是主元素
else return 0;
}
素数测试的蒙特卡罗概率算法:
可以多次随机选取小于 n 的正整数 a,重复计算 a^(n-1) mod n 来判定 n 是否是素数。
费尔马定理:如果正整数 n 是一个素数,取任一正整数 a 且1<a<n,则 a^(n-1) mod n≡1。
int FermatPrime(int n)
{
int i, a, b = 1;
a = Random(2, n-1); // 产生[2, n-1]之间的一个随机整数
for (i = 1; i < n; i++) // 求 a^(n-1) mod n
b = (b * a) % n;
if (b == 1) return 1; // 可能是素数或Carmichael数
else return 0; // 一定不是素数
}