อัลกอริทึมที่มีประสิทธิภาพที่สุดในการพิมพ์ 1-100 โดยใช้ตัวสร้างตัวเลขสุ่มที่กำหนด


11

เราได้รับตัวสร้างตัวเลขสุ่มRandNum50ซึ่งสร้างจำนวนเต็มแบบสุ่มในช่วงที่ 1–50 เราอาจใช้ตัวสร้างตัวเลขสุ่มนี้เท่านั้นในการสร้างและพิมพ์จำนวนเต็มทั้งหมดจาก 1 ถึง 100 ตามลำดับแบบสุ่ม ทุกหมายเลขจะต้องมาหนึ่งครั้งและความน่าจะเป็นของหมายเลขใด ๆ ที่เกิดขึ้น ณ สถานที่ใด ๆ จะต้องเท่ากัน

อัลกอริทึมที่มีประสิทธิภาพที่สุดสำหรับสิ่งนี้คืออะไร?


1
ใช้อาร์เรย์ / หรือเวกเตอร์บิตเพื่อบันทึกตัวเลขที่เห็นแล้วและตัวนับเพื่อบันทึกจำนวนเฉพาะที่เห็น
Dave Clarke

@DaveClarke ฉันจะสร้างจำนวนที่มากกว่า 50 ด้วยวิธีนี้ได้อย่างไร ถ้าฉันใช้มากกว่า 1 ครั้งแล้วฉันจะสร้าง 1 โดยใช้ได้อย่างไร
Raj Wadhwa

1
ความท้าทายของหลักสูตรคือการทำให้แน่ใจว่าทุกสถานที่เกิดขึ้นกับโอกาสที่เท่าเทียมกัน RandNum100 = (RandNum50() * 2) - (RandNum50 > 25) ? 0 : 1)คุณสามารถใช้
Dave Clarke

2
@DaveClarke: ดังนั้นคุณเสนอการสุ่มตัวอย่างการปฏิเสธซ้ำแล้วซ้ำอีกหรือไม่ ที่จะยุติในความคาดหวังเท่านั้น
ราฟาเอล

ฉันแค่ให้คำใบ้
Dave Clarke

คำตอบ:


3

ผมคิดว่า (ดังนั้นจึงสามารถจะผิด :-) นี้วิธีการแก้ปัญหาที่ใช้ในการสับเปลี่ยน Fisher-Yates เพื่อให้การกระจายอย่างสม่ำเสมอมีการประมาณที่ดี (ดูหัวข้อแก้ไขด้านล่าง) ในการวนซ้ำทุกครั้งคุณสามารถใช้เคล็ดลับนี้เพื่อสร้างค่าระหว่าง0ถึงk - 1 :O(ยังไม่มีข้อความ2)krand0k-1

 // return a random number in [0..k-1] with uniform distribution
 // using a uniform random generator in [1..50]
 funtion krand(k) {    
   sum = 0
   for i = 1 to k do sum = sum + RandNum50() - 1
   krand = sum mod k
 }

อัลกอริทึม Fisher-Yates กลายเป็น:

arr : array[0..99]
for i = 0  to 99 do arr[i] = i+1; // store 1..100 in the array
for i = 99 downto 1 {
  r = krand(i+1)  // random value in [0..i]
  exchange the values of arr[i] and arr[r]
}
for i = 0 to 99 do print arr[i]

แก้ไข:

ตามที่เอริคชี้ให้เห็นkrandฟังก์ชั่นด้านบนไม่ได้ให้การกระจายที่เหมือนกันอย่างแท้จริง มีวิธีการอื่น ๆ ที่สามารถใช้ในการรับที่ดีกว่า (โดยพลการดีกว่า) และประมาณได้เร็วขึ้น; แต่ (ขึ้นอยู่กับความรู้ของฉัน) วิธีเดียวที่จะได้รับเครื่องแบบกระจายอย่างแท้จริงคือการใช้การสุ่มตัวอย่างปฏิเสธ : รับบิตสุ่มและถ้าจำนวนRได้รับน้อยกว่าkผลตอบแทนมันสร้างอย่างอื่น อีกจำนวนสุ่ม การใช้งานที่เป็นไปได้:ม.=เข้าสู่ระบบ2(k)Rk

