导读

这里随机数问题介绍3个题目,可以算是一大类的吧。
话不多说,直接看题:

第一题:基础题目

  • 思路:

用随机数1——5, 来产生随机数1——7。
就要想办法通过已有的条件1——5,来凑!
首先要有个0出来,方便后面计算。rand1To5() - 1 : 可产生:0,1,2,3,4
这些范围比7小,还要连续的数字都要有,(4后面是5,所以乘以5)
那么rand1To5() * 5 可产生:0,5,10,15,20
这两个凑出来的 相加,可产生:0,1,2,3,4,5,6,。。。,20,21,22,23,24。
这些范围已经大于7了,那么就将大于7的那部分,在重新随机下,保证产生在0——6之间的数即可,在加上个1,就是产生1——7。

  • 代码:
	public static int rand1To5() {
   
		return (int)(Math.random()*5) + 1;
	}
	
	// 解法
	public static int rand1To7() {
   
		int num;
		do {
   
			num = (rand1To5() - 1) + ((rand1To5() - 1) * 5); 
		}while(num >= 7);
		return num + 1;
		
	}
  • 检验正确性
	public static void main(String[] args) {
   
		// 检验基础题目
		int[] map = new int[8]; 	// 哈希表。 下标是对于产生的数字,相应的值是产生的次数。
		int times = 1000; 			//随机生成的次数
		int sum = 0;
		for(int i = 0; i < times; i++) {
   
			int num = rand1To7();
			map[num]++;
		}
		for(int i = 0; i < 8; i++) {
   
			System.out.println("出现数字" + i + "的次数 : " + map[i]);
			sum += map[i];
		}
		System.out.println(sum); // 看看总的次数是不是和 随机生成的次数相等
	}

运行结果:

第二题: 补充题目

  • 思路:

题目中不是等概率产生的0,1。
但是!
调用2次,产生01,和产生10的概率是相等的,概率都是p*(1-p)。以此思想来等概率的产生 0 和 1。记为 rand01()
接着做法和 基础题目差不多了,
rand01() 等概率产生0,1
则 rand01() * 2 等概率产生 0,2
以上相加,等概率产生 0,1,2,3
至此,还是没有产生到1——6,范围小了。还要连续的数字都要有,(3后面是4,所以乘以4)
则 以上相加后的结果 * 4,即(0,1,2,3)*4, 等概率产生 0,4,8,12
2组的(0,1,2,3)和 (0,4,8,12) 相加,
等概率产生 0,1,2,3,4,5,6,。。。,12,13,14,15
在6之后的重复此步骤,保证随机产生0——5,
结果加1,就等概率产生了1——6.

  • 代码:
	public static int rand01p() {
   
		double p = 0.83;
		return Math.random() < p ? 0 : 1;
	}
	
	// 解法:
	// 先等概率产生0和1
	public static int rand01() {
   
		int num;
		do {
   
			num = rand01p();
		}while(num == rand01p()); // 这2此调用,一次0,一次1,不相等,就跳出while循环。 相应的,产生的0或1是等概率的
		return num;
	}
	// 等概率产生0到3
	public static int rand0To3() {
   
		return rand01() * 2 + rand01();
	}
	// 等概率产生1到6
	public static int rand1To6() {
   
		int num;
		do {
   
			num = rand0To3() * 4 + rand0To3();
		}while(num >= 6);	// 产生0——5
		return num + 1;
	}
  • 检验正确性:
	public static void main(String[] args){
   
		// 检验补充题目
		int[] map = new int[7]; 	// 哈希表。 下标是对于产生的数字,相应的值是产生的次数。
		int times = 500; 			//随机生成的次数
		int sum = 0;
		for(int i = 0; i < times; i++) {
   
			int num = rand1To6();
			map[num]++;
		}
		for(int i = 0; i < 7; i++) {
   
			System.out.println("出现数字" + i + "的次数 : " + map[i]);
			sum += map[i];
		}
		System.out.println(sum); // 看看总的次数是不是和 随机生成的次数相等
	}

运行结果:

第三题: 进阶题目

  • 思路:

用等概率产生1——M, 来等概率产生1——N。
这说明 可以用任意的等概率随机数,来产生另外的任意的等概率随机数。

