原理(P142)
1.正确理解所遇到的问题。(与用户讨论,自己的想法)
2.提炼抽象问题。(简洁,明确地陈述问题)
3.考虑尽可能多的解法(想法要灵活,Google搜索,书籍等)
4.实现一种解决方案
5.回顾。Review看是否可以优化
问题定义:输入两个正整数参数m,n ,其中m<=n.输出是0~n-1中m个随机的有序列表,不允许重复。其中每个数出现的概率要相等。
解法:
解法一:利用概率,从剩余的r个元素选择s个,我们当前数被选择的概率可表示为 s/r
解法二 : 利用一个初始为空的TreeSet集合,往里面加入数,直到个数满足需要
解法三:将数组前m个元素打乱,然后进行排序输出
另外的思考:(要灵活)
第一点,如果n为100万而m为n-10时,我们会考虑先生成10个随机的有序样本,然后取剩下的
第二点,如果m为1000万而n为2^32,我们会先生成1100万个整数,然后对其进行排序,并删除其中重复的元素,最后得到一个有1000万的元素。
下面是书上的实现
package chapter12;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.TreeSet;
import chapter11.QuickSort;
public class GenOrderlyRandomNumbers {
public static int bigRand(){
Random random=new Random();
int result=random.nextInt(100000);
return Integer.MAX_VALUE-result;
}
//从[0,n)中产生m个有序的随机序列(m<=n),bigRand()%remaining<select来判断选不选
public static void genKnuth(int m,int n){
int select=m;
int remaining=n;
for(int i=0;i<n;i++){
if(select==0){
break;
}
if(bigRand()%remaining<select){
System.out.print(i+" ");
select--;
}
remaining--;
}
}
//往TreeSet集合插入元素,直到元素个数满足
public static void genSets(int m,int n){
TreeSet<Integer> treeSet=new TreeSet<Integer>();
while(treeSet.size()<m){
treeSet.add(bigRand()%n);
}
for(Integer i:treeSet){
System.out.print(i+" ");
}
}
public static int randomInt(int a,int b){
Random random=new Random();
return random.nextInt(b-a+1)+a;
}
//将数组前m个元素打乱,然后进行排序输出
public static void genShuffle(int m,int n){
int arr[]=new int[n];
for(int i=0;i<arr.length;i++){
arr[i]=i;
}
for(int i=0;i<m;i++){
int randomIndex=randomInt(i+1, n-1);
int temp=arr[randomIndex];
arr[randomIndex]=arr[i];
arr[i]=temp;
}
QuickSort.quickSort1(arr, 0, m-1);//上一单元的quickSort1算法,参见上一篇博客
for(int i=0;i<m;i++){
System.out.print(arr[i]+" ");
}
}
public static void main(String args[]){
// genKnuth(10, 1000);
// genSets(10, 1000);
genShuffle(10, 1000);
}
}
习题部分
1.已实现,见上述代码
2.
问题描述:给出一个算法,其中每个元素的选中概率相等,但是某些子集选中的概率比其他子集大一些。
解法:为了从0~n-1中选择m个数,可以现在该范围内随机选择一个整数,然后输出i,i+1,….i+m-1(当然假定是首尾相连的,对n取余就好),而此时对于特定子集即i+1,….,i+m-1被选中的概率明显比较大。
代码如下
//t2的解决方法
public static void genKnuth2(int m,int n){
int re=bigRand()%n;
for(int count=0;count<m;count++){
System.out.print(re+" ");
re=(re+1)%n;
}
}
4.赠券收集问题和生日悖论
7.将print语句放在递归函数之后
8.
第一种情况:使用treeSet来进行保存bigrand()%n产生的随机数,直到已经保存了m个元素。
第二种情况:直接使用arrayList来保存即可,最后对其排序即可。
第三种情况:直接bigrand()%n产生的随机数并输出,直到输出m个数停止
9.
问题描述:当m接近n时,之前基于集合的算法生成的随机数大都要丢掉,因为他们之前已经存在于集合了。能否给出一个算法,使得即使在最坏的情况下,也只使用m个随机数。
解法;巧妙利用计数器,让它再承担一个功能–当产生的随机数已经存在时,就把它加入集合
package chapter12;
import java.util.TreeSet;
public class t9 {
public static void genFloyd(int m,int n){
TreeSet<Integer> treeSet=new TreeSet<Integer>();
for(int count=n-m;count<n;count++){//count既是计数,又可以必要时充当集合treeSet的元素
int randomValue=GenOrderlyRandomNumbers.bigRand()%(count+1);
if(treeSet.contains(randomValue)){
treeSet.add(count);
}
else{
treeSet.add(randomValue);
}
}
System.out.println(treeSet);
}
public static void main(String args[]){
genFloyd(100, 101);
}
}
10.
问题描述:如何在不知道文本文件行数的情况下,从中随机选择一行并输出
解法:我们让第一行一定被选,然后第二行有1/2的概率被选,第三行有三分之一的概率被选,以此类推。最后对于n行的每一行,它被选中的概率为1/n;
程序:
package chapter12;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class t10 {
public static void selectRandomLine(File file){
try {
Scanner scanner=new Scanner(file);
int i=0;
String theChooseLine = null;
while(scanner.hasNextLine()){
if(GenOrderlyRandomNumbers.bigRand()%(++i)<1){
theChooseLine=scanner.nextLine();
// System.out.println(theChooseLine);
}
else
scanner.nextLine();
}
System.out.println(theChooseLine);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String args[]){
selectRandomLine(new File("E:/MyEclipse/WorkSpace/Pearls/src/chapter12/data_t10.txt"));
}
}
其中data_t10.txt的内容可以任意,此处我的内容为:
J
Java
JavaScript
PHP
Python
Android
Java Web
HTML 5
CSS
C
C++
Linux
ASP.net
Ruby
React Native
11.仔细分析问题,剔除无用的信息,不要受到问题陈述信息的误导,也不要因为你有CPU时间而肆意妄为