การสร้างตัวเลขสุ่มโดยไม่ซ้ำกัน


89

ในกรณีนี้ MAX เป็นเพียง 5 ดังนั้นฉันจึงสามารถตรวจสอบรายการที่ซ้ำกันได้ทีละรายการ แต่จะทำอย่างไรให้ง่ายกว่า ตัวอย่างเช่นจะเกิดอะไรขึ้นถ้า MAX มีค่าเป็น 20? ขอบคุณ.

int MAX = 5;

for (i = 1 , i <= MAX; i++)
{
        drawNum[1] = (int)(Math.random()*MAX)+1;

        while (drawNum[2] == drawNum[1])
        {
             drawNum[2] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[3] == drawNum[1]) || (drawNum[3] == drawNum[2]) )
        {
             drawNum[3] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[4] == drawNum[1]) || (drawNum[4] == drawNum[2]) || (drawNum[4] == drawNum[3]) )
        {
             drawNum[4] = (int)(Math.random()*MAX)+1;
        }
        while ((drawNum[5] == drawNum[1]) ||
               (drawNum[5] == drawNum[2]) ||
               (drawNum[5] == drawNum[3]) ||
               (drawNum[5] == drawNum[4]) )
        {
             drawNum[5] = (int)(Math.random()*MAX)+1;
        }

}

3
เครื่องสร้างตัวเลขสุ่ม (หลอก) จำนวนมากไม่ทำซ้ำสำหรับ "รอบ" ทั้งหมด ปัญหาคือแน่นอนว่า "วัฏจักร" ทั้งหมดของพวกเขามีมูลค่าหลายพันล้านหรือล้านล้านและมูลค่าที่พวกเขาสร้างขึ้นอาจเป็นค่าใดค่าหนึ่งในหลายพันล้านหรือหลายล้านล้าน ในทางทฤษฎีคุณสามารถสร้างเครื่องสร้างตัวเลขสุ่มที่มี "รอบ" เป็น 5 หรือ 10 หรืออะไรก็ได้ แต่มันอาจจะมีปัญหามากกว่าที่ควรจะเป็น
Hot Licks

1
นอกจากนี้ตัวสร้างตัวเลขสุ่มที่ไม่ทำซ้ำจะสุ่ม "น้อยกว่า" ด้วยซ้ำ: ถ้า MAX = 5 และคุณอ่านตัวเลข 3 ตัวคุณสามารถเดาตัวเลขถัดไปด้วยความน่าจะเป็น 50% หากคุณอ่าน 4 ตัวเลขคุณจะรู้ว่าตัวเลขถัดไป 100% แน่!
icza

มีคำตอบสำหรับคำถามที่ซ้ำกันที่นี่
Alex - GlassEditor.com


คำตอบ:


151

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

มันใช้ไม่ได้ผลถ้าคุณต้องการ (พูด) 10 องค์ประกอบแบบสุ่มในช่วง 1..10,000 - คุณจะทำงานได้มากโดยไม่จำเป็น เมื่อถึงจุดนั้นอาจเป็นการดีกว่าที่จะเก็บชุดของค่าที่คุณสร้างไว้จนถึงตอนนี้และเพียงแค่สร้างตัวเลขวนซ้ำไปเรื่อย ๆ จนกว่าจะไม่มีค่าถัดไป

if (max < numbersNeeded)
{
    throw new IllegalArgumentException("Can't ask for more numbers than are available");
}
Random rng = new Random(); // Ideally just create one instance globally
// Note: use LinkedHashSet to maintain insertion order
Set<Integer> generated = new LinkedHashSet<Integer>();
while (generated.size() < numbersNeeded)
{
    Integer next = rng.nextInt(max) + 1;
    // As we're adding to a set, this will automatically do a containment check
    generated.add(next);
}

โปรดใช้ความระมัดระวังกับตัวเลือกชุด - ฉันใช้อย่างจงใจLinkedHashSetเนื่องจากรักษาลำดับการแทรกซึ่งเราสนใจที่นี่

อีกทางเลือกหนึ่งคือทำให้ก้าวหน้าอยู่เสมอโดยการลดช่วงในแต่ละครั้งและชดเชยค่าที่มีอยู่ ตัวอย่างเช่นสมมติว่าคุณต้องการ 3 ค่าในช่วง 0..9 ในการวนซ้ำครั้งแรกคุณจะต้องสร้างตัวเลขใด ๆ ในช่วง 0..9 - สมมติว่าคุณสร้าง 4

ในการทำซ้ำครั้งที่สองคุณจะต้องสร้างตัวเลขในช่วง 0..8 หากหมายเลขที่สร้างน้อยกว่า 4 คุณจะคงไว้ตามที่เป็นอยู่ ... มิฉะนั้นคุณจะเพิ่มหมายเลขเข้าไป นั่นทำให้คุณได้ผลลัพธ์เป็นช่วง 0..9 โดยไม่มี 4 สมมติว่าเราได้ 7 ทางนั้น

ในการวนซ้ำครั้งที่สามคุณจะต้องสร้างตัวเลขในช่วง 0..7 หากหมายเลขที่สร้างน้อยกว่า 4 คุณจะคงไว้ตามที่เป็นอยู่ ถ้าเป็น 4 หรือ 5 คุณต้องเพิ่ม ถ้าเป็น 6 หรือ 7 คุณจะต้องเพิ่มสองตัว ด้วยวิธีนี้ช่วงผลลัพธ์คือ 0..9 โดยไม่มี 4 หรือ 6