先说下 对这道题的宏观看法,宏观思想:
首先,如果M >= N 的话,直接进行前面的题的最后步骤,超出范围内的话,在重复随机产生步骤,保证产生的数在范围内。很简单。
最后,如果M < N 的话,我们还要用1——M这个函数,来随机产生比N范围大的数,不管怎么凑,只要比N范围大就行。接着就是上面的那种情况了

好了,再来看看微观的解法:(怎么用代码实现)
已有的是随机产生1——M,我们可以拿到这些数字,那么把N这个数字用M进制来表示,其范围也仍是 0——M-1。
在随机产生1——N范围的数字,用M进制表示,因为我们能控制的是1——M的随机数,用M进制要方便很多。
最后把 产生的在范围内的 M进制数字 转化为 十进制数字即可。

	public static int rand1ToM(int m) {
   
		return (int)(Math.random() * m + 1);
	}
	
	// 解法:
	// 将N 转成 M进制的数字,方便后续操作
	public static int[] getNnumByMsys(int m, int n) {
   
		int[] res = new int[32];	//这里认为 一个整数 的位数 最多有 32位。
		int index = res.length - 1;
		while(n != 0) {
   
			res[index--] = n % m;
			n = n / m;
		}
		return res;
	}
	
	// 生成 用M进制表示的 0——N的随机数,
	public static int[] getRandNumByMsys(int m, int[] nByMsys) {
   
		int[] res = new int[nByMsys.length];
		int start = 0;		// 记录着 第一个不为0的数字的下标位置
		while(nByMsys[start] == 0) {
   
			start++;
		}
		int index = start;
		boolean lastEqual = true; 	// 比较后面的数字 有没有必要 接着和 N 的M进制的数字 来比较
		while(index != nByMsys.length) {
   
			res[index] = rand1ToM(m) - 1;
			if(lastEqual) {
   
				if(res[index] > nByMsys[index]) {
     // 第一位数字,不能比 N 的数字要大, 否则就超出了1——N的范围了。
					// 一旦是要大于的,不管在那个步骤,都要重新来,从头来(即从start位置开始) 产生, 全部恢复初始状态位置。
					// 否则的话,只有continue。 后面产生2位数字的话,最后几位数(比较接近末尾的,比较大的数)产生的概率要明显比前面的数的概率要大。
					// 因为没有恢复初始状态,而只是在index位置继续随机产生,前面start位置的数字是相等的,所以概率对应的也要大一些。
					// 可以试试 只有continue的情况, N的取值>=10。 就能看出情况了。
					index = start;	
					lastEqual = true;
					continue;
				}else {
   
					// 第一位数字相等还要比较后面的数字,如果产生的比 N 的相应数字要小,则后面的数随便产生了(当然是M进制内的数字)。
					lastEqual = res[index] == nByMsys[index];  
				}
			}
			index++;
		}
		return res;
	}
	
	// 将生成的 M进制的随机数 转换成 十进制的数字
	public static int getNnumFromMsys(int m, int[] nByMsys) {
   
		int res = 0;
		int index = 0;
		while(index < nByMsys.length) {
   
			res = res * m + nByMsys[index++]; 	// 秦九韶算法。 就是把那些公因子 一步一步的提出来了。
		}
		return res;
	}
	
	// 随机产生 1——N
	public static int rand1ToN(int m, int n) {
   
		int[] nByMsys = getNnumByMsys(m, n);
		int res = 0;
		while(res == 0) {
   	// 函数内部实现的是0——N,我们没有要0。
			int[] nRandByMsys = getRandNumByMsys(m, nByMsys);
			res = getNnumFromMsys(m, nRandByMsys);
		}
		return res;
	}

-检验正确性

	public static void main(String[] args){
   
		// 检验进阶题目
		int m = 3;
		int n = 20;
		int times = 10000;
		int[] map = new int[n+1];
		for(int i = 0; i < times; i++) {
   
			int num = rand1ToN(m, n);
			map[num]++;
		}
		int sum = 0;
		for(int i = 0; i < n+1; i++) {
   
			System.out.println( i + " : " + map[i]);
			sum += map[i];
		}
		System.out.println("sum : " + sum);
	}

运行结果:

呼~ 终于写完了。。。

啰嗦一句:
特别是 第三题的代码里的注释要好好看一下,可能写的不是很清楚,但是自己手写一遍,带几个数字进去,跟着代码走一遍,应该也很清楚了。。。