随机化算法概述
Posted 快乐江湖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了随机化算法概述相关的知识,希望对你有一定的参考价值。
文章目录
一:概述
(1)什么是随机化算法
随机化算法:随机化算法把“对于所有合理的输入都必须给出正确的输出”这一求解问题的条件放宽,将随机性选择注入到算法中,在算法执行某些步骤时,可以随机地选择下一步该如何进行,同时允许结果是近似解或以较小概率出现错误,并以此为代价,获得算法运行时间的大幅度减少。随机化算法执行中注入的随机性依赖于随机数发生器所产生的随机数
(2)随机化算法的特点
随机化特点:
- 允许算法执行过程中随机选择下一计算步骤。
- 当算法执行过程中面临选择时,随机化算法通常比最优选择算法省时。可在很大程度上降低算法复杂性
- 对所求问题的同一实 用同一随机化算法求解两次,两次求解所需的时间甚至所得的结果可能有相当大的差别
- 设计思想简单,易于实现
(3)随机化算法分类
随机化算法分类:
- 数值随机算法
- 舍伍德算法
- 拉斯维加斯算法
- 蒙特卡罗算法
(4)随机数
随机数:随机数在随机化算法设计中扮演着十分重要的角色,但计算机无法产生真正的随机数,在随机化算法中使用的随机数都是伪随机数,其中线性同余法是产生伪随机数的最常用的方法
二:数值随机化算法(以计算 π π π值为例)
数值随机化算法:将一个问题的计算与某已经确定的概率分布的事件联系起来,算法求解结果一般为近似解,注意
- 解精度随计算时间的增加而提高
- 某些情况下计算问题的精确解是不可能或没必要
以计算 π π π值问题为例:设有一半径为 r r r的圆及其外切四边形。向该正方形随机地投掷 n n n个点。设落入圆内的点数为 k k k。若所投入的点在正方形上满足均匀分布,则所投入的点落入圆内的概率可由下式计算:
π r 2 4 r 2 = π 4 ≈ k n \\fracπr^24r^2=\\fracπ4\\approx \\frackn 4r2πr2=4π≈nk
当 n n n足够大时, k k k与 n n n之比近似逼近这一概率,从而有:
π ≈ 4 k n π\\approx\\frac4kn π≈n4k
代码如下
#include <iostream>
#include <cstdlib>
using namespace std;
// 获得0-1之间的随机数
double get_random_num ()
return (double)rand() / RAND_MAX ;
// 用随机投点法计算 PI
double darts (int n)
int k = 0 ;
for (int i = 0; i < n; ++ i)
// 投掷n个点,随机生成点(x, y)
double x = get_random_num() ;
double y = get_random_num() ;
//如果该点落在圆内,则k++
if ((x * x + y * y) <= 1.0)
++ k ;
//计算公式
return (4 * k) / (double)n ;
int main()
cout << darts (200000000) << endl ;
三:舍伍德算法(以快速排序为例)
舍伍德算法:当一个确定性算法在最坏情况下的计算复杂度与其在平均情况下的计算复杂度相差较大时,可以在该确定性算法中引入随机性将它改造为一个舍伍德算法,用来消除或减少问题不同实例之间在计算时间上的差别,舍伍德算法总能求得问题的正确解,具体方法为数据洗牌+确定算法
以快速排序为例:快速排序是基于分治策略的一个典型代表算法,其基本思想是对于输入的子数组a[p:r]
,按以下三个步骤进行排序
- 分解:以
a[p]
为基准元素将a[p:r]
划分成3段a[p:q-1]
、a[q]
和a[q+1:r]
,使a[p:q-1]
中任一元素小于等于a[q]
,而a[q+1:r]
中任一元素大于等于a[q]
。下标q
在划分过程中确定 - 递归求解:通过递归调用快速排序算法,分别对
a[p:q-1]
和a[q+1:r]
进行排序 - 合并:由于对
a[p:q-1]
和a[q+1:r]
的排序是就地进行的,因此在a[p:q-1]
和a[q+1:r]
都已排好序后,不需要执行任何计算,a[p:r]
就已经排好序了
代码如下
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int Partition(vector<int>& data, int begin, int end)
int key = data[begin];//以左侧元素为基准元素
int save = begin;
while(begin < end)
while(begin < end && data[end] >= key)
//end处元素应该大于等于key,一旦找到小于key的就停止
--end;
while(begin < end && data[begin] <= key)
//end处元素应该小于等于key,一旦找到大于key的就停止
++begin;
swap(data[begin], data[end]);//交换
swap(data[save], data[end]);//让基准元素到"中间";
return begin;
void quickSort(vector<int>& data, int left, int right)
if(left < right)
int div = Partition(data, left, right);
quickSort(data, left, div-1);
quickSort(data, div+1, right);
else
return;
void print(vector<int>& data)
for(int i = 0; i < data.size(); i++)
cout << data[i] << " ";
cout << endl;
int main()
vector<int> test = 21, 43, 32, 18, 29, 30, 21, 55, 43, 17, 83;
print(test);
quickSort(test, 0, test.size());
print(test);
return 0;
快速排序算法的核心在于合适的划分基准,这个划分基准将直接导致划分的对称性,继而影响算法的性能。一旦基准挑选的不合理,如下图,那么将导致算法时间复杂度非常高
因此,舍伍德算法通过采用随机选择策略来随机选取出一个元素作为划分基准,这样可以使划分基准是随机的,从而可以期望划分是较为对称的
代码如下
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int Partition(vector<int>& data, int begin, int end)
// 随机挑选
int rand_idx = (rand() % (end - begin +1)) + begin;
swap(data[begin], data[rand_idx]);
int key = data[begin];//以左侧元素为基准元素
int save = begin;
while(begin < end)
while(begin < end && data[end] >= key)
//end处元素应该大于等于key,一旦找到小于key的就停止
--end;
while(begin < end && data[begin] <= key)
//end处元素应该小于等于key,一旦找到大于key的就停止
++begin;
swap(data[begin], data[end]);//交换
swap(data[save], data[end]);//让基准元素到"中间";
return begin;
void quickSort(vector<int>& data, int left, int right)
if(left < right)
int div = Partition(data, left, right);
quickSort(data, left, div-1);
quickSort(data, div+1, right);
else
return;
void print(vector<int>& data)
for(int i = 0; i < data.size(); i++)
cout << data[i] << " ";
cout << endl;
int main()
vector<int> test = 21, 43, 32, 18, 29, 30, 21, 55, 43, 17, 83;
print(test);
quickSort(test, 0, test.size());
print(test);
return 0;
四:拉斯维加斯算法(以N皇后问题为例)
(1)拉斯维加斯算法概述
拉斯维加斯算法:舍伍德型算法的优点是,其计算时间复杂性对所有实例而言相对均匀,但与其相应的确定性算法相比,其平均时间复杂性没有改进。拉斯维加斯算法则不然,它能显著地改进算法的有效性,甚至对某些迄今为止找不到有效算法的问题,也能得到满意的结果。具体来说
-
拉斯维加斯算法求得的解总是正确的,求解中算法所作的随机性决策有可能导致算法找不到所需的解
-
一般情况下,求得正确解的概率随计算时间增加而增大
-
对同一实例,重复执行一个拉斯维加斯算法,可使求解失败概率任意小
-
由于拉斯维加斯型算法有时运行成功,有时运行失败,故通常拉斯维加斯型算法的返回类型为
bool
-
拉斯维加斯算法运行一次,或者得到一个正确的解,或者无解。因此,需要对同一输入实例反复多次运行算法,直到成功地获得问题的解
算法有两个参数
- 算法输入
- 当算法运行成功时保存问题的解
拉斯维加斯算法典型调用形式如下
void obstinate(Object x, Object y)
//反复调用拉斯维加斯算法直到找到问题的一个解y
bool success = false;
while(!success) success = lv(x, y)
(2)N皇后问题
A:问题概述
N皇后问题:在 n × n n×n n×n格的棋盘上放置彼此不相互攻击的 N N N个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。N皇后问题等价于在 n × n n×n n×n格的棋盘上放置 N N N个皇后,任何2个皇后不能放在同一行或同一列或同一斜线上
B:回溯算法解决N皇后问题
C:拉斯维加斯算法解决N皇后问题
在棋盘上相继各行中随机地放置皇后,使新放置皇后与已放置皇后互不攻击,直至 n n n个皇后均已相容地放置好,或没有下一个皇后的可放置位置时为止
- 注意:完全使用拉斯维加斯算法可能复杂度太高,长时间得不到解,所以下面的示例中前 t t t个皇后的位置由拉斯维加斯算法产生,后 N − t N-t N−t个皇后位置由回溯法产生
#include <iostream>
#include "Random.h"
#include <cmath>
#define MAX_V 100
using namespace std;
/* 判断某行放置皇后是否合法
* i : 即将放置的行数
* j : 即将放置的列数
* */
bool place(int map[MAX_V][MAX_V], int n, int i, int j)
for(int a = i - 1, b = 1; a >= 0; a--, b++)
//判断直线方向
if(map[a][j] == 1)
return false;
int l = j - b;
int r = j + b;
//判断两个斜线方向
if(l >= 0 && map[a][l] == 1)
return false;
if(r < n && map[a][r] == 1)
return false;
return true;
/* 回溯剩下的皇后位置
* t : 回溯开始的位置
* */
bool recall(int map[MAX_V][MAX_V], int n, int t)
if(t == n)
return true;
for(int i = 0; i < n; i++)
if(!place(map, n, t, i))
continue;
map[t][i] = 1;
if(recall(map, n, t+1))
return true;
map[t][i] = 0;
return false;
/* n 皇后问题
* map : 棋盘
* n : 棋盘大小
* t : 使用拉斯维加斯算法的层次数
* */
void nQueen(int map[MAX_V][MAX_V], int n, int t)
Random rand;
int i;
while(i != n) //循环调用,直到产生解
for(i = 0; i < t; i++)
int randPos = rand.rand(n); //产生[0, n)的随机数
if(!place(map, n, i, randPos))
break;
for(int j = 0; j < n; j++)
map[i][j] = 0;
map[i][randPos] = 1;
if(i == t && recall(map, n, t))
i = n;
int main()
int map随机取基准 快速排序的一种算法也算一种优化