随机算法
概述随机算法是一种在接受输入的同时,为了随机选择的目的,还接受一串随机比特流并且在运行过程中使用该比特流的算法(允许算法在执行过程中随机地选择下一个计算步骤)。随机算法通常有两个优点:较之那些我们所知的解决同一问题最好的确定性算法,随机算法所需的运行时间或空间通常小一些。随机算法比较易于理解和实现(呵,站着说话不腰疼)。随机算法的基本特征:对所求解问题的同一实例用同一随机算法...
概述
随机算法是一种在接受输入的同时,为了随机选择的目的,还接受一串随机比特流并且在运行过程中使用该比特流的算法(允许算法在执行过程中随机地选择下一个计算步骤)。
随机算法通常有两个优点:
- 较之那些我们所知的解决同一问题最好的确定性算法,随机算法所需的运行时间或空间通常小一些。
- 随机算法比较易于理解和实现(呵,站着说话不腰疼)。
随机算法的基本特征:对所求解问题的同一实例用同一随机算法求解两次可能得到完全不同的效果。
这两次求解所需的时间、所得到的结果可能会有相当大的差别。
随机算法的类别
随机(概率)算法大致分四类:
数值概率算法
常用于数值问题的求解。
这类算法所得到的往往是近似解且近似解的精度随计算时间的增加而不断提高。
在许多情况下,要计算出问题的精确解是不可能的,或者没有必要,此时,用数值概率算法可以得到相当满意的解。蒙特卡洛(Monte Carlo)算法
用于求问题的准确解。
对于许多问题来说,近似解毫无意义。
例如,一个判定问题其解为“是”或“否” ,二者必居其一,
不存在任何近似解。
又如,我们要求一个整数的因子,这样问题的解答必须是准
确的,一个整数的近似因子没有任何意义。
用蒙特卡洛算法能求得问题的一个解,但这个解未必是正确的。
求得正确解的概率依赖于算法所用的时间,算法所用的时间越多,得到正确解的概率就越高。
蒙特卡洛算法的主要缺点也在于此,即无法有效地判定所得到的解是否肯定正确。拉斯维加斯(Las Vegas)算法
拉斯维加斯算法不会得到不正确的解。
一旦用拉斯维加斯算法找到一个解,这个解就一定是正确
解。
但有时用拉斯维加斯算法会找不到解。
与蒙特卡洛算法类似,拉斯维加斯算法找到正确解的**概率随着它所用的计算时间
的增加而提高**。
对于所求解问题的任一实例,用拉斯维加斯算法反复对该实例求解足够多次,可使求解失效的概率任意小。
- 舍伍德(Sherwood)算法
舍伍德算法总能求得问题的一个解,且所求得的解总是正确的。
当一个确定性算法在最坏情况下的计算复杂性与其在平均情况下的计算复杂性有较大差别时,可在这个确定性算法中引入随机性,将它改造成一个舍伍德算法,消除或减少问题的好坏实例间的这种差别。
例子
- Monte Carlo算法
总是给出解,但是偶尔可能会产生非正确的解。然而,可以通过多次运行原算法,并且满足每次运行时的随机选择都相互独立,使产生非正确解的概率可以减到任意小。
主主元素问题(多数元素问题)
问题描述
设T[1…n]是一个长度为n的数组,当某个元素在该数组中存在的数量多于int(s/2)时称该元素为数组T的主元素(多数元素)。
求解思路
算法随机选择数组元素x,由于数组T的非主元素个数小于n/2,所以,x不为主元素的概率小于1/2。因此判定数组T的主元素存在性的算法是一个偏真1/2正确的算法。50%的错误概率是不可容忍的,利用重复调用技术将错误概率降低到任何可接受的范围内。对于任何给定的p> 0,算法majorityMC重复调用(向上取整)log(1/p)次算法majority。它是一个偏真蒙特卡罗算法,且其错误概率小于p。算法majorityMC所需的计算时间显然是O(nlog(1/p))。
代码实现
//重复k次调用算法Majority
template<class Type>
bool MajorityMC(Type *T,int n,double e)
{
int k = ceil(log(1/e)/log((float)2));
for(int i=1; i<=k; i )
{
if(Majority(T,n))
{
return true;
}
}
return false;
}
bool Majority(Type *T,int n)
{
RandomNumber rnd;
int i = rnd.Random(n);
Type x = T[i]; //随机选择数组元素
int k = 0;
for(int j=0; j<n; j )
{
if(T[j] == x)
{
k ;
}
}
return (k>n/2); //k>n/2时,T含有主元素
}
随机化快速排序
(可能是最为流行的一种随机算法?)
首先要明确传统快速排序的流程:从待排序序列中拿出最后一个(或者第一个)作为主元,将小于它的放在它的前面,大于它的放在它的后面,这时对该主元的前一部分和后一部分再分别递归进行“划分”,最后达到有序。这种方法有一个很大的问题就是当待排序数组是一个几乎有序的序列时其复杂度会很容易达到senta(n^2),因为如果每次都选择第一个元素或者最后一个元素作为主元进行划分,对一个几乎有序的序列,划分后的递归对象(子序列)将会一个很长一个很短,这样可能经过好多次划分后还是有一个待划分的子部分很长。解决方法是每次不选择第一个或者最后一个作为主元,而是随机产生一个从第一个到最后一个之间的随机数作为主元进行划分,这样即保留了快速排序的优越性又避免了排序几乎有序序列时的痛点。
核心代码
void RandomQuickSort(int low,int high){
if(low<high){
int v=random(low,high);
int t=A[low];
A[low]=A[v];
A[v]=t;
Split(A[low...high],low);
RandomQuickSort(low,v-1);
RandomQuickSort(v 1,high);
}
}
该算法在最坏情况下仍然是senta(n^2),但这与输入形式无关。如果最坏情况发生,那是因为用随机数选取的主元不凑巧,这个事件发生的概率是非常小的。事实上,没有一种输入的排列可以引起它的最坏情况,算法的期望运行时间是senta(nlogn).
随机化的选择算法
什么是选择算法:
SELECT 算法描述
- 如果数组元素个数小于 44,则直接将数组排序并返回第 k小元素(采用直接的方法来解决问题,因为当总元素个数小于44*5=220的时候用直接的方法解决问题更快)。
- 把 n 个元素以每组 5 个元素划分为 int( n/5) 组,如果 n 不是 5的倍数则抛弃剩余元素。
- 对每组进行排序,之后取出每组的中间项(第 3 个元素)。
- 递归调用 SELECT 算法,得到这些中间项序列中的中项元素 mm
根据 mm,将原数组 A 划分为三个子数组:
- A1={小于 mm 的元素};
- A2={等于 mm 的元素};
- A3={大于 mm 的元素};
根据 k 的大小,判断第 k 小元素会出现在 A1,A2,A3 中的
哪一个数组里,之后,或者返回第 k 小元素(mm,在 A2
中),或者在 A1 或 A3 上递归。1:k <len(A 1) 第 k 小元素在 A1 中; 2:len(A 1) =k <= A 1 A 2 第 k 小元素在 A2 中; 3: A 1 A 2 < k <= A 1 A 2 A 3第 k 小元素在 A3 中;
该算法的运行时间是senta(n)(T(n)<=20cn,c是排序43个元素所需的时间),具有一个很大的常数系数,所以就有了随机版的选择算法:
/* * 输入:n个元素的数组A[1...n]和整数k * 输出:A中的第k小元素 * */ R_Select(A,1,n,k); void R_Select(int *A, int low, int high, int k) { int v = random(low, high); int x = A[v]; A1 = {a | a < x}; A2 = {a | a = x}; A3 = {a | a > x}; if (A1.len >= k)return R_Select(A, 1, A1.len, k); else if (A.len A2.len >= k)return x; else if (A1.len A2.len < k)return R_Select(A, 1, A3.len, k - A1.len - A2.len); }
该版本的随机算法与原版本的主要不用是原版本是将序列分为若干个5元素序列分别进行排序后找到那些5元素序列中值组成的新序列的中值作为主元对元素进行划分,而随机算法是产生一个序列中的随机数(就是在待找序列中随机找了一个数)作为主元,省去了那些排序5元素数组的步骤,对于大小为n的输入,算法RandomizedSekect执行的期望比较次数小于4n,他的期望运行时间是senta(n)。
可以发现该随机算法最坏情况下的运行时间也是senta(n),但是其发生最坏情况不依赖于输入,仅当产生的随机数序列很不凑巧时才会发生,而这种概率是非常小的。
更多推荐
所有评论(0)