หมายเลขสุ่มที่ไม่ซ้ำ (ไม่ใช่ซ้ำ) ใน O (1)?


179

ฉันต้องการสร้างตัวเลขสุ่มที่ไม่ซ้ำกันระหว่าง 0 ถึง 1,000 ที่ไม่เคยทำซ้ำ (เช่น 6 ไม่ปรากฏขึ้นสองครั้ง) แต่นั่นไม่ได้หันไปหาสิ่งที่ต้องการการค้นหา O (N) ของค่าก่อนหน้า เป็นไปได้ไหม


4
นี่ไม่ใช่คำถามเดียวกันกับstackoverflow.com/questions/158716/…
jk

2
เป็น 0 ระหว่าง 0 ถึง 1,000
Pete Kirkham

4
หากคุณกำลังห้ามสิ่งใด ๆ ในช่วงเวลาที่คงที่ (เช่นO(n)ในเวลาหรือหน่วยความจำ) คำตอบด้านล่างหลายคำนั้นผิดรวมถึงคำตอบที่ยอมรับ
jww

คุณจะสุ่มไพ่ได้อย่างไร?
พันเอก Panic

9
คำเตือน! คำตอบหลายข้อที่ให้ไว้ด้านล่างนี้จะไม่สร้างลำดับสุ่มอย่างแท้จริงช้ากว่า O (n) หรือมีข้อบกพร่อง! codinghorror.com/blog/archives/001015.htmlเป็นการอ่านที่จำเป็นก่อนที่คุณจะใช้สิ่งเหล่านี้หรือพยายามปรุงด้วยตัวคุณเอง!
ivan_pozdeev

คำตอบ:


247

เริ่มต้นอาร์เรย์ของจำนวนเต็ม 1001 ด้วยค่า 0-1000 และตั้งค่าตัวแปร max เป็นดัชนีสูงสุดปัจจุบันของอาร์เรย์ (เริ่มต้นด้วย 1,000) เลือกตัวเลขสุ่ม r ระหว่าง 0 ถึงสูงสุดสลับหมายเลขที่ตำแหน่ง r ด้วยหมายเลขที่ตำแหน่งสูงสุดและส่งกลับหมายเลขตอนนี้ที่ตำแหน่งสูงสุด ลดสูงสุด 1 และดำเนินการต่อ เมื่อ max เป็น 0 ให้ตั้งค่า max กลับเป็นขนาดของ array - 1 แล้วเริ่มต้นใหม่อีกครั้งโดยไม่ต้องกำหนดค่าเริ่มต้นใหม่

อัปเดต: แม้ว่าฉันจะใช้วิธีนี้ด้วยตัวเองเมื่อฉันตอบคำถามหลังจากการวิจัยบางอย่างฉันรู้ว่านี่เป็นรุ่นที่ได้รับการแก้ไขของFisher-Yatesหรือที่เรียกว่า Durstenfeld-Fisher-Yates หรือ Knuth-Fisher-Yates เนื่องจากคำอธิบายอาจเป็นเรื่องยากที่จะติดตามฉันได้ให้ตัวอย่างด้านล่าง (ใช้ 11 องค์ประกอบแทน 1001):

Array เริ่มต้นด้วยองค์ประกอบ 11 ค่าเริ่มต้นไปยังอาร์เรย์ [n] = n ค่าสูงสุดเริ่มต้นที่ 10:

+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|
+--+--+--+--+--+--+--+--+--+--+--+
                                ^
                               max    

ที่การวนซ้ำแต่ละครั้งสุ่มหมายเลข r ถูกเลือกระหว่าง 0 ถึงสูงสุดอาร์เรย์ [r] และอาร์เรย์ [max] จะถูกสลับกลับอาร์เรย์ใหม่ [สูงสุด] จะถูกส่งคืนและสูงสุดจะลดลง:

max = 10, r = 3
           +--------------------+
           v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 7| 8| 9| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 9, r = 7
                       +-----+
                       v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 1| 2|10| 4| 5| 6| 9| 8| 7: 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 8, r = 1
     +--------------------+
     v                    v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 5| 6| 9| 1: 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

max = 7, r = 5
                 +-----+
                 v     v
+--+--+--+--+--+--+--+--+--+--+--+
| 0| 8| 2|10| 4| 9| 6| 5: 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

...

หลังจากการวนซ้ำ 11 ครั้งตัวเลขทั้งหมดในอาเรย์ได้ถูกเลือกสูงสุด == 0 และองค์ประกอบอาเรย์จะถูกสับ:

+--+--+--+--+--+--+--+--+--+--+--+
| 4|10| 8| 6| 2| 0| 9| 5| 1| 7| 3|
+--+--+--+--+--+--+--+--+--+--+--+

ณ จุดนี้สูงสุดสามารถรีเซ็ตเป็น 10 และกระบวนการสามารถดำเนินการต่อ


6
โพสต์ของเจฟฟ์ในสับบ่งชี้ว่านี่จะไม่กลับตัวเลขสุ่มที่ดี .. codinghorror.com/blog/archives/001015.html
โปร

14
@ Peter Rounce: ฉันคิดว่าไม่ ดูเหมือนว่าฉันจะชอบอัลกอริทึม Fisher Yates ซึ่งอ้างถึงในโพสต์ของ Jeff (ในฐานะคนดี)
Brent.Longborough

3
@ โรเบิร์ต: ฉันแค่อยากจะชี้ให้เห็นว่ามันไม่ได้ผลิตเช่นเดียวกับในชื่อของคำถาม "ตัวเลขสุ่มที่ไม่ซ้ำกันใน O (1)"
ชาร์ลส์

3
@mikera: เห็นด้วยแม้ว่าในทางเทคนิคหากคุณใช้จำนวนเต็มขนาดคงที่รายการทั้งหมดสามารถสร้างได้ใน O (1) (ด้วยค่าคงที่ขนาดใหญ่ ได้แก่ 2 ^ 32) นอกจากนี้เพื่อวัตถุประสงค์ในทางปฏิบัติคำจำกัดความของ "random" นั้นสำคัญถ้าคุณต้องการใช้เอนโทรปีของระบบขีด จำกัด คือการคำนวณของบิตสุ่มแทนที่จะคำนวณเองและในกรณีนั้น n log n นั้นเกี่ยวข้อง อีกครั้ง แต่ในกรณีที่เป็นไปได้ว่าคุณจะใช้ (เทียบเท่า) / dev / urandom มากกว่า / dev / random คุณกลับไปที่ 'จริง' O (n)
ชาร์ลส์

4
ฉันสับสนนิดหน่อยความจริงที่ว่าคุณไม่ต้องทำNซ้ำ (11 ในตัวอย่างนี้) เพื่อให้ได้ผลลัพธ์ตามที่ต้องการทุกครั้งหมายความว่าO(n)อย่างไร เนื่องจากคุณต้องทำNซ้ำเพื่อรับN!ชุดค่าผสมจากสถานะเริ่มต้นเดียวกันมิฉะนั้นเอาต์พุตของคุณจะเป็นหนึ่งในสถานะ N เท่านั้น
Seph

71

คุณสามารถทำได้:

  1. สร้างรายการ 0..1000
  2. สลับรายการ (ดูFisher-Yates shuffleเพื่อดูวิธีการทำเช่นนี้)
  3. ส่งคืนตัวเลขตามลำดับจากรายการที่สับ