สร้างอาร์เรย์ของค่าที่เป็นไปได้สุ่มเลือกหนึ่งตัว (ขนาดอาร์เรย์ mod mod แบบสุ่ม) ลบ (และบันทึก) หมายเลขที่เลือกแล้วทำซ้ำ
Hot Licks

หรือใช้เครื่องกำเนิดไฟฟ้าแบบสุ่มที่มีรอบเต็ม (เครื่องกำเนิดไฟฟ้าที่ขึ้นอยู่กับราคาสามารถใช้ช่วงเวลาเล็ก ๆ - มีรอบเล็ก ๆ ที่สอดคล้องกัน) และปล่อยค่าออกนอกช่วง
Paul de Vrieze

"แต่อีกทางเลือกหนึ่งคือการทำให้ก้าวหน้าอยู่เสมอ" คือ WAAAAY ที่ดีกว่าของการแก้ปัญหา โปรดแก้ไขเพื่อสะท้อน และขอขอบคุณสำหรับคำตอบที่ยอดเยี่ยมนี้
user123321

1
@musselwhizzle: จะพยายามหาเวลาเร็ว ๆ นี้ ฉันไม่แน่ใจว่า "WAAAY ดีกว่า" แม้ว่า - มันจะ "ถูกต้อง" น้อยกว่ามากแม้ว่ามันจะมีประสิทธิภาพมากกว่าก็ตาม บ่อยครั้งที่ฉันมีความสุขที่ได้เสียสละการแสดงเพื่อประโยชน์ในการอ่าน
Jon Skeet

@Deepthi: สิ่งที่ OP ต้องการสูงสุด - ตามคำถาม
Jon Skeet

19

นี่คือวิธีที่ฉันจะทำ

import java.util.ArrayList;
import java.util.Random;

public class Test {
    public static void main(String[] args) {
        int size = 20;

        ArrayList<Integer> list = new ArrayList<Integer>(size);
        for(int i = 1; i <= size; i++) {
            list.add(i);
        }

        Random rand = new Random();
        while(list.size() > 0) {
            int index = rand.nextInt(list.size());
            System.out.println("Selected: "+list.remove(index));
        }
    }
}

ดังที่นาย Skeet ผู้นับถือได้ชี้ให้เห็น:
ถ้าnคือจำนวนของตัวเลขที่สุ่มเลือกที่คุณต้องการเลือกและNคือพื้นที่ตัวอย่างทั้งหมดของตัวเลขที่มีให้เลือก:

  1. ถ้าn << Nคุณควรเก็บตัวเลขที่คุณเลือกไว้และตรวจสอบรายการเพื่อดูว่ามีหมายเลขที่เลือกอยู่หรือไม่
  2. ถ้าn ~ = Nคุณน่าจะใช้วิธีของฉันโดยใส่ข้อมูลในรายการที่มีพื้นที่ตัวอย่างทั้งหมดจากนั้นลบตัวเลขออกเมื่อคุณเลือก

รายการควรเป็น LinkedList การลบดัชนีแบบสุ่มออกจากรายการ
อาร์เรย์

@RiccardoCasatta คุณมีแหล่งที่มาสำหรับการยืนยันของคุณหรือไม่? ฉันนึกไม่ถึงว่าการข้ามผ่านรายการที่เชื่อมโยงจะมีประสิทธิภาพมากเช่นกัน ดูสิ่งนี้ด้วย: stackoverflow.com/a/6103075/79450
Catchwa

ฉันทดสอบแล้วและคุณคิดถูกฉันควรลบความคิดเห็นของฉันหรือไม่?
Riccardo Casatta

@RiccardoCasatta คนอื่น ๆ อาจพบว่าการกลับไปกลับมาของเรามีประโยชน์
Catchwa

13
//random numbers are 0,1,2,3 
ArrayList<Integer> numbers = new ArrayList<Integer>();   
Random randomGenerator = new Random();
while (numbers.size() < 4) {

    int random = randomGenerator .nextInt(4);
    if (!numbers.contains(random)) {
        numbers.add(random);
    }
}

สิ่งนี้จะมีประสิทธิภาพที่น่ากลัวสำหรับคนจำนวนมาก ArrayList.contains กำลังวนซ้ำผ่านรายการ สิ่งที่สะอาดกว่ามากคือการมี Set แทน - คุณไม่จำเป็นต้องตรวจสอบว่ามีอยู่หรือไม่เพียงแค่เพิ่มและประสิทธิภาพจะดีกว่า
kfox

5

มีอีกวิธีหนึ่งในการทำหมายเลขสั่งซื้อแบบ "สุ่ม" ด้วย LFSR โปรดดูที่:

http://en.wikipedia.org/wiki/Linear_feedback_shift_register

ด้วยเทคนิคนี้คุณจะได้รับหมายเลขสุ่มตามดัชนีและตรวจสอบให้แน่ใจว่าค่าไม่ซ้ำกัน

แต่ตัวเลขเหล่านี้ไม่ใช่ตัวเลขสุ่มที่แท้จริงเนื่องจากการสร้างแบบสุ่มนั้นถูกกำหนด

