随机算法 Las Vegas算法 Monte Carlo算法

随机算法

定义

  • 不要求对所有输入均正确计算,只要求出现错误的可能性小到可忽略(得能解决问题)
  • 同一组输入,不要求同一个结果(不确定)

应用

有些使用确定性求解算法效率会很差的问题,如果用随机算法求解,可以很快得到相当可信的结果,典型用于公钥,RSA算法。

分类

主要分为LasVegas算法和MonteCarlo算法

Las Vegas算法

  • 少数应用时会出现求不出解的情况
  • 但一旦找到一个解,则一定是正确的
  • 求不出解的时候需再次调用算法计算,直到获得解
  • 对于此类算法,主要是分析算法的时间复杂度的期望值,以及调用一次产生失败的概率

Monto Carlo算法

  • 通常不能保证计算出来的结果总是正确的, 一般只能断定所给解的正确性不小于p ( 1/2<p<1)

  • 通过算法的反复执行(即以增大算法的执行时间为代价),能够使发生错误的概率 小到可以忽略的程度 (越算越好)

  • 由于每次执行的算法是独立的,故k次执行均发生错误的概率为(1-p)k

  • 对于判定问题(回答只能是“Yes”或 “No”)

    • 带双错的(two-sided error): 回答”Yes”或”No”都 有可能错
    • 带单错的(one-sided error):只有一种回答可能错
  • Las Vegas算法可以看成是单错概率为0的 Monte Carlo算法

优点

  • 对于某一给定的问题,随机算法所需的时 间与空间复杂性,往往比当前已知的确定性算法要好
  • 到目前为止设计出来的各种随机算法,无 论是从理解上还是实现上,都是极为简单的
  • 随机算法避免了去构造最坏情况的例子

具体问题分析及实现

找第k小元素的随机算法 (Las Vegas算法)

  • 在n个数中随机的找一个数A[i]=x, 然后将 其余n-1个数与x比较,分别放入三个数组中S1(元素均<x), S2(元素均=x), S3(元素 均>x)
  • 若|S1|≥k ,则调用Select(k,S1)
  • 若(|S1|+|S2|)≥k,则第k小元素就是x
  • 否则就有(|S1|+|S2|)< k,此时调用 Select(k-|S1|-|S2|,S3)
package LasVegas;

import java.util.*;

public class NumKSmall {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Random r = new Random(1);
		ArrayList<Integer> x = new ArrayList<Integer>();
		for(int i = 0; i < 100; i++) {
			x.add(r.nextInt(100));
		}
		int k = r.nextInt(100);
		System.out.println("在x中第 %d 小的数字是: ",k);
		System.out.println(NKS_LV(x, k));
		
	}

	private static int NKS_LV(ArrayList<Integer> x, int k) {
		// TODO 自动生成的方法存根
		int len = x.size();
		Random r = new Random(1);
		int s = x.get(r.nextInt(len));
		ArrayList<Integer> S1 = new ArrayList<Integer>();
		ArrayList<Integer> S2 = new ArrayList<Integer>();
		ArrayList<Integer> S3 = new ArrayList<Integer>();

		for(int i = 0; i < len; i++) {
			int xx = x.get(i);
			if (xx < s) {
				S1.add(xx);
			}else if (xx == s){
				S2.add(xx);
			}else {
				S3.add(xx);
			}
		}
		if (k <= S1.size()) {
			return NKS_LV(S1,k);
		}else if ( k <= S1.size() + S2.size()) {
			return s;
		}else {
			return NKS_LV(S3, k - S1.size() - S2.size());
		}
			
	}

}

其实经典的BFPTR算法就是改进了随机的部分,而是采用将array分成5个部分,找出5个部分中位数组成的数组的中位数作为最初的s,可以保证**O(N)**的时间复杂度。
用到的思想就是LasVegas或者说Sherwood随机化方法,消除或减少问题的好坏输入实例之间的差别

测试字符串是否相同(Monte Carlo算法)

设A处有一个长字符串x(e.g. 长度为106), B处也有一个长字符串y,A将x发给B,由 B判断是否有x=y。

  • 首先由A发一个x的长度给B,若长 度不等,则x≠y
  • 若长度相等,则采用“取指纹”的方法:
    • A对x进行处理,取出x的“指纹”,然后将x的“指纹” 发给B
    • 由B检查x的“指纹”是否等于y 的“指纹”
    • 若取k次“指纹”(每次取法不同),每次两者结果均相同,则认为x与y是相等的
    • 随着k的增大,误判率可趋于0
      思路是这样,java中hashcode的思路就是这样,取模就是hash,equals判断的时候就是利用哈希,哈希中取的是31。如果多次取不同的k,判断多次两者指纹都一样,MC算法就认定x=y。
	//String里的hashCode()
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

错判率分析
B接到指纹Ip(x)后与Ip(y)比较 如果Ip(x)≠Ip(y),当然有x≠y
如果Ip(x)=Ip(y)而x≠y,则称此种情况为一个误匹配
现在需要确定:误匹配的概率有多大?
若总是随机地去取一个小于M的素数p,则对于给定 的x和y,
Pr[failure] =(使得Ip(x)=Ip(y)但x≠y的素数 p(p<M)的个数)/(小于M的素数的总个数)

Pattern Matching (Monto Carlo算法)