ดังนั้นสิ่งนี้ไม่ต้องการการค้นหาค่าเก่าในแต่ละครั้ง แต่ยังคงต้องใช้ O (N) สำหรับการสลับครั้งแรก แต่เมื่อนิลส์ชี้ให้เห็นในความคิดเห็นนี่เป็นค่าตัดจำหน่าย O (1)


5
@ Just Some Guy N = 1,000 ดังนั้นคุณกำลังบอกว่ามันคือ O (N / N) ซึ่งก็คือ O (1)
Guvante

1
หากการแทรกแต่ละครั้งลงในอาร์เรย์แบบสับเป็นการดำเนินการจากนั้นหลังจากแทรกค่า 1 คุณจะได้รับ 1 ค่าสุ่ม 2 สำหรับ 2 ค่าและอื่น ๆ n สำหรับ n ค่า ใช้การดำเนินการ n เพื่อสร้างรายการดังนั้นอัลกอริทึมทั้งหมดคือ O (n) หากคุณต้องการค่าสุ่ม 1,000,000 ค่าจะต้องใช้ 1,000,000 ops
Kibbee

3
ลองคิดดูด้วยวิธีนี้ถ้ามันเป็นเวลาคงที่มันจะใช้เวลาเท่ากันสำหรับตัวเลขสุ่ม 10 ตัวเช่นเดียวกับค่าหมื่นล้าน แต่เนื่องจากการสับเปลี่ยนการใช้ O (n) เรารู้ว่านี่ไม่เป็นความจริง
Kibbee

1
การดำเนินการนี้ใช้เวลาในการตัดจำหน่ายจริง O (log n) เนื่องจากคุณต้องสร้างบิต n lg n แบบสุ่ม
ชาร์ลส์

2
และตอนนี้ฉันมีเหตุผลทั้งหมดที่จะทำ! meta.stackoverflow.com/q/252503/13
Chris Jester-Young

60

ใช้สูงสุดเป็น Linear ข้อเสนอแนะกะสมัครสมาชิก

มันสามารถใช้งานได้ในไม่กี่บรรทัดของ C และที่รันไทม์ทำมากกว่าสองสามทดสอบ / สาขาเพิ่มเล็กน้อยและขยับเล็กน้อย มันไม่ใช่การสุ่ม แต่มันคนโง่ที่สุด


12
"มันไม่ใช่การสุ่ม แต่มันคนโง่ที่สุด" ที่ใช้กับเครื่องกำเนิดเลขสุ่มทั้งหมดและคำตอบที่เป็นไปได้ทั้งหมดสำหรับคำถามนี้ แต่คนส่วนใหญ่จะไม่คิดถึงมัน ดังนั้นถนัดบันทึกนี้อาจจะส่งผลให้ upvotes เพิ่มเติม ...
f3lix

3
@bobobobo: หน่วยความจำ O (1) คือสาเหตุ
Ash

3
Nit: มันเป็นหน่วยความจำ O (log N)
พอลฮันชิน

2
เมื่อใช้วิธีการนั้นคุณจะสร้างตัวเลขได้อย่างไรว่ามีค่าระหว่าง 0 ถึง 800000 บางคนอาจใช้ LFSR ซึ่งช่วงเวลาคือ 1048575 (2 ^ 20 - 1) และรับหมายเลขถัดไปหากจำนวนอยู่นอกช่วง แต่สิ่งนี้จะไม่มีประสิทธิภาพ
tigrou

1
ในฐานะที่เป็น LFSR สิ่งนี้จะไม่สร้างลำดับการกระจายอย่างสม่ำเสมอ:ลำดับทั้งหมดที่จะสร้างขึ้นจะถูกกำหนดโดยองค์ประกอบแรก
ivan_pozdeev

21

คุณสามารถใช้เป็น Linear congruential ปั่นไฟ โดยที่m(โมดูลัส) จะเป็นนายกที่ใกล้เคียงที่สุดที่ใหญ่กว่า 1,000 เมื่อคุณได้รับตัวเลขจากช่วงเพียงแค่รับคนต่อไป ลำดับจะทำซ้ำเมื่อองค์ประกอบทั้งหมดเกิดขึ้นและคุณไม่จำเป็นต้องใช้ตาราง ระวังข้อเสียของเครื่องกำเนิดไฟฟ้านี้ด้วย (รวมถึงการขาดการสุ่ม)


1
1009 เป็นนายกคนแรกหลังจาก 1,000.
Teepeemm

LCG มีความสัมพันธ์สูงระหว่างตัวเลขที่ต่อเนื่องกันดังนั้นการรวมกันจะไม่ได้รับการสุ่มค่อนข้างมาก (เช่นตัวเลขที่อยู่ไกลกว่าkลำดับที่แยกกันจะไม่เกิดขึ้นพร้อมกัน)
ivan_pozdeev

m ควรเป็นจำนวนขององค์ประกอบ 1001 (1000 + 1 สำหรับศูนย์) และคุณอาจใช้ Next = (1002 * ปัจจุบัน + 757) mod 1001;
Max Abramovich

21

คุณสามารถใช้การเข้ารหัสรูปแบบ - รักษาเพื่อเข้ารหัสตัวนับ ตัวนับของคุณเริ่มจาก 0 ขึ้นไปและการเข้ารหัสใช้คีย์ที่คุณเลือกเพื่อเปลี่ยนเป็นค่าสุ่มและความกว้างที่คุณต้องการ เช่นสำหรับตัวอย่างในคำถามนี้: radix 10, width 3

โดยทั่วไปยันต์บล็อกจะมีขนาดบล็อกคงที่เช่น 64 หรือ 128 บิต แต่การเข้ารหัส Format-Preserving Encryption ช่วยให้คุณใช้รหัสมาตรฐานเช่น AES และสร้างรหัสความกว้างขนาดเล็กลงได้ไม่ว่าคุณต้องการ Radix และ width ใดก็ตามด้วยอัลกอริทึมที่ยังคงแข็งแกร่งในการเข้ารหัส

ไม่รับประกันว่าจะไม่มีการชน (เนื่องจากอัลกอริธึมการเข้ารหัสสร้างการแมป 1: 1) นอกจากนี้ยังสามารถย้อนกลับได้ (การทำแผนที่สองทาง) ดังนั้นคุณสามารถใช้หมายเลขผลลัพธ์และกลับไปที่ค่าตัวนับที่คุณเริ่มต้นด้วย

เทคนิคนี้ไม่จำเป็นต้องมีหน่วยความจำในการจัดเก็บอาเรย์แบบสับเป็นต้นซึ่งสามารถเป็นประโยชน์ในระบบที่มีหน่วยความจำ จำกัด

AES-FFXเป็นหนึ่งในวิธีการมาตรฐานที่เสนอเพื่อบรรลุเป้าหมายนี้ ผมได้ทดลองกับบางรหัสหลามขั้นพื้นฐานซึ่งจะขึ้นอยู่กับความคิด AES-FFX ถึงแม้จะไม่ได้อย่างเต็มที่ conformant-- เห็นรหัสหลามที่นี่ เช่นสามารถเข้ารหัสตัวนับให้เป็นตัวเลขทศนิยม 7 หลักแบบสุ่มหรือตัวเลข 16 บิต นี่คือตัวอย่างของ radix 10 ความกว้าง 3 (เพื่อให้ตัวเลขระหว่าง 0 ถึง 999 รวม) ตามคำถามที่ระบุ:

000   733
001   374
002   882
003   684
004   593
005   578
006   233
007   811
008   072
009   337
010   119
011   103
012   797
013   257
014   932
015   433
...   ...

หากต้องการรับลำดับการสุ่มหลอกที่ไม่ซ้ำให้เปลี่ยนรหัสการเข้ารหัส แต่ละคีย์เข้ารหัสสร้างลำดับหลอกแบบไม่ซ้ำที่ไม่ซ้ำกัน


นี่คือการทำแผนที่แบบง่าย ๆ ดังนั้นจึงไม่แตกต่างจาก LCG และ LFSR กับ kinks ที่เกี่ยวข้องทั้งหมด (เช่นค่าที่มากกว่าkในลำดับที่ไม่สามารถเกิดขึ้นได้ด้วยกัน)
ivan_pozdeev

@ivan_pozdeev: ฉันมีปัญหาในการทำความเข้าใจความหมายของความคิดเห็นของคุณ คุณสามารถอธิบายสิ่งที่ผิดพลาดกับการทำแผนที่นี้คืออะไร "ข้อผิดพลาดที่เกี่ยวข้องทั้งหมด" และอะไรคือสิ่งที่k?
Craig McQueen

"การเข้ารหัส" ทั้งหมดที่มีประสิทธิภาพทำที่นี่คือแทนที่ลำดับ1,2,...,Nด้วยลำดับของตัวเลขเดียวกันในบางอื่น ๆ แต่ยังคงที่สั่ง ตัวเลขจะถูกดึงออกมาจากลำดับนี้ทีละตัว kคือจำนวนค่าที่เลือก (OP ไม่ได้ระบุตัวอักษรดังนั้นฉันต้องแนะนำหนึ่งตัว)
ivan_pozdeev

3
@ivan_pozdeev ไม่ใช่กรณีที่ FPE ต้องใช้การจับคู่แบบคงที่เฉพาะเจาะจงหรือ "ชุดค่าผสมที่ส่งคืนถูกกำหนดโดยตัวเลขแรก" เนื่องจากพารามิเตอร์การกำหนดค่ามีขนาดใหญ่กว่าขนาดของหมายเลขแรก (ซึ่งมีเพียงพันสถานะเท่านั้น) จึงควรมีหลายลำดับที่เริ่มต้นด้วยค่าเริ่มต้นเดียวกันจากนั้นดำเนินการกับค่าที่ตามมาที่แตกต่างกัน เครื่องกำเนิดที่เหมือนจริงใด ๆ จะล้มเหลวในการครอบคลุมพื้นที่ที่เป็นไปได้ทั้งหมดของการเรียงสับเปลี่ยน มันไม่คุ้มที่จะยกระดับโหมดความล้มเหลวนั้นเมื่อ OP ไม่ได้ถาม
sh1

4
+1 เมื่อนำไปใช้อย่างถูกต้องการใช้รหัสบล็อกที่ปลอดภัยกับคีย์ที่เลือกแบบสุ่มลำดับที่สร้างโดยใช้วิธีนี้จะไม่สามารถคำนวณได้จากการสุ่มแบบสุ่มจริง กล่าวคือไม่มีวิธีแยกเอาต์พุตของวิธีนี้จากการสุ่มแบบสุ่มจริงเร็วกว่าการทดสอบคีย์ cipher key ที่เป็นไปได้ทั้งหมดและดูว่ามีวิธีใดในการสร้างเอาต์พุตเดียวกัน สำหรับรหัสที่มีคีย์สเปซแบบ 128 บิตนี่อาจเกินพลังการคำนวณที่มนุษย์มีอยู่ในปัจจุบัน ด้วยปุ่ม 256 บิตมันจะคงอยู่ตลอดไป
Ilmari Karonen

7

สำหรับตัวเลขที่ต่ำเช่น 0 ... 1,000 การสร้างรายการที่มีตัวเลขทั้งหมดและการสับเป็นแบบตรงไปข้างหน้า แต่ถ้าชุดตัวเลขที่จะดึงมีขนาดใหญ่มากก็มีอีกวิธีที่สวยงาม: คุณสามารถสร้างการเรียงสับเปลี่ยนหลอกเทียมโดยใช้คีย์และฟังก์ชันแฮชการเข้ารหัสลับ ดู C ++ ต่อไปนี้ - ตัวอย่างรหัสหลอก ish:

unsigned randperm(string key, unsigned bits, unsigned index) {
  unsigned half1 =  bits    / 2;
  unsigned half2 = (bits+1) / 2;
  unsigned mask1 = (1 << half1) - 1;
  unsigned mask2 = (1 << half2) - 1;
  for (int round=0; round<5; ++round) {
    unsigned temp = (index >> half1);
    temp = (temp << 4) + round;
    index ^= hash( key + "/" + int2str(temp) ) & mask1;
    index = ((index & mask2) << half1) | ((index >> half2) & mask1);
  }
  return index;
}

นี่hashเป็นเพียงฟังก์ชั่นสุ่มหลอกโดยพลการที่แมปสตริงอักขระกับจำนวนเต็มที่ไม่ได้ลงนามขนาดใหญ่ ฟังก์ชั่นrandpermคือการเปลี่ยนแปลงของตัวเลขทั้งหมดภายใน 0 ... pow (2, บิต) -1 สมมติว่าเป็นคีย์คงที่ สิ่งนี้เกิดจากการก่อสร้างเพราะทุกขั้นตอนที่เปลี่ยนแปลงตัวแปรindexนั้นสามารถย้อนกลับได้ นี้เป็นแรงบันดาลใจจากตัวเลข Feistel


เช่นเดียวกับstackoverflow.com/a/16097246/648265ล้มเหลวในการสุ่มสำหรับลำดับเหมือนกัน
ivan_pozdeev

1
@ivan_pozdeev: ตามทฤษฎีแล้วสมมติว่ากำลังการคำนวณไม่มีที่สิ้นสุดใช่ อย่างไรก็ตามสมมติว่าhash()ตามที่ใช้ในรหัสข้างต้นเป็นฟังก์ชั่นหลอกเทียมที่ปลอดภัยการก่อสร้างนี้จะพิสูจน์ได้ (Luby & Rackoff, 1988) ให้การเปลี่ยนรูปแบบหลอกเทียมซึ่งไม่สามารถแยกความแตกต่างจากการสุ่มแบบสุ่มจริง ๆ ค้นหาพื้นที่คีย์ทั้งหมดซึ่งเป็นเลขชี้กำลังในความยาวคีย์ แม้สำหรับปุ่มที่มีขนาดพอเหมาะ (เช่น 128 บิต) สิ่งนี้อยู่นอกเหนือพลังการประมวลผลทั้งหมดบนโลก
Ilmari Karonen

(BTW เพียงเพื่อให้การโต้แย้งนี้มีความเข้มงวดมากขึ้นฉันต้องการแทนที่การhash( key + "/" + int2str(temp) )สร้างเฉพาะกิจด้านบนด้วยHMACซึ่งการรักษาความปลอดภัยสามารถลดลงได้อย่างมากเมื่อเทียบกับฟังก์ชั่นการบีบอัดแฮชพื้นฐานนอกจากนี้การใช้ HMAC อาจทำให้ มีโอกาสน้อยที่คนจะลองใช้สิ่งก่อสร้างนี้โดยไม่ตั้งใจด้วยฟังก์ชั่นแฮชที่ไม่มีการเข้ารหัสลับที่ไม่ปลอดภัย)
Ilmari Karonen