แต่ทั้งนี้ขึ้นอยู่กับกรณีของคุณคุณสามารถใช้เทคนิคนี้เพื่อลดปริมาณการประมวลผลในการสร้างตัวเลขแบบสุ่มเมื่อใช้การสับ

นี่คืออัลกอริทึม LFSR ใน java (ฉันเอาไปที่ไหนสักแห่งที่ฉันจำไม่ได้):

public final class LFSR {
    private static final int M = 15;

    // hard-coded for 15-bits
    private static final int[] TAPS = {14, 15};

    private final boolean[] bits = new boolean[M + 1];

    public LFSR() {
        this((int)System.currentTimeMillis());
    }

    public LFSR(int seed) {
        for(int i = 0; i < M; i++) {
            bits[i] = (((1 << i) & seed) >>> i) == 1;
        }
    }

    /* generate a random int uniformly on the interval [-2^31 + 1, 2^31 - 1] */
    public short nextShort() {
        //printBits();

        // calculate the integer value from the registers
        short next = 0;
        for(int i = 0; i < M; i++) {
            next |= (bits[i] ? 1 : 0) << i;
        }

        // allow for zero without allowing for -2^31
        if (next < 0) next++;

        // calculate the last register from all the preceding
        bits[M] = false;
        for(int i = 0; i < TAPS.length; i++) {
            bits[M] ^= bits[M - TAPS[i]];
        }

        // shift all the registers
        for(int i = 0; i < M; i++) {
            bits[i] = bits[i + 1];
        }

        return next;
    }

    /** returns random double uniformly over [0, 1) */
    public double nextDouble() {
        return ((nextShort() / (Integer.MAX_VALUE + 1.0)) + 1.0) / 2.0;
    }

    /** returns random boolean */
    public boolean nextBoolean() {
        return nextShort() >= 0;
    }

    public void printBits() {
        System.out.print(bits[M] ? 1 : 0);
        System.out.print(" -> ");
        for(int i = M - 1; i >= 0; i--) {
            System.out.print(bits[i] ? 1 : 0);
        }
        System.out.println();
    }


    public static void main(String[] args) {
        LFSR rng = new LFSR();
        Vector<Short> vec = new Vector<Short>();
        for(int i = 0; i <= 32766; i++) {
            short next = rng.nextShort();
            // just testing/asserting to make 
            // sure the number doesn't repeat on a given list
            if (vec.contains(next))
                throw new RuntimeException("Index repeat: " + i);
            vec.add(next);
            System.out.println(next);
        }
    }
}

4

อีกวิธีหนึ่งที่ช่วยให้คุณระบุจำนวนตัวเลขที่คุณต้องการด้วยsizeและminและmaxค่าของตัวเลขที่ส่งคืน

public static int getRandomInt(int min, int max) {
    Random random = new Random();

    return random.nextInt((max - min) + 1) + min;
}

public static ArrayList<Integer> getRandomNonRepeatingIntegers(int size, int min,
        int max) {
    ArrayList<Integer> numbers = new ArrayList<Integer>();

    while (numbers.size() < size) {
        int random = getRandomInt(min, max);

        if (!numbers.contains(random)) {
            numbers.add(random);
        }
    }

    return numbers;
}

หากต้องการใช้มันจะส่งกลับ 7 ตัวเลขระหว่าง 0 ถึง 25

    ArrayList<Integer> list = getRandomNonRepeatingIntegers(7, 0, 25);
    for (int i = 0; i < list.size(); i++) {
        System.out.println("" + list.get(i));
    }

4

สิ่งนี้จะง่ายกว่ามากในjava-8:

Stream.generate(new Random()::ints)
            .distinct()
            .limit(16) // whatever limit you might need
            .toArray(Integer[]::new);

3

วิธีพื้นฐานที่มีประสิทธิภาพและมีประสิทธิภาพที่สุดในการมีตัวเลขสุ่มแบบไม่ซ้ำอธิบายได้จากรหัสหลอกนี้ ไม่จำเป็นต้องมีการวนซ้ำที่ซ้อนกันหรือการค้นหาที่แฮช:

// get 5 unique random numbers, possible values 0 - 19
// (assume desired number of selections < number of choices)

const int POOL_SIZE = 20;
const int VAL_COUNT = 5;

declare Array mapping[POOL_SIZE];
declare Array results[VAL_COUNT];

declare i int;
declare r int;
declare max_rand int;

// create mapping array
for (i=0; i<POOL_SIZE; i++) {
   mapping[i] = i;
}

max_rand = POOL_SIZE-1;  // start loop searching for maximum value (19)

for (i=0; i<VAL_COUNT; i++) {
    r = Random(0, max_rand); // get random number
    results[i] = mapping[r]; // grab number from map array
    mapping[r] = max_rand;  // place item past range at selected location

    max_rand = max_rand - 1;  // reduce random scope by 1
}

สมมติว่าการทำซ้ำครั้งแรกสร้างหมายเลขสุ่ม 3 เพื่อเริ่มต้น (จาก 0 - 19) สิ่งนี้จะทำให้ได้ผลลัพธ์ [0] = การแมป [3] กล่าวคือค่า 3 จากนั้นเราจะกำหนดการแมป [3] เป็น 19

