Câu hỏi này là về một nhận xét trong câu hỏi này
Cách đề xuất để khởi tạo srand? Nhận xét đầu tiên nói rằng srand()
chỉ nên được gọi là MỘT LẦN trong một ứng dụng. Tại sao nó như vậy?
Câu hỏi này là về một nhận xét trong câu hỏi này
Cách đề xuất để khởi tạo srand? Nhận xét đầu tiên nói rằng srand()
chỉ nên được gọi là MỘT LẦN trong một ứng dụng. Tại sao nó như vậy?
Câu trả lời:
Điều đó phụ thuộc vào những gì bạn đang cố gắng đạt được.
Ngẫu nhiên hóa được thực hiện như một hàm có giá trị bắt đầu, cụ thể là hạt giống .
Vì vậy, đối với cùng một hạt giống, bạn sẽ luôn nhận được cùng một chuỗi giá trị.
Nếu bạn cố gắng đặt hạt giống mỗi khi bạn cần một giá trị ngẫu nhiên, và hạt giống là cùng một số, bạn sẽ luôn nhận được cùng một giá trị "ngẫu nhiên".
Hạt giống thường được lấy từ thời điểm hiện tại, là giây, như trong time(NULL)
, vì vậy nếu bạn luôn đặt hạt giống trước khi lấy số ngẫu nhiên, bạn sẽ nhận được cùng một số miễn là bạn gọi combo srand / rand nhiều lần trong cùng giây .
Để tránh vấn đề này, srand chỉ được đặt một lần cho mỗi ứng dụng, vì có thể nghi ngờ rằng hai trong số các phiên bản ứng dụng sẽ được khởi tạo trong cùng một giây, vì vậy mỗi phiên bản sẽ có một chuỗi số ngẫu nhiên khác nhau.
Tuy nhiên, có một chút khả năng là bạn sẽ chạy ứng dụng của mình (đặc biệt nếu đó là một ứng dụng ngắn, hoặc một công cụ dòng lệnh hoặc thứ gì đó tương tự) nhiều lần trong một giây, khi đó bạn sẽ phải sử dụng một số cách khác để chọn seed (trừ khi bạn chấp nhận cùng một chuỗi trong các trường hợp ứng dụng khác nhau). Nhưng như tôi đã nói, điều đó phụ thuộc vào ngữ cảnh sử dụng ứng dụng của bạn.
Ngoài ra, bạn có thể muốn cố gắng tăng độ chính xác lên micro giây (giảm thiểu cơ hội của cùng một hạt giống), yêu cầu ( sys/time.h
):
struct timeval t1;
gettimeofday(&t1, NULL);
srand(t1.tv_usec * t1.tv_sec);
gettimeofday
đã lỗi thời trong POSIX 2008. Thay vào đó, nó giới thiệu clock_gettime
có thể yêu cầu liên kết với -lrt
. Tuy nhiên, nó có thể chưa có sẵn trên nhiều nền tảng. Trong Linux, điều này là ổn. Trên Mac, tôi nghĩ rằng nó chưa có sẵn. Trong Windows, nó có thể sẽ không bao giờ có sẵn.
Số ngẫu nhiên thực sự là giả ngẫu nhiên. Một hạt giống được đặt trước, từ đó mỗi lệnh gọi củarand
nhận được một số ngẫu nhiên, và sửa đổi trạng thái bên trong và trạng thái mới này được sử dụng trong lần rand
gọi tiếp theo để nhận một số khác. Bởi vì một công thức nhất định được sử dụng để tạo ra những "số ngẫu nhiên" này, do đó việc đặt một giá trị nhất định của hạt giống sau mỗi lần gọi tới rand
sẽ trả về cùng một số từ cuộc gọi. Ví dụ srand (1234); rand ();
sẽ trả về cùng một giá trị. Khởi tạo khi trạng thái ban đầu với giá trị gốc sẽ tạo ra đủ số ngẫu nhiên khi bạn không đặt trạng thái bên trong srand
, do đó làm cho các số có khả năng trở thành ngẫu nhiên hơn.
Nói chung, chúng tôi sử dụng time (NULL)
giá trị giây trả về khi khởi tạo giá trị hạt giống. Nóisrand (time (NULL));
là trong một vòng lặp. Sau đó, vòng lặp có thể lặp lại nhiều lần trong một giây, do đó, số lần vòng lặp lặp lại bên trong vòng lặp trong lần rand
gọi thứ hai trong vòng lặp sẽ trả về cùng một "số ngẫu nhiên", không mong muốn. Khởi tạo nó một lần khi bắt đầu chương trình sẽ thiết lập hạt giống một lần và mỗi lần rand
được gọi, một số mới được tạo ra và trạng thái bên trong được sửa đổi, vì vậy lần gọi tiếp theo rand
trả về một số đủ ngẫu nhiên.
Ví dụ mã này từ http://linux.die.net/man/3/rand :
static unsigned long next = 1;
/* RAND_MAX assumed to be 32767 */
int myrand(void) {
next = next * 1103515245 + 12345;
return((unsigned)(next/65536) % 32768);
}
void mysrand(unsigned seed) {
next = seed;
}
Trạng thái nội bộ next
được khai báo là toàn cục. Mỗimyrand
cuộc gọi sẽ sửa đổi trạng thái bên trong và cập nhật nó, đồng thời trả về một số ngẫu nhiên. Mỗi cuộc gọi của myrand
sẽ có một next
giá trị khác nhau do đó phương thức sẽ trả về các số khác nhau mỗi lần gọi.
Nhìn vào việc mysrand
thực hiện; nó chỉ đơn giản là đặt giá trị hạt giống mà bạn chuyển đến next
. Do đó nếu bạn đặtnext
giá trị giống nhau mọi lúc trước khi gọi rand
nó sẽ trả về cùng một giá trị ngẫu nhiên, vì công thức giống hệt nhau được áp dụng trên nó, điều này không mong muốn, vì hàm được tạo thành ngẫu nhiên.
Nhưng tùy thuộc vào nhu cầu của bạn, bạn có thể đặt hạt giống thành một số giá trị nhất định để tạo ra cùng một "chuỗi ngẫu nhiên" cho mỗi lần chạy, chẳng hạn như đối với một số điểm chuẩn hoặc một số điểm khác.
man srand
. Phạm vi từ 0 đến 32767 (giả sử RAND_MAX), nhỏ hơn nhiều so với long
phạm vi. Biến trạng thái next
được thực hiện long
khi phép nhân và phép cộng bên trong sẽ vượt quá phạm vi của an unsigned int
. Sau đó, kết quả được chia tỷ lệ hoặc sửa đổi trong phạm vi được chỉ định ở trên. Mặc dù bạn có thể tạo ra hạt giống long
.
Trả lời ngắn gọn: gọi điện thoại srand()
là không giống như "cán dice" cho bộ tạo số ngẫu nhiên. Nó cũng không giống như xáo trộn một bộ bài. Nếu bất cứ điều gì, nó giống như chỉ cắt một bộ bài.
Hãy nghĩ về nó như thế này. rand()
giao dịch từ một bộ bài lớn và mỗi khi bạn gọi nó, tất cả những gì nó làm là chọn một lá bài tiếp theo ở đầu bộ bài, cung cấp cho bạn giá trị và trả lại lá bài đó về cuối bộ bài. (Đúng, điều đó có nghĩa là chuỗi "ngẫu nhiên" sẽ lặp lại sau một thời gian. Tuy nhiên, đó là một bộ bài rất lớn: thường là 4,294,967,296 thẻ.)
Hơn nữa, mỗi khi chương trình của bạn chạy, một gói thẻ hoàn toàn mới được mua từ cửa hàng trò chơi và mọi gói thẻ hoàn toàn mới luôn có cùng một trình tự. Vì vậy, trừ khi bạn làm điều gì đó đặc biệt, mỗi khi chương trình của bạn chạy, nó sẽ nhận lại chính xác các số "ngẫu nhiên" từ đó rand()
.
Bây giờ, bạn có thể nói, "Được rồi, vậy làm cách nào để xáo trộn bộ bài?" Và câu trả lời - ít nhất là trong chừng mực rand
và srand
có liên quan - là không có cách nào để xáo trộn bộ bài.
Vậy srand
làm gì? Dựa trên phép loại suy mà tôi đã xây dựng ở đây, cách gọi srand(n)
về cơ bản giống như nói, "cắt các n
lá bài từ trên xuống". Nhưng chờ đợi, một điều nữa: nó thực sự bắt đầu với một bộ bài mới toanh khác và cắt n
các lá bài từ trên xuống .
Vì vậy, nếu bạn gọi srand(n)
, rand()
, srand(n)
, rand()
, ..., với cùng n
mọi thời gian, bạn sẽ không chỉ nhận được một chuỗi không-rất-ngẫu nhiên, bạn sẽ thực sự nhận được số lượng lại tương tự từ rand()
mỗi lần. (Có thể không phải là cùng một số bạn đã trao srand
, nhưng cùng một số trở lại từ rand
nhiều lần.)
Vì vậy, tốt nhất bạn có thể làm là cắt bộ bài một lần , nghĩa là, gọi srand()
một lần, vào đầu chương trình của bạn, với một n
điều đó là ngẫu nhiên hợp lý, để bạn sẽ bắt đầu ở một nơi ngẫu nhiên khác trong bộ bài lớn mỗi lần chương trình chạy. Với rand()
, đó thực sự là điều tốt nhất bạn có thể làm.
[Tái bút Vâng, tôi biết, trong cuộc sống thực, khi bạn mua một bộ bài hoàn toàn mới, nó thường theo thứ tự, không phải thứ tự ngẫu nhiên. Để sự tương tự ở đây hoạt động, tôi đang tưởng tượng rằng mỗi bộ bài bạn mua từ cửa hàng trò chơi có thứ tự dường như ngẫu nhiên, nhưng cùng một thứ tự dường như ngẫu nhiên như mọi bộ bài khác mà bạn mua từ cùng một cửa hàng đó. Sắp xếp giống như bộ bài xáo trộn giống hệt bộ bài mà họ sử dụng trong các giải đấu cầu.]
Lý do là srand()
đặt trạng thái ban đầu của trình tạo ngẫu nhiên và tất cả các giá trị mà trình tạo tạo ra chỉ "đủ ngẫu nhiên" nếu bạn không tự mình chạm vào trạng thái ở giữa.
Ví dụ bạn có thể làm:
int getRandomValue()
{
srand(time(0));
return rand();
}
và sau đó nếu bạn gọi hàm đó lặp đi lặp lại để time()
trả về các giá trị giống nhau trong các lệnh gọi liền kề, bạn chỉ nhận được cùng một giá trị được tạo ra - đó là do thiết kế.
Như đã thấy, một giải pháp đơn giản hơn để sử dụng srand()
tạo các hạt giống khác nhau cho các phiên bản ứng dụng chạy cùng một giây.
srand(time(NULL)-getpid());
Phương pháp này làm cho hạt giống của bạn rất gần với ngẫu nhiên vì không có cách nào để đoán lúc nào chuỗi của bạn bắt đầu và pid cũng sẽ khác.
srand gieo hạt tạo số giả ngẫu nhiên. Nếu bạn gọi nó nhiều lần, bạn sẽ gửi lại RNG. Và nếu bạn gọi nó với cùng một đối số, nó sẽ khởi động lại cùng một chuỗi.
Để chứng minh điều đó, nếu bạn làm điều gì đó đơn giản như:
#include <cstdlib>
#include <cstdio>
int main() {
for(int i = 0; i != 100; ++i) {
srand(0);
printf("%d\n", rand());
}
}
bạn sẽ thấy cùng một số được in 100 lần.
1 \ Dường như mỗi khi rand () chạy, nó sẽ đặt một hạt giống mới cho rand () tiếp theo.
2 \ Nếu srand () chạy nhiều lần, vấn đề là nếu hai lần chạy xảy ra trong một giây (thời gian (NULL) không thay đổi), thì rand () tiếp theo sẽ giống như rand () ngay sau lần trước srand ().
srand()
nhiều lần với cùng một hạt giống sẽ dẫn đến các giá trị giống hệt nhau được trả về rand()
.