function trulyrand(k) {
    if (k <= 1) return 0
    while (true) { // ... if you're really unlucky ...
      m = ceil(log_2 (k) ) // calculate m such that k < 2^m
      r = 0  // will hold the random value
      while (m >= 0) {  // ... will add m bits        
        if ( rand50() > 25 ) then b = 1 else b = 0   // random bit
        r = r * 2 + b  // shift and add the random bit
        m = m - 1
      }      
      if (r < k) then return r  // we have 0<=r<2^m ; accept it, if r < k
    }
}

1
หน้าวิกิพีเดียที่คุณเชื่อมโยงไปยังระบุว่ามีตัวแปรO(n)
เดฟคลาร์

1
ฉันคิดว่า "สลับ" เป็นคำสำคัญที่นี่
Raphael

4
เคล็ดลับใน krand (k) ไม่ได้ทำให้เกิดการแจกแจงที่เหมือนกันอย่างแท้จริงแม้ว่าจะเป็นการประมาณที่ดี: แม้สำหรับ k = 3 สิ่งนี้จะให้โอกาสในการแสดงผล 33.333328% 0 มีเหตุผลสำหรับการสรุปไปจนถึง k ที่นี่ ? ฉันจะคิดว่าพอเพียงขีด จำกัด เล็ก ๆ ถ้าเราแค่ต้องการการประมาณ
Erick Wong

1
@ErickWong: คุณพูดถูก ฉันคิดว่าการกระจายชุดที่แท้จริงสามารถเข้าถึงได้โดยใช้วิธีการสุ่มตัวอย่างการปฏิเสธซึ่งไม่รับประกันว่าจะสิ้นสุดในเวลาคงที่ มีรูปแบบการประมาณอื่น ๆ (ที่อนุญาตให้ไปถึงการประมาณที่ต้องการ) สิ่งที่ฉันเสนอคือสิ่งแรกที่อยู่ในใจของฉัน
Vor

2
@ ex0du5: ฉันรู้ว่า แต่คุณจะสร้างการเรียงสับเปลี่ยนแบบสุ่มของตัวเลข [1..100] โดยใช้ตัวกำเนิดการสุ่มแบบสม่ำเสมอใน [1..100] ได้อย่างไร วิธีการทางเลือกเดียวที่ฉันรู้คือขั้นที่ 1) เลือกสุ่มค่าใน1..100 ; ขั้นที่ 2) ถ้าrได้ถูกเลือกไปแล้วให้ทิ้งไปและข้ามขั้นที่ 1; ขั้นตอนที่ 3) พิมพ์r ; ขั้นตอนที่ 4) ถ้าเรายังไม่ได้พิมพ์ 100 จำนวนทั้งหมดไปที่ขั้นตอนที่ 1 แต่วิธีนี้จะเลื่อนการปฏิเสธไปยังองค์ประกอบที่เลือกไว้แล้ว r1..100RR
Vor

4

เนื่องจากคนอื่น ๆ ให้วิธีแก้ปัญหาโดยประมาณและวิธีแก้ปัญหาที่เกี่ยวข้องกับการหาค่าเบี่ยงเบนจำนวนไม่แน่นอนวิธีการพิสูจน์ว่าไม่มีอัลกอริธึมที่รับประกันว่าจะต้องใช้จำนวน จำกัด เท่านั้นRandNum50()?

ดังที่คนอื่น ๆ ได้กล่าวไว้การพิมพ์ตัวเลขจาก 1-100 ในลำดับสุ่มนั้นเทียบเท่ากับการพิมพ์การเรียงสับเปลี่ยนแบบสุ่มของตัวเลขเหล่านี้ มี 100! ของการเปลี่ยนลำดับเหล่านี้และดังนั้นการเปลี่ยนรูปแบบใด ๆ จะต้องถูกส่งออกด้วยความน่าจะเป็น.1100!

