問題描述:
現要求產生 0~n-1 範圍內的 m 個隨機整數的有序列表,且不允許重複,m <= n。
考慮到 n 的值可能很大,而通常 C/C++ 提供的隨機數產生器所能返回的隨機數在 [0,RAND_MAX],其中,RAND_MAX 爲 0x7FFF。也就是說只有 15 位的隨機性。因此,我們需要要有自己的隨機數產生器,以便能夠返回更多位數的隨機數,通常爲 30 位。下面的函數可以滿足我們的要求:
int bigrand()
{
return (RAND_MAX * rand() + rand());
}
注意,這裏也可以採用將第一個 rand() 的返回值左移 15 位,然後加上第二個 rand() 的返回值的方法來提速。
解法一、
下面,我們就可以用這個隨機數產生器來解決上面的問題了。看到這裏,你是不是想到了什麼解決方法呢。下面的方法來自算法大師 Donald Knuth。
void genknuth(int m, int n)
{
srand((unsigned int)time(NULL));
for(int i = 0; i < n; i++)
{
if((rand() % (n - i)) < m)
{
cout<<i<<"/n";
m--;
}
}
}
其中的 srand 語句是我爲了在每次運行時能夠得到不同的隨機數序列而加的。
不知道這個方法有沒有出乎你的意料,算上 for 循環才 4 句話。不過,就是這 4 句話,已經很完美地解決了上面提出的問題,有序、互異、m 個數。
這個方法通過順序掃描 0~n-1 範圍內的每個數保證產生的隨機數有序和互異。
那產生 m 個數怎麼保證呢?又沒有類似循環的語句來控制產生的隨機數的個數。
要記住,程序語句只是我們實現某種功能的一種表現形式,這也就是說,我們要實現某種功能,可以由很多種表現形式。下面就來分析一下這個方法是如何保證能夠產生 m 個隨機數的。
想必大家都已經看到產生的隨機數都是在 if 語句中輸出的,那也就是說 if 語句控制了產生的隨機數的個數。因此,要說明能夠產生 m 個隨機數,只要說明 if 語句能夠執行且僅能執行 m 次。
首先,在 for 循環沒有推出的情況下,if 語句最多能夠執行 m 次,因爲 if 語句的每次執行都會使 m 的值減 1,當 m=0 時,if 語句就不會再執行了。因爲 (rand() % (n - i)) 是一個非負數。
其次,我們分析 if 語句最少能夠執行多少次。如果 if 語句開始一直得不到執行,那麼 (n-i) 便會一直減小,直到與 m 相等。這時 (rand() % (n - i)) 一定小於 (n-i),也就是 m,從而使得 if 語句得到一次執行。而此時 for 循環已經執行了 (n-m+1) 次,離推出循環只剩下 (m-1) 次,要使得 if 語句能夠執行 m 次,那麼接下來的 (m-1) 次循環 if 語句必須都得能夠執行。事實是什麼樣的呢?第一次 if 語句開始執行時,(n-i)=m,執行完時,(n-i) 和 m 都減少 1,從而仍然有 (n-i)=m 成立。這個過程一直持續到 m=0。剛好是 m-1 次。
因此,我們可以相信 if 語句確實能夠執行且只能執行 m 個。
Donald Knuth 實在是太厲害了!
順便說一句,據說在大學期間,有關程序設計之類的比賽,只要有他參加,第一名非他莫屬。
解決方法二:就是在一個初始爲空的集合中插入隨機整數,直到足夠的整數。
僞代碼如下所示:
initialize set S to empty
size = 0
while size < m do
t = bigrand()%n
if t is not in S
insert t into S
size++
print the elements of S in sorted order
該算法在選擇元素時能夠保證所有的元素都具有相同的選中概率,它的輸出是隨機的
void genknuth(int m, int n)
{
set<int> S;
while( S.size() < m)
S.insert(bigrand()%n);
set<int>::iterator i;
for(i = S.begin(); i != S.end(); ++i)
cout<<*i<<"/n";
}
C++標準模板庫規範能夠保證在O(log m)時間內完成每個插入操作,集合內部迭代需要O(m),所以整個程序需要的時間爲O(m log(m)(和n相比,m較小時)。然而,該數據結構的空間消耗較大。
解法三、
for i = [0,n)
swap(i, randint(i,n-1))
這個是弄亂數據x[0...n-1]。在這個問題中,只需要攪亂數組前面m個元素
int bigrand()
{
return RAND_MAX*rand() + rand();
}
int randint(int l, int u)
{
return l + bigrand() % (u-l+1);
}
void genknuth(int m, int n)
{
int i,j;
int *x = new int[n];
for(i = 0; i < n; i++)
x[i] = i;
for(i = 0; i < m; i++)
{
j = randint(i,n-1);
swap(x[i],x[j]);
}
sort(x, x+m);
for(i=0; i<m; i++)
cout<<x[i]<<"/n";
}
int main()
{
srand((unsigned)time(NULL));
int m,n;
while(1)
{
cout<<"Please enter m and n:";
cin>>m>>n;
genknuth(m,n);
}
return 0;
}
該算法使用了n個字的內存並需要O(n+mlogm)時間
解法四:(遞歸)
void randselect(int m,int n)
{
assert(m>=0 && m <= n);
if(m>0)
if((bigrand()%n) < m)
{
randselect(m-1,n-1);
cout<<n-1<<",";
}
else
randselect(m, n-1);
}
原文鏈接:
http://blog.csdn.net/ztj111/archive/2007/10/23/1840190.aspx
http://hi.baidu.com/xwf_like/blog/item/ff2a9b98293022006e068c2d.html