如今跟你們分享一下第十二章的心得。 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時,第一種方法比較適合。