ในการทำซ้ำครั้งต่อไปจำนวนสุ่มคือ 5 (จาก 0 - 18) สิ่งนี้จะทำให้ได้ผลลัพธ์ [1] = การแมป [5] กล่าวคือค่า 5 จากนั้นเราจะกำหนดการแมป [5] เป็น 18

ตอนนี้สมมติว่าการทำซ้ำครั้งต่อไปเลือก 3 อีกครั้ง (จาก 0 - 17) ผลลัพธ์ [2] จะถูกกำหนดค่าของการแมป [3] แต่ตอนนี้ค่านี้ไม่ใช่ 3 แต่เป็น 19

การป้องกันเดียวกันนี้ยังคงมีอยู่สำหรับทุกหมายเลขแม้ว่าคุณจะได้หมายเลขเดียวกัน 5 ครั้งติดต่อกัน เช่นถ้าตัวสร้างตัวเลขสุ่มให้คุณ 0 ห้าครั้งติดต่อกันผลลัพธ์จะเป็น: [0, 19, 18, 17, 16]

คุณจะไม่ได้รับหมายเลขเดียวกันซ้ำสอง


ฉันสงสัยว่านี่เป็นการสุ่มเหมือนที่คุณทำให้มันฟัง ผ่านการทดสอบการสุ่มมาตรฐานหรือไม่; ดูเหมือนว่าจะมีสมาธิอยู่ใกล้กับจุดสิ้นสุดของสเปกตรัม
tucuxi

นี่คือกรณีพื้นฐาน พูลคือ {a, b, c} เราต้องการองค์ประกอบที่ไม่ซ้ำกัน 2 รายการ อัลกอริทึมต่อไปนี้เป็นชุดค่าผสมที่เราสามารถวาดได้และผลลัพธ์ของมัน: 0,0: a, c 0,1: a, b 1,0: b, a 1,1: b, c 2,0: c, a 2, 1: c, b คะแนน: a-4, b-4, c-4
blackcatweb

3

การสร้างดัชนีทั้งหมดของลำดับโดยทั่วไปเป็นความคิดที่ไม่ดีเนื่องจากอาจต้องใช้เวลามากโดยเฉพาะอย่างยิ่งหากอัตราส่วนของตัวเลขที่จะเลือกMAXต่ำ (ความซับซ้อนจะถูกครอบงำโดยO(MAX)) สิ่งนี้จะแย่ลงถ้าอัตราส่วนของตัวเลขที่จะเลือกMAXเข้าใกล้หนึ่งเนื่องจากการลบดัชนีที่เลือกออกจากลำดับของทั้งหมดก็มีราคาแพงเช่นกัน (เราเข้าใกล้O(MAX^2/2)) แต่สำหรับคนจำนวนน้อยสิ่งนี้มักจะใช้ได้ดีและไม่เกิดข้อผิดพลาดโดยเฉพาะ

การกรองดัชนีที่สร้างขึ้นโดยใช้คอลเลคชันก็เป็นความคิดที่ไม่ดีเช่นกันเนื่องจากใช้เวลาในการแทรกดัชนีลงในลำดับและไม่รับประกันความคืบหน้าเนื่องจากตัวเลขสุ่มเดียวกันสามารถวาดได้หลายครั้ง (แต่สำหรับจำนวนที่มากพอMAXก็ไม่น่าเป็นไปได้ ). สิ่งนี้อาจใกล้เคียงกับความซับซ้อน
O(k n log^2(n)/2)โดยไม่สนใจสิ่งที่ซ้ำกันและสมมติว่าคอลเล็กชันใช้ต้นไม้เพื่อการค้นหาที่มีประสิทธิภาพ (แต่มีต้นทุนคงที่ที่สำคัญkในการจัดสรรโหนดต้นไม้และอาจต้องปรับสมดุลใหม่ )

อีกทางเลือกหนึ่งคือการสร้างค่าแบบสุ่มโดยไม่ซ้ำกันตั้งแต่เริ่มต้นซึ่งรับประกันความคืบหน้า นั่นหมายความว่าในรอบแรกดัชนีแบบสุ่ม[0, MAX]จะถูกสร้างขึ้น:

items i0 i1 i2 i3 i4 i5 i6 (total 7 items)
idx 0       ^^             (index 2)

ในรอบที่สอง[0, MAX - 1]จะถูกสร้างขึ้นเท่านั้น(เนื่องจากได้เลือกหนึ่งรายการแล้ว):

items i0 i1    i3 i4 i5 i6 (total 6 items)
idx 1          ^^          (index 2 out of these 6, but 3 out of the original 7)

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

สำหรับลำดับสั้น ๆ นี่เป็นO(n^2/2)อัลกอริทึมที่ค่อนข้างเร็ว:

void RandomUniqueSequence(std::vector<int> &rand_num,
    const size_t n_select_num, const size_t n_item_num)
{
    assert(n_select_num <= n_item_num);

    rand_num.clear(); // !!

    // b1: 3187.000 msec (the fastest)
    // b2: 3734.000 msec
    for(size_t i = 0; i < n_select_num; ++ i) {
        int n = n_Rand(n_item_num - i - 1);
        // get a random number

        size_t n_where = i;
        for(size_t j = 0; j < i; ++ j) {
            if(n + j < rand_num[j]) {
                n_where = j;
                break;
            }
        }
        // see where it should be inserted

        rand_num.insert(rand_num.begin() + n_where, 1, n + n_where);
        // insert it in the list, maintain a sorted sequence
    }
    // tier 1 - use comparison with offset instead of increment
}

