编程珠玑第十二章--应用之取样问题

本文介绍了一种生成指定数量的有序随机数的算法,包括基于概率的选择法、使用TreeSet集合的方法及数组洗牌法。此外,还探讨了在不同场景下如何优化算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理(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时间而肆意妄为

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值