c語言 rand() 隨機函數


各種編程語言返回的隨機數(確切地說是偽隨機數)實際上都是根據遞推公式計算的一組數值,當序列足夠長,這組數值近似滿足均勻分布。

c的標准函數庫提供一隨機數生成器rand(定義在stdlib.h),能返回0~RAND_MAX之間均勻分布的偽隨機整數(RAND_MAX至少為32767,一般都默認為32767)。

用rand()隨機生成在[x,y]內的整數

int k;

k=x+rand()%(y-x+1),k即為所求范圍內隨機生成的數,rand()%a的結果最大為a-1.

rand( )%20的意思的生成20以內的隨機數。

例如:

#include<stdio.h>
#include<stdlib.h>
void main()
{
       for(int i=0;i<10;i++)
             printf("%d\n",rand());
}

如果我們是第一次運行,而且對其不太清楚,那么它生成的基本上算是0-RAND_MAX之間的等概率隨機數列了。但是如果你第二次運行的時候會發現輸出結果仍和第一次一樣。

原來rand()生成偽隨機數時需要一個種子(計算偽隨機序列的初始數值)才行,如果種子相同就會得到相同的序列結果(這就是函數的好處T-T)。這個“優點”被有的軟件利用於加密和解密。加密時,可以用某個種子數生成一個偽隨機序列並對數據進行處理;解密時,再利用種子數生成一個偽隨機序列並對加密數據進行還原。這樣,對於不知道種子數的人要想解密就需要多費些事了。當然,這種完全相同的序列對於你來說是非常糟糕的。要解決這個問題,需要在每次產生隨機序列前,先指定不同的種子,這樣計算出來的隨機序列就不會完全相同了。

srand()用來設置rand()產生隨機數時的隨機數種子。在調用rand()函數產生隨機數前,必須先利用srand()設好隨機數種子seed, 如果未設隨機數種子, rand()在調用時會自動設隨機數種子為1(有人說默認是0,困惑中)。上面的兩個例子就是因為沒有設置隨機數種子,每次隨機數種子都自動設成相同值1 ,進而導致rand()所產生的隨機數值都一樣。(可能有人知道C語言中的隨機函數random,可是random函數並不是ANSI C標准,所以說,random函數不能在gcc,vc等編譯器下編譯通過。我們可以自己編一個^0^)我們需要使程序每一次使用的種子都不一樣,現在主要問題是種子srand的選擇是不是接近隨機(不存在完全隨機),你也可以人為指定種子數。Windows 9x/NT的游戲FreeCell就允許用戶指定種子數,這樣用戶如果一次游戲沒有成功,下次還可以以同樣的發牌結果再玩一次。但我們還是喜歡系統自動生成……最簡單的方法就是利用系統時間了(幾乎所有的人都這么做),因為時間的數值隨時間變化而變化,運行兩次,一般不會出現前一次和后一次相同的局面,time(NULL)會返回一個表示當前系統時間的整數(它在time.h中,據說time()函數求出來的是自1970年1月1日到現在的秒數,有的說是unix元年,不知道這兩個是不是一天……:( ,另外有的嫌麻煩會寫作time(0))。我們這么來寫:

 #include<stdio.h>
#include<stdlib.h>
#include<time.h>

void main()
{

     srand((int)time(0));
     for(int x=0;x<10;x++)
           printf("%d\n",(rand());
}

 據說如果軟件一直開兩天,種子會有1/(60*60*24)個可能會重復,雖說這對於一般人已經絕對足夠了,但縱然人不舒服,於是我在網上開到有這么寫的:

 #include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/timeb.h>

void main(void)
{
    int i;
    unsigned int seedVal;
    struct timeb timeBuf;
    ftime(&timeBuf);
    seedVal=((((unsigned int)timeBuf.time&0xFFFF)+
                 (unsigned int)timeBuf.millitm)^
                 (unsigned int)timeBuf.millitm);
    srand((unsigned int)seedVal);
    for(i=0;i<10;++i)
        printf("%6d\n",rand());
}

“ 上面的程序先是調用_ftime()來檢查當前時間,並把它的值存入結構成員timeBuf.time中,當前時間的值從1970年1月1日開始以秒計算。在調用了_ftime()之后,在結構timeBuf的成員millitm中還存入了當前那一秒已經度過的毫秒數,但在DOS中這個數字實際上是以百分之一秒來計算的。然后,把毫秒數和秒數相加,再和毫秒數進行異或運算。當然也可以對這兩個結構成員進行更多的計算,以控制seedVal的取值范圍,並進一步加強它的隨機性,但上例用的邏輯運算就已經足夠了。”

看下面代碼:

void main() 

 for(int i=0;i<100000;i++) 
 { 
 srand( (unsigned)time( NULL ) ); 
 printf("%d\n",rand());
 } 
}

 為什么總是生成一個數???因為此程序產生一個隨機數之前,都調用一次srand,而由於計算機運行很快,所以每用time得到的時間都是一樣的(time的時間精度較低,只有55ms)。這樣相當於使用同一個種子產生隨機序列,所以產生的隨機數總是相同的。把srand放在循環外看看: 
srand( (unsigned)time( NULL ) ); 
for(int i=0;i<100000;i++) 
問題解決了:)

既然生成的是 0-RAND_MAX之間均勻分布的隨機整數(我們姑且把以上方法生成的是理想的隨機數吧),那么要生成0-x之間(包括0不包括x)的隨機數就把rand()改成  rand()/(double)RAND_MAX*x ,要生成x-y之間(包括x不包括y)的就是  rand()/(double)RAND_MAX*(y-x)+x   了。但是如果要生0-10之間的整數很多人會這么寫:

 #include<stdio.h>