n_select_num5 ของคุณอยู่ที่ไหนและn_number_numเป็นMAXไฟล์. n_Rand(x)ผลตอบแทนจำนวนเต็มสุ่ม[0, x](รวม) สิ่งนี้สามารถทำได้เร็วขึ้นเล็กน้อยหากเลือกรายการจำนวนมาก (เช่นไม่ใช่ 5 แต่ 500) โดยใช้การค้นหาแบบไบนารีเพื่อค้นหาจุดแทรก ในการทำเช่นนั้นเราต้องตรวจสอบให้แน่ใจว่ามีคุณสมบัติตรงตามข้อกำหนด

เราจะทำค้นหาแบบทวิภาคกับการเปรียบเทียบซึ่งเป็นเช่นเดียวกับn + j < rand_num[j]
n < rand_num[j] - jเราจำเป็นต้องแสดงให้เห็นว่ายังคงเรียงลำดับสำหรับการเรียงลำดับrand_num[j] - j rand_num[j]โชคดีที่แสดงให้เห็นได้อย่างง่ายดายเนื่องจากระยะห่างต่ำสุดระหว่างสององค์ประกอบของต้นฉบับrand_numคือหนึ่ง (ตัวเลขที่สร้างขึ้นไม่ซ้ำกันดังนั้นจึงมีความแตกต่างอย่างน้อย 1 เสมอ) ในขณะเดียวกันถ้าเราลบดัชนีjออกจากองค์ประกอบทั้งหมดความ
rand_num[j]แตกต่างของดัชนีจะเท่ากับ 1 ดังนั้นในกรณีที่ "แย่ที่สุด" เราจะได้ลำดับคงที่ - แต่ไม่เคยลดลง ดังนั้นจึงสามารถใช้การค้นหาไบนารีได้โดยให้O(n log(n))อัลกอริทึม:

struct TNeedle { // in the comparison operator we need to make clear which argument is the needle and which is already in the list; we do that using the type system.
    int n;

    TNeedle(int _n)
        :n(_n)
    {}
};

class CCompareWithOffset { // custom comparison "n < rand_num[j] - j"
protected:
    std::vector<int>::iterator m_p_begin_it;

public:
    CCompareWithOffset(std::vector<int>::iterator p_begin_it)
        :m_p_begin_it(p_begin_it)
    {}

    bool operator ()(const int &r_value, TNeedle n) const
    {
        size_t n_index = &r_value - &*m_p_begin_it;
        // calculate index in the array

        return r_value < n.n + n_index; // or r_value - n_index < n.n
    }

    bool operator ()(TNeedle n, const int &r_value) const
    {
        size_t n_index = &r_value - &*m_p_begin_it;
        // calculate index in the array

        return n.n + n_index < r_value; // or n.n < r_value - n_index
    }
};

และในที่สุดก็:

void RandomUniqueSequence(std::vector<int> &rand_num,
    const size_t n_select_num, const size_t n_item_num)
{
    assert(n_select_num <= n_item_num);

    rand_num.clear(); // !!

    // b1: 3578.000 msec
    // b2: 1703.000 msec (the fastest)
    for(size_t i = 0; i < n_select_num; ++ i) {
        int n = n_Rand(n_item_num - i - 1);
        // get a random number

        std::vector<int>::iterator p_where_it = std::upper_bound(rand_num.begin(), rand_num.end(),
            TNeedle(n), CCompareWithOffset(rand_num.begin()));
        // see where it should be inserted

        rand_num.insert(p_where_it, 1, n + p_where_it - rand_num.begin());
        // insert it in the list, maintain a sorted sequence
    }
    // tier 4 - use binary search
}

ฉันได้ทดสอบสิ่งนี้กับสามเกณฑ์มาตรฐาน อันดับแรกหมายเลข 3 ตัวถูกเลือกจาก 7 รายการและฮิสโตแกรมของรายการที่เลือกสะสมมากกว่า 10,000 รัน

4265 4229 4351 4267 4267 4364 4257

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

เกณฑ์มาตรฐานที่สองเกี่ยวข้องกับการเลือก 7 หมายเลขจาก 5,000 รายการ เวลาของอัลกอริทึมหลายเวอร์ชันถูกสะสมมากกว่า 10,000,000 รัน b1ผลที่จะได้แสดงในความคิดเห็นในรหัสเป็น อัลกอริทึมรุ่นธรรมดาเร็วกว่าเล็กน้อย

เกณฑ์มาตรฐานที่สามเกี่ยวข้องกับการเลือก 700 หมายเลขจาก 5,000 รายการ เวลาของอัลกอริทึมหลายเวอร์ชันถูกสะสมอีกครั้งคราวนี้มีการรันมากกว่า 10,000 ครั้ง b2ผลที่จะได้แสดงในความคิดเห็นในรหัสเป็น อัลกอริทึมรุ่นการค้นหาแบบไบนารีตอนนี้เร็วกว่ารุ่นธรรมดามากกว่าสองเท่า