6

คุณสามารถใช้อัลกอริทึมของ Xincrol ของฉันอธิบายไว้ที่นี่:

http://openpatent.blogspot.co.il/2013/04/xincrol-unique-and-random-number.html

นี่เป็นวิธีอัลกอริทึมบริสุทธิ์ในการสร้างตัวเลขสุ่ม แต่ไม่ซ้ำใครโดยไม่ต้องมีอาร์เรย์รายการการเปลี่ยนลำดับหรือโหลดซีพียูจำนวนมาก

เวอร์ชันล่าสุดอนุญาตให้ตั้งช่วงของตัวเลขได้เช่นหากฉันต้องการตัวเลขสุ่มที่ไม่ซ้ำกันในช่วง 0-1073741821

ฉันใช้มันในทางปฏิบัติสำหรับ

  • เครื่องเล่น MP3 ที่เล่นทุกเพลงแบบสุ่ม แต่เพียงครั้งเดียวต่ออัลบั้ม / ไดเรกทอรี
  • เฟรมวิดีโอที่ชาญฉลาดพิกเซลละลายผล (รวดเร็วและราบรื่น)
  • การสร้าง "เสียง" ที่เป็นความลับเหนือภาพสำหรับลายเซ็นและเครื่องหมาย (ซูรินาเม)
  • Data Object IDs สำหรับซีเรียลไลซ์ชัน Java จำนวนมากผ่านฐานข้อมูล
  • การป้องกันบิตหน่วยความจำส่วนใหญ่สามส่วน
  • การเข้ารหัสที่อยู่ + ค่า (ทุกไบต์ไม่เพียงเข้ารหัส แต่ย้ายไปยังตำแหน่งเข้ารหัสใหม่ในบัฟเฟอร์) สิ่งนี้ทำให้เพื่อนคริปโตแนลไลเซชันเป็นบ้าจริง ๆ กับฉัน :-)
  • การแปลงข้อความธรรมดาเป็นธรรมดาการเข้ารหัสข้อความสำหรับ SMS อีเมล ฯลฯ
  • เครื่องคิดเลข Texas Hold'em Poker ของฉัน (THC)
  • เกมของฉันหลายเกมสำหรับการจำลอง "การสับ" การจัดอันดับ
  • มากกว่า

มันเปิดฟรี ให้มันลอง...


วิธีการนั้นสามารถใช้กับค่าทศนิยมได้หรือไม่เช่นการแปลงตัวนับทศนิยม 3 หลักเพื่อให้ได้ผลลัพธ์ทศนิยม 3 หลักเสมอ
Craig McQueen

ตัวอย่างของอัลกอริธึมXorshiftเป็น LFSR พร้อม kinks ที่เกี่ยวข้องทั้งหมด (เช่นค่าที่มากกว่าkกันในลำดับไม่สามารถเกิดขึ้นพร้อมกันได้)
ivan_pozdeev

5

คุณไม่จำเป็นต้องมีอาร์เรย์เพื่อแก้ปัญหานี้

คุณต้องมี bitmask และตัวนับ

เริ่มต้นตัวนับเป็นศูนย์และเพิ่มขึ้นเมื่อมีการโทรติดต่อกัน แฮคเกอร์นับด้วย bitmask (สุ่มเลือกเมื่อเริ่มต้นหรือคงที่) เพื่อสร้างหมายเลข psuedorandom หากคุณไม่สามารถมีตัวเลขที่เกิน 1,000 อย่าใช้ bitmask ที่กว้างกว่า 9 บิต (กล่าวอีกนัยหนึ่ง bitmask เป็นจำนวนเต็มไม่สูงกว่า 511)

ตรวจสอบให้แน่ใจว่าเมื่อตัวนับผ่าน 1,000 คุณตั้งค่าใหม่เป็นศูนย์ ในเวลานี้คุณสามารถเลือก bitmask แบบสุ่ม - หากคุณต้องการ - เพื่อสร้างชุดหมายเลขเดียวกันในลำดับที่แตกต่างกัน


2
นั่นจะหลอกคนน้อยกว่า LFSR
starblue

"bitmask" ภายใน 512 ... 1023 ก็ใช้ได้เช่นกัน สำหรับการสุ่มที่ผิดพลาดมากกว่านี้ให้ดูคำตอบของฉัน :-)
sellibitze

โดยพื้นฐานแล้วเทียบเท่ากับstackoverflow.com/a/16097246/648265นอกจากนี้ยังล้มเหลวในการสุ่มสำหรับลำดับ
ivan_pozdeev

4

ฉันคิดว่าเครื่องกำเนิดเชิงเส้นตรงนั้นจะเป็นทางออกที่ง่ายที่สุด

ป้อนคำอธิบายรูปภาพที่นี่

และมีเพียง 3 ข้อ จำกัด ใน, และค่า

  1. mและ cค่อนข้างดี
  2. a-1สามารถหารได้ด้วยปัจจัยหลักทั้งหมดของ m
  3. a-1หารด้วย 4ถ้า mหารด้วย 4

PSวิธีการที่ถูกกล่าวถึงแล้ว แต่โพสต์มีข้อสันนิษฐานผิดเกี่ยวกับค่าคงที่ ค่าคงที่ด้านล่างควรทำงานได้ดีสำหรับกรณีของคุณ

ในกรณีของคุณคุณอาจใช้a = 1002, c = 757,m = 1001

X = (1002 * X + 757) mod 1001

3

ต่อไปนี้เป็นโค้ดที่ฉันพิมพ์ขึ้นซึ่งใช้ตรรกะของโซลูชันแรก ฉันรู้ว่านี่คือ "ผู้ไม่เชื่อเรื่องภาษา" แต่ต้องการนำเสนอสิ่งนี้เป็นตัวอย่างใน C # ในกรณีที่ทุกคนกำลังมองหาวิธีแก้ปัญหาที่ใช้งานได้จริง

// Initialize variables
Random RandomClass = new Random();
int RandArrayNum;
int MaxNumber = 10;
int LastNumInArray;
int PickedNumInArray;
int[] OrderedArray = new int[MaxNumber];      // Ordered Array - set
int[] ShuffledArray = new int[MaxNumber];     // Shuffled Array - not set

// Populate the Ordered Array
for (int i = 0; i < MaxNumber; i++)                  
{
    OrderedArray[i] = i;
    listBox1.Items.Add(OrderedArray[i]);
}

// Execute the Shuffle                
for (int i = MaxNumber - 1; i > 0; i--)
{
    RandArrayNum = RandomClass.Next(i + 1);         // Save random #
    ShuffledArray[i] = OrderedArray[RandArrayNum];  // Populting the array in reverse
    LastNumInArray = OrderedArray[i];               // Save Last Number in Test array
    PickedNumInArray = OrderedArray[RandArrayNum];  // Save Picked Random #
    OrderedArray[i] = PickedNumInArray;             // The number is now moved to the back end
    OrderedArray[RandArrayNum] = LastNumInArray;    // The picked number is moved into position
}

for (int i = 0; i < MaxNumber; i++)                  
{
    listBox2.Items.Add(ShuffledArray[i]);
}

3

วิธีการนี้ให้ผลที่ดีเมื่อขีด จำกัดสูงและคุณต้องการสร้างตัวเลขสุ่มเพียงไม่กี่ตัว

