編程珠璣之生成0至n-1之間的k個不同隨機序列的擴展問題 --2014人人網筆試題目
《編程珠璣》中習題1.4的題目是:“如果認真考慮了習題3,你將會麵對生成小於n且沒有重複的k個整數的問題。最簡單的方法就是使用前k個正整數。這個極端的數據集合將不會明顯的改變位圖方法的運行時間,但是可能會歪曲係統排序的運行時間。如何生成位於0至n - 1之間的k個不同的隨機順序的隨機整數?盡量使你的程序簡短高效。”
解決這個問題可以使用以空間換時間的方式,基本的思想是 利用洗牌的原理,將n個數(0至n-1)按次序排好,依次讓每個數和一個隨機挑選出的位子進行互換,這樣肯定不會重複,而且次序被打亂,具有隨性。 隻用交換k次,就可以取出k個小於n的互不相同的隨機數。
由這個問題我們引出以下這個筆試題目的問題:
“使用一個長度為n的數組來產生隨機數,當產生的隨機數與數組的存在的相同則重新隨機,當不同時插入到數組中,使用這種方法產生1到n的隨機數的期望次數是多少次?(編程實現)”
解答:
一般的思想是產生一個隨機數 arr[i] 後,和前麵已經產生的arr[0]~arr[i-1]進行比較,如果有重複的就重新產生一個,該算法的平均時間複雜度為:O(n^2) ,而最壞複雜度為無限!!
這裏我們按照編程珠璣上那個問題的擴展想法,利用空間換時間的算法,生成隨機排列的數,此時時間複雜度為O(n)。
(1)建立一個長度為n+1的臨時數組b,對該數組賦值依次賦值0~n, 即b[i] = i;
(2)取一個1~n之間的隨機整數k=rand(0,n],取b[k]讀入arr[i],再將b數組最後一個元素b[b.size-1]賦值給b[k],將b的長度減1;
代碼實現:
/* *BLOG:https://blog.csdn.net/wdzxl198 *AUTHOR:Atlas *EMAIL:wdzxl198@163.com */ #include<iostream> using namespace std; void Random(int *arr,int length) { srand(time(NULL)); int i,randnum; int limitsize = length; int b[limitsize]; for(i=0; i<limitsize; i++) { b[i] = i+1; } for(i=0; i<length; i++) { randnum = rand()%limitsize; limitsize--; arr[i] = b[randnum]; b[randnum] = b[limitsize]; } } int main() { int arr[100]; //假使隨機出1到100的隨機數 Random(arr,100); for(int i=0;i<100;i++) { cout<<arr[i]<<" "; } cout<<endl; quickSort(arr,0,99); for(int i=0;i<100;i++) { cout<<arr[i]<<" "; } cout<<endl; system("PAUSE"); return 0; }這種方法的是O(n)時間複雜度,但是沒有求出題目中期望的次數。
在求期望的過程中,我發現一篇博文,題目是有一個數組,每次從中間隨機取一個,然後放回去,當所有的元素都被取過,返回總共的取的次數。寫一個函數實現。複雜度是什麼。,這個題目與文章中的類似,轉載過來分析下,
import java.util.Random; import java.util.Set; import java.util.TreeSet; /** * #麵試題#有一個數組,每次從中間隨機取一個,然後放回去,當所有的元素都被取過,返回總共的取的次數。 * 寫一個函數實現。複雜度是什麼。 * bitmap,隻要一個bit就可以標記是否被取過,可參考《編程珠璣》的位圖排序 * * 時間複雜度的話,我不太會算,以下是引用https://github.com/vyan/test/blob/master/accessTimes.cpp: * 使用bit打點記錄已經取的數, * 複雜度分析,假設數組總長度為n * 取到第1個之前未被取到的數的期望 E(1)=1 * 取到第2個之前未被取到的數的期望 E(2)=n/n-1 * 取到第3個之前未被取到的數的期望 E(3)=n/n-2 * ... * 取到第n個之前未被取到的數的期望 E(n)=n/1 * 總得期望次數E=n+n/(n-1)+n/(n-2)+...+n/1; * =n(1+1/(n-1)+1/(n-2)+...+1/1) * =nln(n) * 所以算法平均複雜度為nlogn * * 下麵的代碼裏麵,除法和求模運算我都用位運算來實現,事實上直接用java提供的(/,%)也可以 * 同時,為了驗證是否真的取到了數組的所有元素,我用了TreeSet保存已選中的下標(去重) * * 最後,還有一個問題是,可能取了很多次,都沒能全選中,這個時候可以設置一個最長時間或者最大嚐試次數,超過則結束程序, * 避免程序長時間運行甚至死循環 * * @author lijinnan * */ public class TimesOfAccessArray { public static final int SHIFT = 5; public static final int BLOCK_SIZE = (1 << SHIFT); //32 public static final int MASK = (1 << SHIFT) - 1; //31 public static void main(String[] args) { int[] array = new int[200]; long times = accessTimes(array); System.out.println(times); } public static long accessTimes(int[] array) { if (array == null || array.length == 0) { return -1L; } long result = 0L; int len = array.length; int[] bitmap = new int[divide(len, BLOCK_SIZE) + 1]; int setTimes = 0; Set<Integer> set = new TreeSet<Integer>(); while (setTimes < len) { int pos = new Random().nextInt(len); set.add(pos); if (set(bitmap, pos)) { setTimes++; } result++; } System.out.println(set); return result; } public static boolean set(int[] bitmap, int pos) { boolean result = false; int blockNo = divide(pos, BLOCK_SIZE); int index = mod(pos, BLOCK_SIZE); boolean notExist = (bitmap[blockNo] & (1 << index))== 0; if (notExist) { bitmap[blockNo] |= (1 << index); result = true; } return result; } public static boolean exist(int[] bitmap, int pos) { int blockNo = divide(pos, BLOCK_SIZE); int index = mod(pos, BLOCK_SIZE); return (bitmap[blockNo] & (1 << index)) != 0; } private static int divide(int x, int y) { return x >> offSet(y); } private static int mod(int x, int y) { int z = x; int i = offSet(y); return z - ((z >> i) << i); } //e.g. 32=2^5, return 5 隻考慮2的冪 private static int offSet(int y) { return howManyBits(y) - 1; } //二進製的表示裏麵,有多少位。例如32是6位 private static int howManyBits(int y) { int bitNum = 0; while (y != 0) { y = (y >> 1); bitNum++; } return bitNum; } }
最後更新:2017-04-03 15:21:55
上一篇:
JDBC小結 單例模式 靜態代碼塊
下一篇:
實戰DeviceIoControl 之一:通過API訪問設備驅動程序
手機瀏覽器無法獲取COOKIE的原因
開發那點事係列五 - 技術方案的發散思維
J2EE部署項目至Tomcat報錯:Unable to read TLD "META-INF/c.tld"
關於Fintech的九個預言
Vim技能修煉教程(3) - 語法高亮進階
python3學習
android:layout_weight屬性詳解
android listview addHeaderView和addFooterView的注意事項
雙11背後基礎設施軟硬 結合實踐創新
System.InvalidOperationException: 集合已修改;可能無法執行枚舉操作。