วิธีที่สองเริ่มเร็วขึ้นสำหรับการเลือกมากกว่า cca 75 รายการบนเครื่องของฉัน (โปรดทราบว่าความซับซ้อนของอัลกอริทึมใดไม่ขึ้นอยู่กับจำนวนรายการMAX)

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

โปรดทราบว่าแหล่งที่มาอยู่ใน C ++ ฉันไม่มี Java ในเครื่องของฉัน แต่แนวคิดควรชัดเจน

แก้ไข :

เพื่อความบันเทิงฉันยังได้ใช้วิธีการสร้างรายการพร้อมดัชนีทั้งหมด
0 .. MAXเลือกแบบสุ่มและลบออกจากรายการเพื่อรับประกันความเป็นเอกลักษณ์ เนื่องจากฉันเลือกค่อนข้างสูงMAX(5,000) ประสิทธิภาพจึงเป็นหายนะ:

// b1: 519515.000 msec
// b2: 20312.000 msec
std::vector<int> all_numbers(n_item_num);
std::iota(all_numbers.begin(), all_numbers.end(), 0);
// generate all the numbers

for(size_t i = 0; i < n_number_num; ++ i) {
    assert(all_numbers.size() == n_item_num - i);
    int n = n_Rand(n_item_num - i - 1);
    // get a random number

    rand_num.push_back(all_numbers[n]); // put it in the output list
    all_numbers.erase(all_numbers.begin() + n); // erase it from the input
}
// generate random numbers

ฉันยังใช้วิธีการนี้กับset(คอลเลกชัน C ++) ซึ่งมาอันดับสองในเกณฑ์มาตรฐานb2โดยช้ากว่าวิธีการค้นหาแบบไบนารีเพียงประมาณ 50% นั่นเป็นที่เข้าใจได้เนื่องจากการsetใช้ต้นไม้ไบนารีซึ่งต้นทุนการแทรกนั้นคล้ายกับการค้นหาแบบไบนารี ข้อแตกต่างเพียงอย่างเดียวคือโอกาสในการได้รับรายการที่ซ้ำกันซึ่งจะทำให้ความคืบหน้าช้าลง

// b1: 20250.000 msec
// b2: 2296.000 msec
std::set<int> numbers;
while(numbers.size() < n_number_num)
    numbers.insert(n_Rand(n_item_num - 1)); // might have duplicates here
// generate unique random numbers

rand_num.resize(numbers.size());
std::copy(numbers.begin(), numbers.end(), rand_num.begin());
// copy the numbers from a set to a vector

รหัสที่มาเต็มรูปแบบที่นี่


2

คุณสามารถใช้หนึ่งในคลาสที่ใช้ Set interface ( API ) จากนั้นแต่ละหมายเลขที่คุณสร้างให้ใช้ Set.add () เพื่อแทรก

หากค่าที่ส่งคืนเป็นเท็จแสดงว่ามีการสร้างตัวเลขก่อนหน้านี้แล้ว


2

แทนที่จะทำทั้งหมดนี้ให้สร้างLinkedHashSetวัตถุและตัวเลขสุ่มตามMath.random()ฟังก์ชัน .... หากมีรายการที่ซ้ำกันเกิดขึ้นLinkedHashSetวัตถุจะไม่เพิ่มหมายเลขนั้นในรายการ ... เนื่องจากในคลาสคอลเล็กชันนี้ไม่อนุญาตให้มีค่าที่ซ้ำกัน .. ในที่สุดคุณจะได้รับรายการตัวเลขสุ่มที่ไม่มีค่าซ้ำกัน .... : D


2

ปัญหาของคุณดูเหมือนจะลดลงในการเลือกองค์ประกอบ k โดยการสุ่มจากคอลเลกชันของ n องค์ประกอบ ดังนั้นคำตอบ Collections.shuffle จึงถูกต้อง แต่ตามที่ชี้ให้เห็นว่าไม่มีประสิทธิภาพ: O (n) ของมัน

Wikipedia: Fisher – Yates shuffleมีเวอร์ชัน O (k) เมื่อมีอาร์เรย์อยู่แล้ว ในกรณีของคุณไม่มีอาร์เรย์ขององค์ประกอบและการสร้างอาร์เรย์ขององค์ประกอบอาจมีราคาแพงมากหากสูงสุดคือ 10,000000 แทนที่จะเป็น 20

อัลกอริทึมการสับเปลี่ยนเกี่ยวข้องกับการเริ่มต้นอาร์เรย์ขนาด n โดยที่ทุกองค์ประกอบมีค่าเท่ากับดัชนีโดยเลือกหมายเลขสุ่ม k แต่ละหมายเลขในช่วงที่มีค่าสูงสุดน้อยกว่าช่วงก่อนหน้าจากนั้นจึงสลับองค์ประกอบไปที่ส่วนท้ายของอาร์เรย์

คุณสามารถดำเนินการเดียวกันในเวลา O (k) ด้วยแฮชแมปแม้ว่าฉันจะยอมรับความเจ็บปวดก็ตาม โปรดทราบว่าสิ่งนี้จะคุ้มค่าก็ต่อเมื่อ k น้อยกว่า n มาก (เช่น k ~ lg (n) หรือมากกว่านั้น) มิฉะนั้นคุณควรใช้สับเปลี่ยนโดยตรง