#!/usr/bin/perl

($top, $n) = @ARGV; # generate $n integer numbers in [0, $top)

$last = -1;
for $i (0 .. $n-1) {
    $range = $top - $n + $i - $last;
    $r = 1 - rand(1.0)**(1 / ($n - $i));
    $last += int($r * $range + 1);
    print "$last ($r)\n";
}

โปรดทราบว่าตัวเลขจะถูกสร้างตามลำดับจากน้อยไปมาก แต่คุณสามารถสลับได้หลังจากนั้น


เนื่องจากสิ่งนี้สร้างชุดค่าผสมมากกว่าการเรียงสับเปลี่ยนจึงเหมาะสำหรับstackoverflow.com/questions/2394246//
ivan_pozdeev

1
การทดสอบแสดงให้เห็นว่านี้มีอคติต่อตัวเลขที่ต่ำกว่าความน่าจะเป็นวัดสำหรับตัวอย่าง 2M กับมีดังนี้:(top,n)=(100,10) (0.01047705, 0.01044825, 0.01041225, ..., 0.0088324, 0.008723, 0.00863635)ฉันทดสอบใน Python ความแตกต่างทางคณิตศาสตร์เล็กน้อยอาจมีบทบาทที่นี่ (ฉันแน่ใจว่าการคำนวณrทั้งหมดเป็นทศนิยม)
ivan_pozdeev

ใช่เพื่อให้วิธีนี้ทำงานอย่างถูกต้องขีด จำกัด บนต้องมากกว่าจำนวนค่าที่จะแตกออกมาอย่างมาก
ซัลวา

มันจะไม่ทำงาน "ถูกต้อง" แม้ว่า"ขีด จำกัด ด้านบน [คือ] ใหญ่กว่าจำนวนค่ามาก" ความน่าจะเป็นยังคงไม่สม่ำเสมอเพียงแค่มาร์จิ้นที่น้อยลง
ivan_pozdeev

2

คุณสามารถใช้ตัวสร้างตัวเลขสุ่มหลอกได้ดีด้วย 10 บิตและทิ้ง 1001 ถึง 1,023 ทิ้ง 0 ถึง 1,000

จากที่นี่เราได้รับการออกแบบสำหรับ PRNG 10 บิต ..

  • 10 บิตข้อเสนอแนะพหุนาม x ^ 10 + x ^ 7 + 1 (ช่วง 1023)

  • ใช้ Galois LFSR เพื่อรับรหัสที่รวดเร็ว


@Phob ไม่ใช่ที่จะไม่เกิดขึ้นเพราะโดยทั่วไป PRNG 10 บิตซึ่งขึ้นอยู่กับ Linear Feedback Shift Register จะทำจากโครงสร้างที่ถือว่าค่าทั้งหมด (ยกเว้นหนึ่ง) หนึ่งครั้งก่อนที่จะกลับไปที่ค่าแรก กล่าวอีกนัยหนึ่งมันจะเลือก 1001 เพียงครั้งเดียวในระหว่างรอบ
Nuoji

1
@Phob ประเด็นทั้งหมดของคำถามนี้คือการเลือกหมายเลขแต่ละครั้งอย่างแน่นอน แล้วคุณบ่นว่า 1001 จะไม่เกิดขึ้นสองครั้งติดต่อกัน? LFSR ที่มีการแพร่กระจายที่ดีที่สุดจะข้ามตัวเลขทั้งหมดในพื้นที่ของมันในแบบสุ่มหลอกแล้วรีสตาร์ทรอบ มันไม่ได้ใช้เป็นฟังก์ชั่นสุ่มปกติ เมื่อใช้เป็นแบบสุ่มเรามักจะใช้ชุดย่อยของบิตเท่านั้น อ่านนิดหน่อยแล้วมันจะเข้าท่า
Nuoji

1
ปัญหาเดียวคือ LFSR ที่กำหนดมีเพียงหนึ่งลำดับเท่านั้นดังนั้นจึงให้ความสัมพันธ์ที่ดีระหว่างตัวเลขที่เลือก - โดยเฉพาะอย่างยิ่งไม่สร้างชุดค่าผสมที่เป็นไปได้ทั้งหมด
ivan_pozdeev

2
public static int[] randN(int n, int min, int max)
{
    if (max <= min)
        throw new ArgumentException("Max need to be greater than Min");
    if (max - min < n)
        throw new ArgumentException("Range needs to be longer than N");

    var r = new Random();

    HashSet<int> set = new HashSet<int>();

    while (set.Count < n)
    {
        var i = r.Next(max - min) + min;
        if (!set.Contains(i))
            set.Add(i);
    }

    return set.ToArray();
}

N ตัวเลขสุ่มที่ไม่ใช่การทำซ้ำจะมีความซับซ้อน O (n) ตามที่ต้องการ
หมายเหตุ: สุ่มควรเป็นแบบคงที่โดยใช้ความปลอดภัยของเธรด


O (n ^ 2) เนื่องจากจำนวนครั้งในการลองใหม่เป็นสัดส่วนโดยเฉลี่ยกับจำนวนขององค์ประกอบที่เลือกไว้
ivan_pozdeev

ลองคิดดูถ้าคุณเลือก min = 0 max = 10,000000 และ N = 5 ลองใหม่ ~ = 0 ไม่ว่าจะเลือกกี่รายการ แต่ใช่คุณมีประเด็นที่ถ้า max-min มีขนาดเล็ก o (N) จะหยุดพัก
Erez Robinson

ถ้า N << (max-min) มันก็ยังคงเป็นสัดส่วนมันก็แค่ค่าสัมประสิทธิ์มีขนาดเล็กมาก และสัมประสิทธิ์ไม่สำคัญสำหรับการประมาณเชิงซีมโทติค
ivan_pozdeev

นี่ไม่ใช่ O (n) ทุกครั้งที่ชุดประกอบด้วยค่านี่คือและวงพิเศษ
paparazzo

2

สมมติว่าคุณต้องการดูรายการที่มีการสับซ้ำหลายครั้งโดยไม่O(n)ล่าช้าในแต่ละครั้งที่คุณเริ่มต้นเพื่อสลับอีกครั้งในกรณีนี้เราสามารถทำได้:

  1. สร้าง 2 รายการ A และ B ที่มี 0 ถึง 1,000 ใช้2nพื้นที่

  2. รายการสลับ A ที่ใช้ Fisher-Yates ใช้nเวลานาน

  3. เมื่อวาดตัวเลขให้ทำแบบสุ่ม 1 ขั้นตอนกับ Fisher-Yates ในรายการอื่น

  4. เมื่อเคอร์เซอร์อยู่ที่ท้ายรายการให้เปลี่ยนเป็นรายการอื่น

preprocess

cursor = 0

selector = A
other    = B

shuffle(A)

วาด

temp = selector[cursor]

swap(other[cursor], other[random])

if cursor == N
then swap(selector, other); cursor = 0
else cursor = cursor + 1

return temp

ไม่จำเป็นต้องเก็บ 2 รายการ - หรือใช้รายการก่อนที่จะหมด Fisher-Yates ให้ผลการสุ่มอย่างสม่ำเสมอจากสถานะเริ่มต้นใด ๆ ดูstackoverflow.com/a/158742/648265สำหรับคำอธิบาย
ivan_pozdeev