#include<stdlib.h>
#include<time.h>

void main()
{

     srand((int)time(0));
     for(int x=0;x<10;x++)
           printf("%d\n",(rand()%10);
}

x-y的就是 rand()%(y-x)+x

???懂一點概率的就知道這樣寫,會使得到的數列分布不均的,但是大家確實都喜歡這么寫……因為在x的值比較小,RAND_MAX相對較大,而生成的數列有不算太大,又是用來解決精確度要求不高的問題(如一些游戲目標,傳說游戲中踩地雷式的遇敵就是用rand()實現的. 當主角在地圖上走的時候動不動冒出三兩小兵挑釁兼找死.它的實現方式是設主角所立位置為0,主角每走一步,變量加1,當變量==隨機取得的數時,小兵出現),這樣寫足夠了……

 

 

 

下面再討論生成m個小於n的不重復隨機數的算法

本人認為算法結構很重要,但語句結構也很重要,所以我喜歡一個算法給出多個語句結構……

最容易想到的傻瓜算法,逐個產生這些隨機數,每產生一個,都跟前面的隨機數比較,如果重復,就重新產生:

算法一(1)

srand((unsigned)time(NULL));

for(j = 0; j < m; j++) {

a:a[j] = rand() % n; 
for(i=0;i<j;i++){if(a[i]==a[j])goto a;}

很早的時候學編程都喜歡用goto語句,因為過去算法是用流程圖表示的,用goto可以直接轉譯,而且循環語句發展不完善,僅僅是作為條件分支的一個補充,如果條件分支箭頭向上就是一個循環,而且goto可以實現過去循環所不能實現的結構,形成很巧妙的循環交叉。所以過去一般認為有兩種結構,順序和分支,循環是分支的特殊情況。但是break和continue語句使這些結構實現成為可能,后來發現goto的使用會造成維護和調試的許多麻煩,所以循環逐漸代替了goto,使程序更有層次。現在編程不建議用goto了,如果用goto還會被認為編程修養不好……言歸正題,把它的goto去掉:

算法一(2)

srand((unsigned)time(NULL));

j=0; 
while (j<m) 

a[j] = rand() % n ; 
for(i=0;i<j;i++){if(a[i]==a[j]) break; } 
if(i<j)continue;

j++
}

 但是后來都建議用for循環,這樣可以使循環條件跟緊湊:

算法一(3)

srand((unsigned)time(NULL));

for(j=0;j<m;j++)

{a[j]=rand()%n;

 for(i=0;i<j;i++)

 {if(a[i]==a[j])

   {i=-1;a[j]=rand()%n;}

 }

}

但這是個很笨的方法,且比較次數呈線性增長,越往后次數越多……另一個想法是生成一個就把它從中去掉,就可以實現不重復了:

算法二(1)

 for (i=0;i<n;i++)

     a[i]=i+1;

for(i=0;i<m;i++)

{j=rand()%(n-i);

 b[i]=a[j];

 for (k=j;k<n-i-2;k++)

 a[k]=a[k+1];

 }

b[]是生成的隨機數列

 這樣做也太麻煩了,生成的直接做個標記不就行了?

算法三(1-1)

i=rand()%m;

a[i]=1;b[0]=i;

for(j=1;j<m;j++)

{for (i=rand()%m;a[i]==1;i=rand()%m);

 b[j]=i;a[i]=1;

}

寫緊湊一些吧,直接:

算法三(1-2)

for(j=0;j<m;j++)

{for (i=rand()%m;a[i]==1;i=rand()%m);

 b[j]=i;a[i]=1;

}

但是我看到了更簡潔的:

int n=某值;

int a[n]={0};

for (i=1;i<=n;i++)

{while(a[x=rand()%n]);

  a[x]=i;

}

這個無循環體的while保證a[m]是初始化的0狀態。這生成了n個,我們只取m個,這太浪費了,結合一下:

int n=某值,m=某值;

int a[n]={0},b[m]

for (i=1;i<=n;i++)

{while(a[x=rand()%n]);

  b[i]=x;a[x]=1;

}

但是總叫人不舒服,對這種算法誰有更好的寫法呢?

這種算法越到后面,遇到已使用過的元素的可能性越高,重復次數就越多,但是當m較小時還是很好的:)

這都不太讓人滿意么?看看下面這個:

算法四(1)

for (i=0;i<n;i++)

  a[i]=i+1;

for (i=n-1;i>0;i--)

{w=rand()%i;

 t=a[i];

 a[i]=a[w];

 a[w]=t;

}

這個算法很不錯,有人會懷疑其隨機性,但個人認為是沒問題的,首先第二行按順序用0到n填滿整個數組;第三行,是隨機產生從0到n-2個數組下標,把這個下標的元素值跟n-1下標的元素值交換,一直進行到下標為1的元素。因此它只需要遍歷一次就能產生全部的隨機數。

但這樣會生成n個小於n的隨機數,我們只要m個,再加兩句:

for(i=0;i<m;i++)

 b[i]=a[i];

b[]是生成的隨機數列

 如果m和n很接近的話或者相等是最好,但可能不是……那改一下:

算法四(2)

for (i=0;i<n;i++)

  a[i]=i+1;

for (i=0;i<m;i++)

{w=rand()%n;

 t=a[i];

 a[i]=a[w];

 a[w]=t;

}

但是條件反過來了,如果m遠小於n還行,如果接近,其隨機性就存在問題了(為什么?),再改一下:

算法四(3)

for (i=0;i<n;i++)

  a[i]=i+1;

for (i=0;i<m;i++)

{w=rand()%(n-i)+i;

 t=a[i];

 a[i]=a[w];

 a[w]=t;

}

這樣可以萬無一失了……

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM