《編程珠璣》第十二章:取樣問題

如今跟你們分享一下第十二章的心得。 java

1.問題描述 數組

程序的輸入包含兩個整數m和n,其中m<n。輸出是0~n-1範圍內m個隨機整數的有序列表,不容許重複。從機率的角度說,咱們但願獲得沒有重複的有序選擇,其中每一個選擇出新的機率相等。 dom

2.程序設想 函數

  • 問題先從簡單的方面開始想。

當m=2,n=5時,首先考慮第一個整數0。選擇0的機率是2/5,即m/n。如今開始對第二個整數1進行思考。若已經選擇了0,那麼剩下的4個數中,選擇1個數,因此選擇1的機率爲1/4;但對於沒有選擇0的狀況進行分析,那麼就從剩下4個數中,還能夠選擇2個數,因此選擇1的機率就變爲2/4。 this

  • 問題抽象提煉。

當從r個剩餘的整數中選出s個,機率就會是s/r。因此能夠用如下僞代碼來表示: spa

select = m
remaining = n
for i= [0,n]
    if ( rand() % remaining) < select 
            print i
            select --
    remaining --

只要m<n,程序會正確地選出m個整數。不會選擇更多的整數,由於當select = 0 時,if的判斷語言爲false,因此就不會進行選擇;也不會選擇更少的整數,由於select/remaing = 1時,這個整數必定會被選到。 code

對於此中,具體java代碼實現以下: 排序

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

import ckj.programperl.util.Constants;
import ckj.programperl.util.Util;

public class RandomSelect {
	
	private int m_length;          // n
	private int m_select;          // m
	private int[] m_array_range; 
	private int[] m_select_range;
	private Random m_rand;        // 隨機數
	
	public RandomSelect(){
		this(Constants._ARRAY_SELECT,Constants._ARRAY_LENGTH);
	}
	

	public RandomSelect(int m, int n){
		m_length = n ;
		m_select = m ;
		m_rand = new Random();
	}
	
	public void method1(){
		m_select_range = new int[m_select];
		int select = m_select;
		int remaing = m_length;
//		System.out.println(m_rand.nextInt());
		int pos = 0 ;
		for ( int i = 0 ; i < m_length ; i ++){
			if ( m_rand.nextInt(remaing)  < select ){
				m_select_range[pos] = i ;
				pos ++ ;
				select --;
			}
			remaing -- ;
		}
		System.out.println("method1產生的隨機數--->");
		Util.print(m_select_range);
	}

 

3.其餘方法 rem

想到這裏,可能不少人都放棄了繼續對此問題的思考。這書要教會的是,不要知足與一個解法。 get

考慮都Set這個數據集合的特殊性,(沒有重複性),因此能夠叫Set做爲咱們存儲的結構來存儲這個隨機數。由於當出現了相同的隨機數時,SET會自動把它丟棄,直到添加到新的隨機數爲主。爲了保證其有序性,因此使用了TreeSet集合保存隨機數。其代碼實現以下:

public void method2(){
		m_select_range = new int[m_select];
		Set<Integer> set = new TreeSet<Integer>();
		while(set.size() < m_select){
			set.add(m_rand.nextInt(m_length));
		}
		Iterator<Integer> itr = set.iterator();
		int i = 0 ;
		while ( itr.hasNext()){
			m_select_range[i] = itr.next();
			i++;
		}
		System.out.println("\nmethod2產生的隨機數--->");
		Util.print(m_select_range);
	}

而第三種方法,也是我本身最初想到的方法。由於記得前面的章節曾提出過隨機生成不重複的數,因此我能夠直接調用其方法,而後對這些隨機數排序便可。而隨機生成不重複的數,運用的是互換位置法。如,一個數組:0,1,2,3,4,5。因此任意生成從[0,5]的隨機數,而後與第1個位置的數互換。接着,隨機生成[1,5]的隨機數,與第2個位置互換。這樣獲得的前2位就是咱們想要的不重複的隨機數。這種方法的好處,就是空間利用爲0(n)。不如第一種方法。具體代碼實現以下:

public void mehtod3(){
		List<Integer> list = new ArrayList<Integer>();
		int range = m_length;
		int select =m_select;
		m_array_range = new int[range];
		m_select_range = new int[select];

		for ( int i = 0 ; i < range;i++){
			m_array_range[i] = i;
		}
		for (int i = 0 ; i < select ; i ++){
//			System.out.println(m_rand.nextInt(range));
			Util.swap ( m_array_range,i,i+m_rand.nextInt(range-i) );
			list.add(m_array_range[i]);
		}
		Collections.sort(list);
		//Util.print(list.toArray());
		for ( int i = 0 ; i < m_select ; i ++){
			m_select_range[i] = list.get(i);
		}
		System.out.println("\nmethod3產生的隨機數--->");
		Util.print(m_select_range);
	}

 

在util包中的實際的靜態方法以下:

package ckj.programperl.util;

public class Util {
	
	public static void print(int array[]){
		for ( int i = 0 ; i < array.length ; i ++){
			System.out.print(array[i] + "  ");
		}
		System.out.println();
	}
	
	public static void swap ( int array[], int i, int j ){
//		System.out.println("i--->"+i+"   j--->" + j);
		int temp = array[i];
		array[i] = array[j];
		array[j] = temp;
	}
}

 

參數數值:

package ckj.programperl.util;

public class Constants {
	public static int _ARRAY_LENGTH = 16000000;
	public static int _ARRAY_SELECT = 20;
}

 

4.評價與小結

 

主函數的調用:

package ckj.programperl.randomselect.Main;

import ckj.programperl.randomselect.RandomSelect;

public class Main {

	public static void main(String[] args) {
		RandomSelect rs = new RandomSelect();
		long time1 = System.currentTimeMillis();
		rs.method1();
		long time2 = System.currentTimeMillis();
		System.out.println("花費時間--->"+(time2-time1));
		rs.method2();
		long time3 = System.currentTimeMillis();
		System.out.println("花費時間--->"+(time3-time2));
		rs.mehtod3();
		long time4 = System.currentTimeMillis();
		System.out.println("花費時間--->"+(time4-time3));
	}

}

運行效果爲:

method1產生的隨機數--->
1997491  2750273  3127074  3833180  4427167  4581482  4676440  5215199  6053172  6260700  6342617  6982987  7713904  8902963  9999452  10547400  12117138  12260635  12919556  14783364  
花費時間--->258

method2產生的隨機數--->
313021  1386478  2009643  2397259  3266888  4292300  5405340  5434602  5577191  8033889  8973808  10698762  11024965  11295896  11568427  12631196  12639134  12675218  12872131  13338840  
花費時間--->2

method3產生的隨機數--->
2517438  2628671  3355097  3503923  6049574  7359175  7711849  8980609  9106329  9245641  9539006  10240380  11092067  11617640  11854653  11993375  12317527  14661711  15945139  15981795  
花費時間--->35

第二種方法,當m接近與n時,SET集合就會丟棄不少重複的隨機數,使得效率較慢。而第三種方法,使用O(n)的空間,比第一種方法的效果差。但從結果來看,當n遠遠大於m時,第二種方法比較適合。當m接近n時,第一種方法比較適合。

相關文章
相關標籤/搜索