@ivan_pozdeev ใช่มันเป็นผลลัพธ์เดียวกัน แต่ความคิดของฉันที่นี่คือทำให้มันตัดจำหน่าย O (1) โดยการสลับส่วนของการวาดภาพ
Khaled.K

คุณไม่เข้าใจ คุณไม่จำเป็นต้องรีเซ็ตรายการเลยก่อนที่จะสับอีกครั้ง สับจะผลิตผลเช่นเดียวกับการสับ[1,3,4,5,2] [1,2,3,4,5]
ivan_pozdeev

2

คำถามคุณสร้างรายการจำนวนเต็ม K ที่ไม่ซ้ำกันอย่างมีประสิทธิภาพระหว่าง 0 ถึงขีด จำกัด บนได้อย่างไรถูกลิงก์เป็นซ้ำได้อย่างไรและถ้าคุณต้องการบางสิ่งที่เป็น O (1) ต่อการสุ่มตัวเลขที่สร้างขึ้น (โดยไม่มี O (n) ราคาเริ่มต้น)) มีการบิดคำตอบที่ยอมรับได้ง่าย

สร้างแผนที่ที่ไม่มีการเรียงลำดับว่างเปล่า (แผนที่ที่ว่างเปล่าที่สั่งจะใช้ O (ล็อก k) ต่อองค์ประกอบ) จากจำนวนเต็มถึงจำนวนเต็ม - แทนที่จะใช้อาร์เรย์ที่กำหนดค่าเริ่มต้น ตั้งค่าสูงสุดเป็น 1,000 หากนั่นคือค่าสูงสุด

  1. เลือกตัวเลขสุ่ม r ระหว่าง 0 ถึงสูงสุด
  2. ตรวจสอบให้แน่ใจว่าทั้งองค์ประกอบของแผนที่ r และ max มีอยู่ในแผนที่ที่ไม่ได้เรียงลำดับ หากไม่มีอยู่ให้สร้างด้วยค่าเท่ากับดัชนี
  3. สลับองค์ประกอบ r และสูงสุด
  4. ส่งคืนองค์ประกอบสูงสุดและค่าลดลงสูงสุด 1 (ถ้าค่าสูงสุดเป็นลบคุณก็ทำเสร็จแล้ว)
  5. กลับไปที่ขั้นตอนที่ 1

ความแตกต่างเพียงอย่างเดียวเมื่อเทียบกับการใช้อาเรย์เริ่มต้นคือการเริ่มต้นขององค์ประกอบถูกเลื่อน / ข้าม - แต่มันจะสร้างตัวเลขเดียวกันแน่นอนจาก PRNG เดียวกัน


1

ตำแหน่งอื่น:

คุณสามารถใช้อาร์เรย์ของธง และเลือกอันถัดไปเมื่อเลือกแล้ว

แต่ระวังหลังจากโทร 1,000 ครั้งฟังก์ชั่นจะไม่สิ้นสุดดังนั้นคุณต้องป้องกัน


อันนี้คือ O (k ^ 2) สิ่งที่มีจำนวนขั้นตอนเพิ่มเติมตามสัดส่วนโดยเฉลี่ยกับจำนวนของค่าที่เลือกจนถึงตอนนี้
ivan_pozdeev

1