คุณจะใช้แฮชแมปของคุณเป็นตัวแทนที่มีประสิทธิภาพของอาร์เรย์สำรองในอัลกอริทึมการสุ่ม องค์ประกอบใด ๆ ของอาร์เรย์ที่เท่ากับดัชนีไม่จำเป็นต้องปรากฏในแผนที่ สิ่งนี้ช่วยให้คุณแสดงอาร์เรย์ของขนาด n ในเวลาคงที่โดยไม่มีเวลาที่ใช้ในการเริ่มต้น

  1. เลือก k หมายเลขสุ่ม: ตัวแรกอยู่ในช่วง 0 ถึง n-1, 0 ถึง n-2 ที่สอง, 0 ถึง n-3 ที่สามและอื่น ๆ ผ่าน nk

  2. ถือว่าตัวเลขสุ่มของคุณเป็นชุดของการแลกเปลี่ยน ดัชนีสุ่มแรกจะสลับไปยังตำแหน่งสุดท้าย ดัชนีสุ่มที่สองจะสลับตำแหน่งที่สองไปยังตำแหน่งสุดท้าย อย่างไรก็ตามแทนที่จะทำงานกับอาร์เรย์สำรองให้ทำงานกับแฮชแมปของคุณ แฮชแมปของคุณจะจัดเก็บทุกรายการที่ไม่อยู่ในตำแหน่ง