问题
给定两个字符串:X=x1,…,xn,Y=y1,…,ym, 看Y是否为X的子串?(即Y是否为X中的 一段)

  • KMP算法刚总结过,确实麻烦,随机算法可以大大简化思考的难度。brute-force思想
  • 记X(j)=xj x j+1…x j+m-1(从X的第j位开始、 长度与Y一样的子串)
  • 从起始位置j=1开始到 j=n-m+1,不去逐一 比较X(j)与Y,而仅逐一比较X(j)的指纹 Ip(X(j))与Y的指纹Ip(Y)
  • 由于Ip(X(j+1))可以很方便地根据Ip (X(j))计算出来,故算法可以很快完成
1.随机取一个小于M的素数p,置j←1; 
2.计算Ip(Y)、Ip(X(1))及Wp(=2m mod p); 
3.While j≤n-m+1 
	do 
	{
		if Ip(X(j))=Ip(Y) then return j /﹡X(j)极有可能等于Y﹡/ 
		else{
		根据Ip(X(j))计算出Ip(X(j+1));j增1
		} 
	} 
4.return 0;      /﹡X肯定没有子串等于Y﹡/ 

时间复杂度分析

  • 计算Ip(Y)、Ip(X(1))及2m mod p的时间不 超过O(m)次运算
  • Ip(X(j+1))的计算,只需用O(1)时间
  • 由于循环最多执行n-m+1次,故这部分的 时间复杂度为O(n),于是,总的时间复杂 性为O(m+n)
    错判率分析
  • 当Y≠X(j),但Ip(Y)=Ip(X(j))时产生失败
  • 失败的概率Pr[failure]<1/n 【这里有点迷惑,为什么是<1/n,不应该是<1/m么?每次比的时候是按Y的长度比的啊】,即失败的概率 只与X的长度有关,与Y的长度无关

上面两个问题的应用可以理解为:如果指纹不等,则必不存在;如果指纹相等,则大概率存在。在算法中判定为成功。具体代码没有编写,这种问题多看些资料扩展思路很有必要,我通过学习这些不断查找的名词和资料路径:
Brute Force
Pattern Matching
串匹配 - 中国科学技术大学
指纹函数
模和环(矩阵论,矩阵做f的数学基础需要补足,之前fibonacci的时候那个f就遇到过,本质是一种聪明的映射,需要掌握方法)
其中每个部分深入理解都大有获益。下次遇到相关问题时再进行总结。

  • Random Sampling问题

主元素问题

设T[1:n]是一个含有n个元素的数组。当 |{i|T[i]=x}|>n/2时,称元素x是数组T的主元素
问题描述 :对于给定的数组T,判定T数组中是否含有主元素
Monte Carlo算法:

package LasVegas;

import java.util.*;

public class NumKSmall {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Random r = new Random(1);
		ArrayList<Integer> x = new ArrayList<Integer>();
		for(int i = 0; i < 100; i++) {
			x.add(r.nextInt(100));
		}
		int k = r.nextInt(100);
		System.out.printf("在x中第 %d 小的数字是: \n",k);
		System.out.println(NKS_LV(x, k));
		
	}

	private static int NKS_LV(ArrayList<Integer> x, int k) {
		// TODO 自动生成的方法存根
		int len = x.size();
		Random r = new Random(1);
		int s = x.get(r.nextInt(len));
		ArrayList<Integer> S1 = new ArrayList<Integer>();
		ArrayList<Integer> S2 = new ArrayList<Integer>();
		ArrayList<Integer> S3 = new ArrayList<Integer>();

		for(int i = 0; i < len; i++) {
			int xx = x.get(i);
			if (xx < s) {
				S1.add(xx);
			}else if (xx == s){
				S2.add(xx);
			}else {
				S3.add(xx);
			}
		}
		if (k <= S1.size()) {
			return NKS_LV(S1,k);
		}else if ( k <= S1.size() + S2.size()) {
			return s;
		}else {
			return NKS_LV(S3, k - S1.size() - S2.size());
		}
			
	}

}

n后问题

  • 在n×n格的棋盘上放置彼此不受攻击的n 个皇后。
  • 按照国际象棋的规则,皇后可以攻击与之处在同一 行或同一列或同一斜线上的棋子。n后问题等价于 在n×n格的棋盘上放置n个皇后,任何2个皇后不放 在同一行或同一列或同一斜线上。
  • n后问题的Las Vegas算法思路:
    • 各行随机放置皇后,使新放的与已有的互不攻击,
    • until (n皇后放好||无可供下一皇后放置的位置)

以八皇后为例

package LasVegas;

import java.util.*;
import java.util.Random;

public class NQueens {
	static int n;
	static ArrayList<Integer> queens;
	public static void main(String[] args) {
	 n = 8;
	 boolean t = false;
	 while( t != true) {
		t = FindOne(n);
	 }
	 if(t == true) {
	 
	 System.out.println(queens);
	 }
}

private static boolean FindOne(int nn) {
	// TODO 自动生成的方法存根
	queens= new ArrayList<Integer>();
	int count = 0;
	Random r = new Random();
	for(int i = 1; i < 9; i++) {
		while(queens.size() < i) {
			int a = r.nextInt(8) + 1;
			int b =r.nextInt(8) + 1;
			int j = a*10 + b;
			count++;
			if (notConflict(j)) {
				queens.add(j);
				count = 0;
			}
			if( count == 8) {
				System.out.println("算法失败");
				return false;
			}
		}
		
	}
	return true;
	
}

private static boolean notConflict(int j) {
	// TODO 自动生成的方法存根
	for(int q : queens) {
		if(j%10 == q%10 || j/10 == q/10 || j/10 - j%10 == q/10 - q%10 || j/10 + j%10 == q/10 + q%10) {
			return false;
		}  
	}
	return true;
}
}

猜你喜欢

转载自blog.csdn.net/u013453787/article/details/83144551