นี่คือตัวอย่างโค้ดโคบอลที่คุณสามารถเล่นได้
ฉันสามารถส่งไฟล์ RANDGEN.exe ให้คุณเพื่อให้คุณสามารถเล่นกับมันเพื่อดูว่ามันต้องการหรือไม่

   IDENTIFICATION DIVISION.
   PROGRAM-ID.  RANDGEN as "ConsoleApplication2.RANDGEN".
   AUTHOR.  Myron D Denson.
   DATE-COMPILED.
  * ************************************************************** 
  *  SUBROUTINE TO GENERATE RANDOM NUMBERS THAT ARE GREATER THAN
  *    ZERO AND LESS OR EQUAL TO THE RANDOM NUMBERS NEEDED WITH NO
  *    DUPLICATIONS.  (CALL "RANDGEN" USING RANDGEN-AREA.)
  *     
  *  CALLING PROGRAM MUST HAVE A COMPARABLE LINKAGE SECTION
  *    AND SET 3 VARIABLES PRIOR TO THE FIRST CALL IN RANDGEN-AREA     
  *
  *    FORMULA CYCLES THROUGH EVERY NUMBER OF 2X2 ONLY ONCE. 
  *    RANDOM-NUMBERS FROM 1 TO RANDOM-NUMBERS-NEEDED ARE CREATED 
  *    AND PASSED BACK TO YOU.
  *
  *  RULES TO USE RANDGEN:
  *
  *    RANDOM-NUMBERS-NEEDED > ZERO 
  *     
  *    COUNT-OF-ACCESSES MUST = ZERO FIRST TIME CALLED.
  *         
  *    RANDOM-NUMBER = ZERO, WILL BUILD A SEED FOR YOU
  *    WHEN COUNT-OF-ACCESSES IS ALSO = 0 
  *     
  *    RANDOM-NUMBER NOT = ZERO, WILL BE NEXT SEED FOR RANDGEN
  *    (RANDOM-NUMBER MUST BE <= RANDOM-NUMBERS-NEEDED)       
  *     
  *    YOU CAN PASS RANDGEN YOUR OWN RANDOM-NUMBER SEED
  *     THE FIRST TIME YOU USE RANDGEN.
  *     
  *    BY PLACING A NUMBER IN RANDOM-NUMBER FIELD
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER > ZERO AND 
  *        RANDOM-NUMBER <= RANDOM-NUMBERS-NEEDED
  *       
  *    YOU CAN LET RANDGEN BUILD A SEED FOR YOU
  *     
  *      THAT FOLLOWES THESE SIMPLE RULES:
  *        IF COUNT-OF-ACCESSES = ZERO AND 
  *        RANDOM-NUMBER = ZERO AND 
  *        RANDOM-NUMBER-NEEDED > ZERO  
  *         
  *     TO INSURING A DIFFERENT PATTERN OF RANDOM NUMBERS
  *        A LOW-RANGE AND HIGH-RANGE IS USED TO BUILD
  *        RANDOM NUMBERS.
  *        COMPUTE LOW-RANGE =
  *             ((SECONDS * HOURS * MINUTES * MS) / 3).         
  *        A HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE
  *        AFTER RANDOM-NUMBER-BUILT IS CREATED 
  *        AND IS BETWEEN LOW AND HIGH RANGE
  *        RANDUM-NUMBER = RANDOM-NUMBER-BUILT - LOW-RANGE
  *               
  * **************************************************************         
   ENVIRONMENT DIVISION.
   INPUT-OUTPUT SECTION.
   FILE-CONTROL.
   DATA DIVISION.
   FILE SECTION.
   WORKING-STORAGE SECTION.
   01  WORK-AREA.
       05  X2-POWER                     PIC 9      VALUE 2. 
       05  2X2                          PIC 9(12)  VALUE 2 COMP-3.
       05  RANDOM-NUMBER-BUILT          PIC 9(12)  COMP.
       05  FIRST-PART                   PIC 9(12)  COMP.
       05  WORKING-NUMBER               PIC 9(12)  COMP.
       05  LOW-RANGE                    PIC 9(12)  VALUE ZERO.
       05  HIGH-RANGE                   PIC 9(12)  VALUE ZERO.
       05  YOU-PROVIDE-SEED             PIC X      VALUE SPACE.
       05  RUN-AGAIN                    PIC X      VALUE SPACE.
       05  PAUSE-FOR-A-SECOND           PIC X      VALUE SPACE.   
   01  SEED-TIME.
       05  HOURS                        PIC 99.
       05  MINUTES                      PIC 99.
       05  SECONDS                      PIC 99.
       05  MS                           PIC 99. 
  *
  * LINKAGE SECTION.
  *  Not used during testing  
   01  RANDGEN-AREA.
       05  COUNT-OF-ACCESSES            PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBERS-NEEDED        PIC 9(12) VALUE ZERO.
       05  RANDOM-NUMBER                PIC 9(12) VALUE ZERO.
       05  RANDOM-MSG                   PIC X(60) VALUE SPACE.
  *    
  * PROCEDURE DIVISION USING RANDGEN-AREA.
  * Not used during testing 
  *  
   PROCEDURE DIVISION.
   100-RANDGEN-EDIT-HOUSEKEEPING.
       MOVE SPACE TO RANDOM-MSG. 
       IF RANDOM-NUMBERS-NEEDED = ZERO
         DISPLAY 'RANDOM-NUMBERS-NEEDED ' NO ADVANCING
         ACCEPT RANDOM-NUMBERS-NEEDED.
       IF RANDOM-NUMBERS-NEEDED NOT NUMERIC 
         MOVE 'RANDOM-NUMBERS-NEEDED NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF RANDOM-NUMBERS-NEEDED = ZERO
         MOVE 'RANDOM-NUMBERS-NEEDED = ZERO' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES NOT NUMERIC
         MOVE 'COUNT-OF-ACCESSES NOT NUMERIC' TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF COUNT-OF-ACCESSES GREATER THAN RANDOM-NUMBERS-NEEDED
         MOVE 'COUNT-OF-ACCESSES > THAT RANDOM-NUMBERS-NEEDED'
           TO RANDOM-MSG
           GO TO 900-EXIT-RANDGEN.
       IF YOU-PROVIDE-SEED = SPACE AND RANDOM-NUMBER = ZERO
         DISPLAY 'DO YOU WANT TO PROVIDE SEED  Y OR N: '
           NO ADVANCING
           ACCEPT YOU-PROVIDE-SEED.  
       IF RANDOM-NUMBER = ZERO AND
          (YOU-PROVIDE-SEED = 'Y' OR 'y')
         DISPLAY 'ENTER SEED ' NO ADVANCING
         ACCEPT RANDOM-NUMBER. 
       IF RANDOM-NUMBER NOT NUMERIC
         MOVE 'RANDOM-NUMBER NOT NUMERIC' TO RANDOM-MSG
         GO TO 900-EXIT-RANDGEN.
   200-RANDGEN-DATA-HOUSEKEEPING.      
       MOVE FUNCTION CURRENT-DATE (9:8) TO SEED-TIME.
       IF COUNT-OF-ACCESSES = ZERO
         COMPUTE LOW-RANGE =
                ((SECONDS * HOURS * MINUTES * MS) / 3).
       COMPUTE RANDOM-NUMBER-BUILT = RANDOM-NUMBER + LOW-RANGE.  
       COMPUTE HIGH-RANGE = RANDOM-NUMBERS-NEEDED + LOW-RANGE.
       MOVE X2-POWER TO 2X2.             
   300-SET-2X2-DIVISOR.
       IF 2X2 < (HIGH-RANGE + 1) 
          COMPUTE 2X2 = 2X2 * X2-POWER
           GO TO 300-SET-2X2-DIVISOR.    
  * *********************************************************         
  *  IF FIRST TIME THROUGH AND YOU WANT TO BUILD A SEED.    *
  * ********************************************************* 
       IF COUNT-OF-ACCESSES = ZERO AND RANDOM-NUMBER = ZERO
          COMPUTE RANDOM-NUMBER-BUILT =
                ((SECONDS * HOURS * MINUTES * MS) + HIGH-RANGE).
       IF COUNT-OF-ACCESSES = ZERO        
         DISPLAY 'SEED TIME ' SEED-TIME 
               ' RANDOM-NUMBER-BUILT ' RANDOM-NUMBER-BUILT 
               ' LOW-RANGE  ' LOW-RANGE.          
  * *********************************************     
  *    END OF BUILDING A SEED IF YOU WANTED TO  * 
  * *********************************************               
  * ***************************************************
  * THIS PROCESS IS WHERE THE RANDOM-NUMBER IS BUILT  *  
  * ***************************************************   
   400-RANDGEN-FORMULA.
       COMPUTE FIRST-PART = (5 * RANDOM-NUMBER-BUILT) + 7.
       DIVIDE FIRST-PART BY 2X2 GIVING WORKING-NUMBER 
         REMAINDER RANDOM-NUMBER-BUILT. 
       IF RANDOM-NUMBER-BUILT > LOW-RANGE AND
          RANDOM-NUMBER-BUILT < (HIGH-RANGE + 1)
         GO TO 600-RANDGEN-CLEANUP.
       GO TO 400-RANDGEN-FORMULA.
  * *********************************************     
  *    GOOD RANDOM NUMBER HAS BEEN BUILT        *               
  * *********************************************
   600-RANDGEN-CLEANUP.
       ADD 1 TO COUNT-OF-ACCESSES.
       COMPUTE RANDOM-NUMBER = 
            RANDOM-NUMBER-BUILT - LOW-RANGE. 
  * *******************************************************
  * THE NEXT 3 LINE OF CODE ARE FOR TESTING  ON CONSOLE   *  
  * *******************************************************
       DISPLAY RANDOM-NUMBER.
       IF COUNT-OF-ACCESSES < RANDOM-NUMBERS-NEEDED
        GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.     
   900-EXIT-RANDGEN.
       IF RANDOM-MSG NOT = SPACE
        DISPLAY 'RANDOM-MSG: ' RANDOM-MSG.
        MOVE ZERO TO COUNT-OF-ACCESSES RANDOM-NUMBERS-NEEDED RANDOM-NUMBER. 
        MOVE SPACE TO YOU-PROVIDE-SEED RUN-AGAIN.
       DISPLAY 'RUN AGAIN Y OR N '
         NO ADVANCING.
       ACCEPT RUN-AGAIN.
       IF (RUN-AGAIN = 'Y' OR 'y')
         GO TO 100-RANDGEN-EDIT-HOUSEKEEPING.
       ACCEPT PAUSE-FOR-A-SECOND.
       GOBACK.

1
ฉันไม่มีความคิดว่าสิ่งนี้สามารถตอบสนองความต้องการของผู้ปฏิบัติงานจริงได้หรือไม่
Mac

1

คำตอบส่วนใหญ่ที่นี่ไม่สามารถรับประกันได้ว่าพวกเขาจะไม่ส่งคืนหมายเลขเดิมสองครั้ง นี่คือทางออกที่ถูกต้อง:

int nrrand(void) {
  static int s = 1;
  static int start = -1;
  do {
    s = (s * 1103515245 + 12345) & 1023;
  } while (s >= 1001);
  if (start < 0) start = s;
  else if (s == start) abort();

  return s;
}