int getValue(i) { if (map.contains(i)) return map[i]; return i; } void setValue(i, val) { if (i == val) map.remove(i); else map[i] = val; } int[] chooseK(int n, int k) { for (int i = 0; i < k; i++) { int randomIndex = nextRandom(0, n - i); //(n - i is exclusive) int desiredIndex = n-i-1; int valAtRandom = getValue(randomIndex); int valAtDesired = getValue(desiredIndex); setValue(desiredIndex, valAtRandom); setValue(randomIndex, valAtDesired); } int[] output = new int[k]; for (int i = 0; i < k; i++) { output[i] = (getValue(n-i-1)); } return output; }


creating the array of elements could be very expensive- ทำไมการสร้างอาร์เรย์จึงมีราคาแพงกว่าการสับเปลี่ยน? ฉันคิดว่าไม่มีเหตุผลสำหรับการมองโลกในแง่ร้ายในประเด็นนี้ :-)
Wolf

1

รหัสต่อไปนี้สร้างหมายเลขสุ่มลำดับระหว่าง [1, m] ที่ไม่ได้สร้างมาก่อน

public class NewClass {

    public List<Integer> keys = new ArrayList<Integer>();

    public int rand(int m) {
        int n = (int) (Math.random() * m + 1);
        if (!keys.contains(n)) {
            keys.add(n);
            return n;
        } else {
            return rand(m);
        }
    }

    public static void main(String[] args) {
        int m = 4;
        NewClass ne = new NewClass();
        for (int i = 0; i < 4; i++) {
            System.out.println(ne.rand(m));
        }
        System.out.println("list: " + ne.keys);
    }
}


0

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


0

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


0

มีโซลูชันสำหรับจำนวนเต็มที่มีประสิทธิภาพและยุ่งยากน้อยกว่า Collections.shuffle

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

อัลกอริทึมนี้ใช้สำหรับการโหลดอาร์เรย์ใด ๆ และได้รับลำดับแบบสุ่มเมื่อสิ้นสุดการโหลด นอกจากนี้ยังใช้สำหรับการเพิ่มลงในคอลเล็กชันรายการ (หรือคอลเล็กชันที่จัดทำดัชนีอื่น ๆ ) และบรรลุลำดับแบบสุ่มในคอลเล็กชันเมื่อสิ้นสุดการเพิ่ม

สามารถทำได้โดยใช้อาร์เรย์เดียวสร้างขึ้นครั้งเดียวหรือชุดรวบรวมที่เรียงลำดับตามตัวเลขเช่นรายการในตำแหน่ง สำหรับอาร์เรย์ขนาดอาร์เรย์เริ่มต้นจะต้องมีขนาดที่แน่นอนเพื่อให้มีค่าที่ต้องการทั้งหมด หากคุณไม่ทราบจำนวนค่าที่อาจเกิดขึ้นล่วงหน้าการใช้คอลเลกชันที่เรียงตามลำดับตัวเลขเช่น ArrayList หรือ List ซึ่งขนาดไม่เปลี่ยนรูปก็จะใช้ได้เช่นกัน มันจะทำงานแบบสากลสำหรับอาร์เรย์ที่มีขนาดไม่เกิน Integer.MAX_VALUE ซึ่งมีขนาดมากกว่า 2,000,000,000 รายการวัตถุจะมีขีด จำกัด ดัชนีเดียวกัน เครื่องของคุณอาจมีหน่วยความจำไม่เพียงพอก่อนที่คุณจะไปยังอาร์เรย์ขนาดนั้น อาจมีประสิทธิภาพมากขึ้นในการโหลดอาร์เรย์ที่พิมพ์ลงในประเภทออบเจ็กต์และแปลงเป็นคอลเลคชันบางส่วนหลังจากโหลดอาร์เรย์ โดยเฉพาะอย่างยิ่งหากคอลเล็กชันเป้าหมายไม่ได้ถูกจัดทำดัชนีเป็นตัวเลข

อัลกอริทึมนี้ตรงตามที่เขียนไว้จะสร้างการแจกแจงแบบสม่ำเสมอโดยที่ไม่มีการซ้ำกัน สิ่งหนึ่งที่สำคัญมากคือต้องมีความเป็นไปได้ที่การแทรกของรายการถัดไปจะเกิดขึ้นตามขนาดปัจจุบัน + 1 ดังนั้นสำหรับรายการที่สองอาจเป็นไปได้ที่จะจัดเก็บไว้ในตำแหน่ง 0 หรือตำแหน่ง 1 สำหรับรายการที่ 20 อาจเป็นไปได้ที่จะจัดเก็บไว้ในตำแหน่งใดก็ได้ตั้งแต่ 0 ถึง 19 รายการแรกจะอยู่ในตำแหน่ง 0 ให้มากที่สุดเท่าที่จะเป็นไปได้เพื่อให้มันจบลงในตำแหน่งอื่น เป็นไปได้มากที่สุดสำหรับรายการใหม่ถัดไปที่จะไปที่ใดก็ได้รวมทั้งตำแหน่งใหม่ถัดไป

การสุ่มของลำดับจะสุ่มเหมือนกับการสุ่มของตัวสร้างตัวเลขสุ่ม

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

// RandomSequence.java
import java.util.Random;
public class RandomSequence {

    public static void main(String[] args) {
        // create an array of the size and type for which
        // you want a random sequence
        int[] randomSequence = new int[20];
        Random randomNumbers = new Random();

        for (int i = 0; i < randomSequence.length; i++ ) {
            if (i == 0) { // seed first entry in array with item 0
                randomSequence[i] = 0; 
            } else { // for all other items...
                // choose a random pointer to the segment of the
                // array already containing items
                int pointer = randomNumbers.nextInt(i + 1);
                randomSequence[i] = randomSequence[pointer]; 
                randomSequence[pointer] = i;
                // note that if pointer & i are equal
                // the new value will just go into location i and possibly stay there
                // this is VERY IMPORTANT to ensure the sequence is really random
                // and not biased
            } // end if...else
        } // end for
        for (int number: randomSequence) {
                System.out.printf("%2d ", number);
        } // end for
    } // end main
} // end class RandomSequence

0

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

ขั้นแรกสร้างวิธีการแบบสแตนด์อโลนสำหรับการสร้างหมายเลขสุ่ม อย่าลืมเผื่อขีด จำกัด

public static int newRandom(int limit){
    return generatedRandom.nextInt(limit);  }

จากนั้นคุณจะต้องสร้างโครงสร้างการตัดสินใจที่ง่ายมากที่เปรียบเทียบค่าต่างๆ ซึ่งสามารถทำได้ด้วยวิธีใดวิธีหนึ่งจากสองวิธี หากคุณมีจำนวน จำกัด มากในการตรวจสอบคำสั่ง IF แบบธรรมดาก็เพียงพอแล้ว:

public static int testDuplicates(int int1, int int2, int int3, int int4, int int5){
    boolean loopFlag = true;
    while(loopFlag == true){
        if(int1 == int2 || int1 == int3 || int1 == int4 || int1 == int5 || int1 == 0){
            int1 = newRandom(75);
            loopFlag = true;    }
        else{
            loopFlag = false;   }}
    return int1;    }

ข้างต้นเปรียบเทียบ int1 กับ int2 ถึง int5 รวมทั้งตรวจสอบให้แน่ใจว่าไม่มีศูนย์ในแรนดอม

ด้วยวิธีการทั้งสองนี้เราสามารถทำสิ่งต่อไปนี้:

    num1 = newRandom(limit1);
    num2 = newRandom(limit1);
    num3 = newRandom(limit1);
    num4 = newRandom(limit1);
    num5 = newRandom(limit1);

ติดตามโดย:

        num1 = testDuplicates(num1, num2, num3, num4, num5);
        num2 = testDuplicates(num2, num1, num3, num4, num5);
        num3 = testDuplicates(num3, num1, num2, num4, num5);
        num4 = testDuplicates(num4, num1, num2, num3, num5);
        num5 = testDuplicates(num5, num1, num2, num3, num5);

หากคุณมีรายการที่ยาวกว่าให้ตรวจสอบวิธีการที่ซับซ้อนมากขึ้นจะให้ผลลัพธ์ที่ดีกว่าทั้งในด้านความชัดเจนของโค้ดและในการประมวลผลทรัพยากร

หวังว่านี่จะช่วยได้ ไซต์นี้ช่วยฉันได้มากฉันรู้สึกว่าอย่างน้อยก็ต้องช่วยเช่นกัน


0

ฉันสร้างตัวอย่างข้อมูลที่ไม่สร้างจำนวนเต็มสุ่มซ้ำ ข้อดีของตัวอย่างข้อมูลนี้คือคุณสามารถกำหนดรายการอาร์เรย์ให้กับมันและสร้างรายการแบบสุ่มได้ด้วย

ไม่มีคลาสเครื่องกำเนิดไฟฟ้าแบบสุ่มซ้ำ

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