แต่ถ้าเรารู้ว่าอัลกอริทึมของเราที่ใช้ในการเรียกมากที่สุดสำหรับkบางอย่างเราสามารถโต้แย้งได้ดังนี้: ก่อนอื่นให้วางเส้นทางการคำนวณเหล่านั้นที่ทำให้น้อยกว่าการเรียกkเพื่อโทรแบบจำลองเพิ่มเติม (นั่นคือสายที่ ค่าที่ส่งคืนจะไม่เกี่ยวข้อง) เพื่อให้เส้นทางการคำนวณทั้งหมดทำการโทรkอย่างแม่นยำ ลำดับของผลลัพธ์k ที่ได้รับจากการเรียกของเราต้องส่งผลให้เกิดการเปลี่ยนแปลงเอาท์พุทและเราสามารถสร้าง 'ตารางผลลัพธ์' ที่แมปลำดับที่กำหนด( r 1 , r 2 , , r k )kRandNum50kkRandNum50kkRandNum50(R1,R2,...,Rk)ของผลลัพธ์จากการเรียกของเราไปสู่การเปลี่ยนแปลงเอาท์พุทโดยเฉพาะ เนื่องจากผลลัพธ์เหล่านี้แต่ละรายการมีโอกาสเท่ากัน (แต่ละรายการมีความน่าจะเป็น ) ดังนั้นความน่าจะเป็นที่จะได้รับการเปลี่ยนแปลงใด ๆ จากอัลกอริทึมของเราจะต้องอยู่ในรูปแบบ150kสำหรับบางค แต่150kไม่สามารถอยู่ในรูปแบบนี้ได้เพราะ100! ไม่ได้แบ่ง50kสำหรับการใด ๆk(เช่น 3 แบ่ง100!แต่ไม่สามารถแบ่งจำนวนของรูปแบบใด ๆ50k) ซึ่งหมายความว่าไม่มีการแจกแจงผลลัพธ์ที่เป็นไปได้ในการโทรหาหมายเลขสุ่มสามารถสร้างการเปลี่ยนรูปแบบเดียวกัน1100!100!50kk100!50k


2

nเข้าสู่ระบบn+O(1)

if ( rand50() > 25 ) then b = 1 else b = 0   // random bit

1n!123n

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

P = (RandNum50()-1) + (RandNum50()-1)*50^1 + (RandNum50()-1)*50^2 + ...

P50PQ=Pพอควรnn=100!P>n


1

ผมไม่เคยทำวิเคราะห์เพื่อยืนยันว่าเครื่องแบบ (หรือไม่) นี้จะเป็นและมันอาจจะมีการปรับให้เป็นสับเปลี่ยนจริง แต่คุณอาจจะเพียงแค่เลือกจากอาร์เรย์เริ่มต้นของiดัชนี TH = i + 1ที่(k + RandNum50() + RandNum50() - 1) mod (100 - k)ดัชนีมี ลบออกสำหรับk= 0..99 หรือไม่

นี่เป็นการ "ผลัก" จุดสูงสุดในการRandNum50() + RandNum50()กระจายไปข้างหน้าอย่างสม่ำเสมอ

ฉันค่อนข้างแน่ใจว่านี่ไม่ถูกต้องตามที่ฉันได้กล่าวไว้เพราะดัชนี 0 (1) ไม่สามารถหาได้จากตัวเลือกแรกและฉันไม่สามารถเห็นทางเลือกอย่างรวดเร็ว 1.50 + 1..50 การปรับที่สร้าง 0 ..99

ปรับปรุง

เพื่อแก้ไขปัญหาที่ฉันสังเกตเห็นฉันใช้อย่างมีประสิทธิภาพRandNum100ตามที่กล่าวไว้ในความคิดเห็นคำถามเพื่อเริ่มต้นการสุ่มkออฟเซ็ตแรก

สิ่งนี้ก่อให้เกิดการกระจายตัวของคลื่นที่สำคัญด้านหน้า

แทนที่จะก้าวหน้าโดยที่ 1 ผมใช้อื่นเพื่อเพิ่มที่แรกRandNum50 kสิ่งนี้สร้างผลลัพธ์ที่สุ่มพอสำหรับฉัน แต่ก็ยังไม่สุ่ม "แท้จริง" ดังที่เห็นได้ง่ายถ้าคุณเปลี่ยน K เป็น 2

การทดสอบโค้ด VB.NETที่ฉันให้การสนับสนุนแม้แต่เคหมายเหตุเป็น O (K), 6K + 2 อันที่จริง

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