ฉันไม่แน่ใจว่ามีการระบุข้อ จำกัด ไว้อย่างดี หนึ่งสมมติว่าหลังจาก 1000 เอาต์พุตอื่น ๆ ค่าจะได้รับอนุญาตให้ทำซ้ำ แต่ที่ไร้เดียงสาอนุญาตให้ 0 ติดตามทันทีหลังจาก 0 ตราบใดที่พวกเขาทั้งสองปรากฏในตอนท้ายและเริ่มชุด 1,000 ในทางกลับกันในขณะที่เป็นไปได้ที่จะรักษาระยะห่าง 1,000 ค่าอื่น ๆ ระหว่างการทำซ้ำการทำเช่นนั้นบังคับให้สถานการณ์ที่ลำดับไกลตัวเองในลักษณะเดียวกันทุกครั้งเพราะไม่มีค่าอื่น ๆ ที่เกิดขึ้นนอกขีด จำกัด นั้น

ต่อไปนี้เป็นวิธีการที่รับประกันอย่างน้อย 500 ค่าอื่น ๆ ก่อนที่จะทำซ้ำค่า:

int nrrand(void) {
  static int h[1001];
  static int n = -1;

  if (n < 0) {
    int s = 1;
    for (int i = 0; i < 1001; i++) {
      do {
        s = (s * 1103515245 + 12345) & 1023;
      } while (s >= 1001);
      /* If we used `i` rather than `s` then our early results would be poorly distributed. */
      h[i] = s;
    }
    n = 0;
  }

  int i = rand(500);
  if (i != 0) {
      i = (n + i) % 1001;
      int t = h[i];
      h[i] = h[n];
      h[n] = t;
  }
  i = h[n];
  n = (n + 1) % 1001;

  return i;
}

นี่คือ LCG เช่นstackoverflow.com/a/196164/648265 แบบไม่สุ่มสำหรับลำดับรวมถึงข้อผิดพลาดอื่น ๆ ที่เกี่ยวข้องเหมือนกัน
ivan_pozdeev

@ivan_pozdeev ของฉันดีกว่า LCG เพราะมันช่วยให้มั่นใจได้ว่ามันจะไม่ส่งคืนซ้ำในการโทร 1001 ครั้ง
sh1

1

เมื่อ N มากกว่า 1,000 และคุณต้องวาดตัวอย่างสุ่ม K คุณสามารถใช้ชุดที่มีตัวอย่างจนถึง สำหรับการจับแต่ละครั้งคุณใช้การสุ่มตัวอย่างการปฏิเสธซึ่งจะเป็นการดำเนินการ "เกือบ" O (1) ดังนั้นเวลาการทำงานทั้งหมดคือเกือบ O (K) พร้อมที่เก็บ O (N)

อัลกอริธึมนี้เกิดการชนกันเมื่อ K ใกล้เคียงกับ N ซึ่งหมายความว่าเวลาทำงานจะแย่กว่า O (K) มาก การแก้ไขอย่างง่ายคือการย้อนกลับตรรกะเพื่อที่ K> N / 2 คุณจะเก็บบันทึกตัวอย่างทั้งหมดที่ยังไม่ถูกดึงออกมา การจับแต่ละครั้งจะลบตัวอย่างออกจากชุดการปฏิเสธ

ปัญหาที่ชัดเจนอื่น ๆ ที่มีการสุ่มตัวอย่างการปฏิเสธคือมันเป็นที่เก็บ O (N) ซึ่งเป็นข่าวร้ายถ้า N อยู่ในพันล้านหรือมากกว่านั้น อย่างไรก็ตามมีอัลกอริทึมที่แก้ปัญหานั้นได้ อัลกอริทึมนี้เรียกว่าอัลกอริทึมของ Vitter หลังจากเป็นนักประดิษฐ์ ขั้นตอนวิธีการอธิบายไว้ที่นี่ ส่วนสำคัญของอัลกอริธึมของ Vitter คือหลังจากการจับสลากแต่ละครั้งคุณคำนวณการข้ามแบบสุ่มโดยใช้การแจกแจงบางตัวซึ่งรับประกันการสุ่มตัวอย่างแบบสม่ำเสมอ


ได้โปรด! วิธีการ Fisher-Yates เสีย คุณเลือกอันแรกที่มีความน่าจะเป็น 1 / N และอันที่สองที่มีความน่าจะเป็น 1 / (N-1)! = 1 / N นี่เป็นวิธีการสุ่มตัวอย่างแบบเอนเอียง! คุณต้องการอัลกอริทึมของ Vittter เพื่อแก้ปัญหาความเอนเอียง
Emanuel Landeholm

0

ฟิชเชอร์เยตส์

for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

จริงๆแล้วมันเป็น O (n-1) เนื่องจากคุณต้องการเพียงหนึ่ง swap สำหรับสองอันสุดท้าย
นี่คือ C #

public static List<int> FisherYates(int n)
{
    List<int> list = new List<int>(Enumerable.Range(0, n));
    Random rand = new Random();
    int swap;
    int temp;
    for (int i = n - 1; i > 0; i--)
    {
        swap = rand.Next(i + 1);  //.net rand is not inclusive
        if(swap != i)  // it can stay in place - if you force a move it is not a uniform shuffle
        {
            temp = list[i];
            list[i] = list[swap];
            list[swap] = temp;
        }
    }
    return list;
}

มีคำตอบอยู่แล้ว แต่ค่อนข้างยืดยาวและไม่ทราบว่าคุณสามารถหยุดได้ที่ 1 (ไม่ใช่ 0)
paparazzo

0

โปรดดูคำตอบของฉันที่ https://stackoverflow.com/a/46807110/8794687

มันเป็นหนึ่งในอัลกอริทึมที่ง่ายที่สุดที่มีความซับซ้อนของเวลาเฉลี่ยO ( s log s ) sแสดงถึงขนาดตัวอย่าง นอกจากนี้ยังมีการเชื่อมโยงบางอย่างไปยังอัลกอริทึมตารางแฮชที่ซับซ้อนอ้างว่าเป็นO ( s )


-1

มีคนโพสต์ "สร้างตัวเลขสุ่มใน excel" ฉันกำลังใช้อุดมคตินี้ สร้างโครงสร้างที่มี 2 ส่วนคือ str.index และ str.ran สำหรับ 10 ตัวเลขสุ่มสร้างอาร์เรย์ของ 10 โครงสร้าง ตั้ง str.index จาก 0 ถึง 9 และ str.ran เป็นจำนวนสุ่มที่แตกต่างกัน

for(i=0;i<10; ++i) {
      arr[i].index = i;
      arr[i].ran   = rand();
}

เรียงลำดับอาร์เรย์ตามค่าใน arr [i] .ran str.index ขณะนี้อยู่ในลำดับแบบสุ่ม ด้านล่างคือรหัส c:

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

struct RanStr { int index; int ran;};
struct RanStr arr[10];

int sort_function(const void *a, const void *b);

int main(int argc, char *argv[])
{
   int cnt, i;

   //seed(125);

   for(i=0;i<10; ++i)
   {
      arr[i].ran   = rand();
      arr[i].index = i;
      printf("arr[%d] Initial Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   qsort( (void *)arr, 10, sizeof(arr[0]), sort_function);
   printf("\n===================\n");
   for(i=0;i<10; ++i)
   {
      printf("arr[%d] Random  Order=%2d, random=%d\n", i, arr[i].index, arr[i].ran);
   }

   return 0;
}

int sort_function(const void *a, const void *b)
{
   struct RanStr *a1, *b1;

   a1=(struct RanStr *) a;
   b1=(struct RanStr *) b;

   return( a1->ran - b1->ran );
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.