Tạo số ngẫu nhiên không trùng lặp


88

Trong trường hợp này, MAX chỉ là 5, vì vậy tôi có thể kiểm tra từng bản sao một, nhưng làm cách nào để thực hiện điều này một cách đơn giản hơn? Ví dụ, nếu MAX có giá trị là 20 thì sao? Cảm ơn.

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
Nhiều bộ tạo số ngẫu nhiên (giả) không lặp lại trong "chu kỳ" đầy đủ của chúng. Tất nhiên, vấn đề là "chu kỳ" đầy đủ của họ là hàng tỷ hoặc nghìn tỷ giá trị, và giá trị mà họ tạo ra có thể là bất kỳ một trong số hàng tỷ hoặc nghìn tỷ giá trị đó. Về lý thuyết, bạn có thể tạo ra một bộ tạo số ngẫu nhiên có "chu kỳ" là 5 hoặc 10 hoặc bất cứ điều gì, nhưng nó có thể rắc rối hơn giá trị của nó.
Hot Licks

1
Ngoài ra, một trình tạo số ngẫu nhiên không lặp lại thậm chí là ngẫu nhiên "ít hơn": nếu MAX = 5 và bạn đọc 3 số, bạn có thể đoán tiếp theo với xác suất 50%, nếu bạn đọc 4 số, bạn biết tiếp theo cho 100%. chắc chắn rồi!
icza

Đã trả lời cho một câu hỏi trùng lặp ở đây
Alex - GlassEditor.com


Câu trả lời:


149

Cách đơn giản nhất là tạo một danh sách các số có thể có (1..20 hoặc bất kỳ) và sau đó trộn chúng với nhau Collections.shuffle. Sau đó, chỉ cần lấy nhiều yếu tố bạn muốn. Điều này thật tuyệt nếu cuối cùng phạm vi của bạn bằng với số phần tử bạn cần (ví dụ: xáo trộn một bộ bài).

Điều đó không hoạt động tốt nếu bạn muốn (giả sử) 10 phần tử ngẫu nhiên trong phạm vi 1..10.000 - bạn sẽ phải làm nhiều việc một cách không cần thiết. Tại thời điểm đó, có lẽ tốt hơn là giữ một tập hợp các giá trị bạn đã tạo cho đến nay và chỉ tiếp tục tạo các số trong một vòng lặp cho đến khi cái tiếp theo không xuất hiện:

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);
}

Tuy nhiên, hãy cẩn thận với lựa chọn đặt - tôi đã rất cố tình sử dụng LinkedHashSetvì nó duy trì thứ tự chèn, điều mà chúng tôi quan tâm ở đây.

Tuy nhiên, một lựa chọn khác là luôn đạt được tiến bộ, bằng cách giảm phạm vi mỗi lần và bù đắp cho các giá trị hiện có. Ví dụ: giả sử bạn muốn có 3 giá trị trong phạm vi 0..9. Trong lần lặp đầu tiên, bạn sẽ tạo ra bất kỳ số nào trong phạm vi 0..9 - giả sử bạn tạo 4.

Ở lần lặp thứ hai, bạn sẽ tạo một số trong phạm vi 0..8. Nếu số được tạo nhỏ hơn 4, bạn sẽ giữ nguyên ... nếu không, bạn thêm một số vào nó. Điều đó mang lại cho bạn một dải kết quả là 0..9 mà không có 4. Giả sử chúng ta nhận được 7 theo cách đó.

Trong lần lặp thứ ba, bạn sẽ tạo một số trong phạm vi 0..7. Nếu số được tạo nhỏ hơn 4, bạn sẽ giữ nguyên. Nếu đó là 4 hoặc 5, bạn sẽ thêm một. Nếu là 6 hoặc 7, bạn sẽ thêm hai. Bằng cách đó, phạm vi kết quả là 0..9 mà không có 4 hoặc 6.


Tạo một mảng các giá trị có thể có, chọn ngẫu nhiên một (kích thước mảng mod số ngẫu nhiên), xóa (và lưu) số đã chọn, sau đó lặp lại.
Hot Licks

Hoặc sử dụng trình tạo ngẫu nhiên có chu kỳ đầy đủ (các trình tạo dựa trên số nguyên tố có thể sử dụng các số nguyên tố nhỏ - với chu kỳ nhỏ tương ứng) và bỏ giá trị ra khỏi phạm vi.
Paul de Vrieze

Tùy chọn "Tuy nhiên, một lựa chọn khác là luôn đạt được tiến bộ" là một giải pháp tốt hơn WAAAAY. Vui lòng chỉnh sửa để phản ánh. Và cảm ơn bạn vì câu trả lời tuyệt vời này.
user123321

1
@musselwhizzle: Sẽ sớm cố gắng tìm lại thời gian. Tôi không chắc về "WAAAY tốt hơn" - nó sẽ ít hơn đáng kể "rõ ràng là chính xác" mặc dù nó sẽ hiệu quả hơn. Thông thường, tôi rất vui khi hy sinh hiệu suất vì mục đích dễ đọc.
Jon Skeet

@Deepthi: OP muốn tối đa là gì - theo câu hỏi.
Jon Skeet

19

Đây là cách tôi sẽ làm điều đó

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));
        }
    }
}

Như ông Skeet đáng kính đã chỉ ra:
Nếu n là số lượng các số được chọn ngẫu nhiên mà bạn muốn chọn và N là tổng không gian mẫu của các số có sẵn để lựa chọn:

  1. Nếu n << N , bạn chỉ nên lưu trữ các số bạn đã chọn và kiểm tra một danh sách để xem số đã chọn có trong đó hay không.
  2. Nếu n ~ = N , bạn có thể nên sử dụng phương pháp của tôi, bằng cách điền vào một danh sách chứa toàn bộ không gian mẫu và sau đó xóa các số khỏi nó khi bạn chọn chúng.

danh sách phải là một LinkedList, loại bỏ các chỉ số ngẫu nhiên từ ArrayList là rất không hiệu quả
Riccardo Casatta

@RiccardoCasatta bạn có nguồn nào cho khẳng định của mình không? Tôi không thể tưởng tượng rằng việc duyệt qua một danh sách được liên kết cũng sẽ rất hiệu quả. Xem thêm: stackoverflow.com/a/6103075/79450
Catchwa

Mình đã test thử và bạn nói đúng, mình có nên xóa comment của mình không?
Riccardo Casatta

@RiccardoCasatta Những người khác có thể thấy thông tin qua lại của chúng tôi hữu ích
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);
    }
}

Điều này sẽ có hiệu suất khủng khiếp đối với số lượng lớn. ArrayList.contains đang lặp lại qua danh sách. Thay vào đó sẽ gọn gàng hơn nhiều là có một Bộ - bạn không cần kiểm tra xem nó có chứa hay không, chỉ cần thêm và hiệu suất sẽ tốt hơn.
kfox

5

Có một cách khác để thực hiện các số có thứ tự "ngẫu nhiên" với LFSR, hãy xem:

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

với kỹ thuật này, bạn có thể đạt được số ngẫu nhiên được sắp xếp theo chỉ mục và đảm bảo các giá trị không bị trùng lặp.

Nhưng đây không phải là các số ngẫu nhiên ĐÚNG vì sự sinh ngẫu nhiên là xác định.

Nhưng tùy thuộc vào trường hợp của bạn, bạn có thể sử dụng kỹ thuật này để giảm số lượng xử lý khi tạo số ngẫu nhiên khi sử dụng xáo trộn.

Đây là thuật toán LFSR trong java, (tôi đã lấy nó ở đâu đó mà tôi không nhớ):

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

Một cách tiếp cận này cho phép bạn xác định có bao nhiêu số điện thoại bạn muốn với sizeminmaxgiá trị của những con số trở lại

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;
}

Để sử dụng nó trả về 7 số từ 0 đến 25.

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

4

Điều này sẽ đơn giản hơn rất nhiều trong java-8:

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

3

Cách cơ bản, hiệu quả nhất để có các số ngẫu nhiên không lặp lại được giải thích bằng mã giả này. Không cần phải có các vòng lặp lồng nhau hoặc tra cứu băm:

// 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
}

Giả sử lần lặp đầu tiên tạo ra số ngẫu nhiên 3 để bắt đầu (từ 0 - 19). Điều này sẽ làm cho kết quả [0] = ánh xạ [3], tức là, giá trị 3. Sau đó, chúng tôi sẽ gán ánh xạ [3] cho 19.

Trong lần lặp tiếp theo, số ngẫu nhiên là 5 (từ 0 - 18). Điều này sẽ tạo ra kết quả [1] = ánh xạ [5], tức là giá trị 5. Sau đó, chúng tôi sẽ gán ánh xạ [5] cho 18.

Bây giờ, giả sử lần lặp tiếp theo chọn lại 3 (từ 0 - 17). kết quả [2] sẽ được gán giá trị của ánh xạ [3], nhưng bây giờ, giá trị này không phải là 3, mà là 19.

Sự bảo vệ tương tự này vẫn tồn tại cho tất cả các số, ngay cả khi bạn nhận cùng một số 5 lần liên tiếp. Ví dụ: nếu trình tạo số ngẫu nhiên cho bạn 0 năm lần liên tiếp, kết quả sẽ là: [0, 19, 18, 17, 16].

Bạn sẽ không bao giờ nhận được cùng một số hai lần.


Tôi nghi ngờ điều này là ngẫu nhiên như bạn làm cho nó âm thanh. Nó có vượt qua các bài kiểm tra ngẫu nhiên tiêu chuẩn không ?; nó dường như sẽ tập trung các con số ở gần cuối phổ.
tucuxi

Đây là một trường hợp cơ sở. Pool là {a, b, c}. Chúng ta cần 2 phần tử không lặp lại. Thuật toán sau đây, đây là các tổ hợp chúng ta có thể rút ra và kết quả của chúng: 0,0: a, c 0,1: a, b 1,0: b, a 1,1: b, c 2,0: c, a 2, 1: c, b Điểm: a-4, b-4, c-4
blackcatweb

3

Tạo tất cả các chỉ số của một chuỗi nói chung là một ý tưởng tồi, vì nó có thể mất rất nhiều thời gian, đặc biệt nếu tỷ lệ các số được chọn MAXthấp (độ phức tạp bị chi phối O(MAX)). Điều này trở nên tồi tệ hơn nếu tỷ lệ các số được chọn MAXtiếp cận một, vì khi đó việc loại bỏ các chỉ số đã chọn khỏi chuỗi của tất cả cũng trở nên đắt đỏ (chúng tôi tiếp cận O(MAX^2/2)). Nhưng đối với số lượng nhỏ, điều này thường hoạt động tốt và đặc biệt không dễ xảy ra lỗi.

Lọc các chỉ số đã tạo bằng cách sử dụng một bộ sưu tập cũng là một ý tưởng tồi, vì dành một số thời gian để chèn các chỉ số vào chuỗi và tiến trình không được đảm bảo vì cùng một số ngẫu nhiên có thể được rút ra nhiều lần (nhưng với số lượng đủ lớn MAXthì không chắc ). Điều này có thể gần đến mức phức tạp
O(k n log^2(n)/2), bỏ qua các bản sao và giả sử tập hợp sử dụng một cây để tra cứu hiệu quả (nhưng với chi phí kphân bổ các nút cây không đổi đáng kể và có thể phải cân bằng lại ).

Một tùy chọn khác là tạo các giá trị ngẫu nhiên duy nhất ngay từ đầu, đảm bảo tiến độ đang được thực hiện. Điều đó có nghĩa là trong vòng đầu tiên, một chỉ mục ngẫu nhiên trong [0, MAX]được tạo:

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

Trong vòng thứ hai, chỉ [0, MAX - 1]được tạo (vì một mục đã được chọn):

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)

Giá trị của các chỉ số sau đó cần được điều chỉnh: nếu chỉ số thứ hai rơi vào nửa sau của chuỗi (sau chỉ số đầu tiên), thì nó cần được tăng lên để bù đắp khoảng cách. Chúng tôi có thể thực hiện điều này như một vòng lặp, cho phép chúng tôi chọn số lượng mục duy nhất tùy ý.

Đối với các chuỗi ngắn, đây là O(n^2/2)thuật toán khá nhanh :

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
}

Đâu n_select_numlà số 5 của bạn và n_number_numlà của bạn MAX. Trả n_Rand(x)về các số nguyên ngẫu nhiên trong [0, x](bao gồm). Điều này có thể được thực hiện nhanh hơn một chút nếu chọn nhiều mục (ví dụ: không phải 5 mà là 500) bằng cách sử dụng tìm kiếm nhị phân để tìm điểm chèn. Để làm được điều đó, chúng tôi cần đảm bảo rằng chúng tôi đáp ứng các yêu cầu.

Chúng tôi sẽ thực hiện tìm kiếm nhị phân với phép so sánh n + j < rand_num[j]giống như
n < rand_num[j] - j. Chúng ta cần chỉ ra rằng đó rand_num[j] - jvẫn là một chuỗi được sắp xếp cho một chuỗi đã được sắp xếp rand_num[j]. May mắn thay, điều này dễ dàng được chỉ ra, vì khoảng cách thấp nhất giữa hai phần tử của bản gốc rand_numlà một (các số được tạo là duy nhất, vì vậy luôn có sự khác biệt ít nhất là 1). Đồng thời, nếu chúng ta trừ các chỉ số jkhỏi tất cả các phần tử
rand_num[j], thì sự khác biệt về chỉ số chính xác là 1. Vì vậy, trong trường hợp "xấu nhất", chúng ta nhận được một dãy không đổi - nhưng không bao giờ giảm. Do đó, tìm kiếm nhị phân có thể được sử dụng, tạo ra O(n log(n))thuật toá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
    }
};

Và cuối cùng:

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
}

Tôi đã thử nghiệm điều này trên ba điểm chuẩn. Đầu tiên, 3 số được chọn trong số 7 mục và biểu đồ của các mục được chọn đã được tích lũy qua 10.000 lần chạy:

4265 4229 4351 4267 4267 4364 4257

Điều này cho thấy rằng mỗi mục trong số 7 mục được chọn với số lần gần như nhau và không có sự sai lệch rõ ràng do thuật toán gây ra. Tất cả các trình tự cũng được kiểm tra tính đúng đắn (tính duy nhất của nội dung).

Điểm chuẩn thứ hai liên quan đến việc chọn 7 số trong số 5000 mục. Thời gian của một số phiên bản của thuật toán đã được tích lũy hơn 10.000.000 lần chạy. Các kết quả được biểu thị trong các nhận xét trong mã như b1. Phiên bản đơn giản của thuật toán nhanh hơn một chút.

Điểm chuẩn thứ ba liên quan đến việc chọn 700 số trong số 5000 mục. Thời gian của một số phiên bản của thuật toán lại được cộng dồn, lần này là hơn 10.000 lần chạy. Các kết quả được biểu thị trong các nhận xét trong mã như b2. Phiên bản tìm kiếm nhị phân của thuật toán hiện nhanh hơn hai lần so với phiên bản đơn giản.

Phương pháp thứ hai bắt đầu nhanh hơn để chọn nhiều hơn cca 75 mục trên máy của tôi (lưu ý rằng độ phức tạp của một trong hai thuật toán không phụ thuộc vào số lượng mục, MAX).

Điều đáng nói là các thuật toán trên tạo ra các số ngẫu nhiên theo thứ tự tăng dần. Nhưng sẽ thật đơn giản nếu thêm một mảng khác mà các số sẽ được lưu vào thứ tự mà chúng được tạo và trả về thay vào đó (với chi phí bổ sung không đáng kể O(n)). Không cần thiết phải xáo trộn đầu ra: điều đó sẽ chậm hơn nhiều.

Lưu ý rằng các nguồn là C ++, tôi không có Java trên máy của mình, nhưng khái niệm phải rõ ràng.

CHỈNH SỬA :

Để giải trí, tôi cũng đã triển khai phương pháp tạo danh sách với tất cả các chỉ số
0 .. MAX, chọn chúng ngẫu nhiên và xóa chúng khỏi danh sách để đảm bảo tính duy nhất. Vì tôi đã chọn khá cao MAX(5000), hiệu suất rất thảm:

// 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

Tôi cũng đã triển khai cách tiếp cận với set(một bộ sưu tập C ++), thực sự đứng thứ hai về điểm chuẩn b2, chỉ chậm hơn khoảng 50% so với cách tiếp cận với tìm kiếm nhị phân. Điều đó có thể hiểu được, vì nó setsử dụng cây nhị phân, trong đó chi phí chèn tương tự như tìm kiếm nhị phân. Sự khác biệt duy nhất là cơ hội nhận được các vật phẩm trùng lặp, điều này làm chậm tiến độ.

// 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

Mã nguồn đầy đủ ở đây .


2

Bạn có thể sử dụng một trong các lớp triển khai giao diện Set ( API ) và sau đó mỗi số bạn tạo, hãy sử dụng Set.add () để chèn nó.

Nếu giá trị trả về là false, bạn biết số đã được tạo trước đó.


2

Thay vì làm tất cả những điều này, hãy tạo một LinkedHashSetđối tượng và các số ngẫu nhiên cho nó theo Math.random()hàm .... nếu có bất kỳ mục nhập trùng lặp nào xảy ra, LinkedHashSetđối tượng sẽ không thêm số đó vào Danh sách của nó ... Vì trong Lớp Bộ sưu tập này không cho phép các giá trị trùng lặp .. cuối cùng bạn nhận được một danh sách các số ngẫu nhiên không có giá trị trùng lặp ....: D


2

Bài toán của bạn dường như giảm để chọn ngẫu nhiên k phần tử từ tập hợp n phần tử. Do đó, câu trả lời Collections.shuffle là đúng, nhưng như đã chỉ ra là không hiệu quả: O (n) của nó.

Wikipedia: Fisher – Yates shuffle có phiên bản O (k) khi mảng đã tồn tại. Trong trường hợp của bạn, không có mảng phần tử và việc tạo mảng phần tử có thể rất tốn kém, giả sử nếu tối đa là 10000000 thay vì 20.

Thuật toán xáo trộn bao gồm việc khởi tạo một mảng có kích thước n trong đó mọi phần tử đều bằng chỉ số của nó, chọn k số ngẫu nhiên mỗi số trong một dải có giá trị lớn nhất nhỏ hơn dải trước đó, sau đó hoán đổi các phần tử về cuối mảng.

Bạn có thể thực hiện thao tác tương tự trong thời gian O (k) với một hashmap mặc dù tôi thừa nhận nó hơi khó. Lưu ý rằng điều này chỉ đáng giá nếu k nhỏ hơn n nhiều. (tức là k ~ lg (n) hoặc lâu hơn), nếu không bạn nên sử dụng xáo trộn trực tiếp.

Bạn sẽ sử dụng bản đồ băm của mình như một biểu diễn hiệu quả của mảng hỗ trợ trong thuật toán xáo trộn. Bất kỳ phần tử nào của mảng bằng với chỉ số của nó không cần xuất hiện trong bản đồ. Điều này cho phép bạn biểu diễn một mảng kích thước n trong thời gian không đổi, không mất thời gian khởi tạo nó.

  1. Chọn k số ngẫu nhiên: số đầu tiên nằm trong phạm vi từ 0 đến n-1, số thứ hai từ 0 đến n-2, số thứ ba từ 0 đến n-3, v.v., cho đến hết n.

  2. Coi các số ngẫu nhiên của bạn như một tập hợp các hoán đổi. Chỉ số ngẫu nhiên đầu tiên hoán đổi vị trí cuối cùng. Chỉ số ngẫu nhiên thứ hai hoán đổi vị trí thứ hai đến vị trí cuối cùng. Tuy nhiên, thay vì làm việc với một mảng hỗ trợ, hãy làm việc với bản đồ băm của bạn. Bản đồ băm của bạn sẽ lưu trữ mọi mục nằm ngoài vị trí.

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- tại sao việc tạo mảng lại đắt hơn xáo trộn? Tôi nghĩ rằng hoàn toàn không có lý do gì để bi quan vào thời điểm này :-)
Wolf

1

Mã sau tạo một dãy số ngẫu nhiên giữa [1, m] mà chưa được tạo trước đó.

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

Có thuật toán của lô thẻ: bạn tạo một dãy số có thứ tự ("lô thẻ") và trong mỗi lần lặp lại, bạn chọn một số ở vị trí ngẫu nhiên từ nó (tất nhiên là xóa số đã chọn khỏi "lô thẻ").


0

Đây là một giải pháp hiệu quả để tạo nhanh một mảng ngẫu nhiên. Sau khi ngẫu nhiên hóa, bạn có thể chỉ cần chọn nphần tử thứ ecủa mảng, tăng dần nvà trả về e. Giải pháp này có O (1) để nhận một số ngẫu nhiên và O (n) để khởi tạo, nhưng như một sự cân bằng yêu cầu một lượng bộ nhớ tốt nếu n đủ lớn.


0

Có một giải pháp hiệu quả hơn và ít cồng kềnh hơn cho các số nguyên là Collections.shuffle.

Vấn đề cũng giống như việc chỉ chọn liên tiếp các mục từ các mục chưa chọn trong một tập hợp và sắp xếp chúng theo thứ tự ở một nơi khác. Điều này giống hệt như chia bài ngẫu nhiên hoặc rút vé xổ số trúng thưởng từ một chiếc mũ hoặc thùng.

Thuật toán này hoạt động để tải bất kỳ mảng nào và đạt được thứ tự ngẫu nhiên khi kết thúc tải. Nó cũng hoạt động để thêm vào bộ sưu tập Danh sách (hoặc bất kỳ bộ sưu tập được lập chỉ mục nào khác) và đạt được một chuỗi ngẫu nhiên trong bộ sưu tập ở cuối phần bổ sung.

Nó có thể được thực hiện với một mảng duy nhất, được tạo một lần hoặc một tập thể được sắp xếp theo số, chẳng hạn như Danh sách, tại chỗ. Đối với một mảng, kích thước mảng ban đầu cần phải là kích thước chính xác để chứa tất cả các giá trị dự định. Nếu bạn không biết trước có bao nhiêu giá trị có thể xuất hiện, thì việc sử dụng một bộ sưu tập có thứ tự số, chẳng hạn như ArrayList hoặc List, trong đó kích thước là không thay đổi, cũng sẽ hoạt động. Nó sẽ hoạt động toàn cầu cho một mảng có kích thước bất kỳ lên đến Integer.MAX_VALUE chỉ hơn 2.000.000.000. Các đối tượng danh sách sẽ có các giới hạn chỉ mục giống nhau. Máy của bạn có thể hết bộ nhớ trước khi bạn nhận được một mảng có kích thước đó. Có thể hiệu quả hơn khi tải một mảng được nhập vào các kiểu đối tượng và chuyển đổi nó thành một số tập hợp, sau khi tải mảng. Điều này đặc biệt đúng nếu tập hợp mục tiêu không được lập chỉ mục số.

Thuật toán này, chính xác như đã viết, sẽ tạo ra một phân phối rất đồng đều, nơi không có bản sao. Một khía cạnh RẤT QUAN TRỌNG là việc chèn mục tiếp theo phải có thể xảy ra với kích thước hiện tại + 1. Vì vậy, đối với mục thứ hai, có thể lưu trữ nó ở vị trí 0 hoặc vị trí 1 . Đối với vật phẩm thứ 20, có thể lưu trữ nó ở bất kỳ vị trí nào, từ 0 đến 19. Chỉ có thể là vật phẩm đầu tiên ở vị trí 0 vì nó sẽ cuối cùng ở bất kỳ vị trí nào khác. Mục mới tiếp theo có thể đi bất cứ đâu, kể cả vị trí mới tiếp theo.

Tính ngẫu nhiên của dãy số sẽ ngẫu nhiên như độ ngẫu nhiên của bộ tạo số ngẫu nhiên.

Thuật toán này cũng có thể được sử dụng để tải các loại tham chiếu vào các vị trí ngẫu nhiên trong một mảng. Vì điều này hoạt động với một mảng, nó cũng có thể hoạt động với các bộ sưu tập. Điều đó có nghĩa là bạn không phải tạo bộ sưu tập và sau đó xáo trộn nó hoặc sắp xếp nó theo bất kỳ thứ tự nào mà các đối tượng được chèn vào. Bộ sưu tập chỉ cần có khả năng chèn một mục vào bất kỳ đâu trong bộ sưu tập hoặc nối nó.

// 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

Tất cả thực sự phụ thuộc vào chính xác CÁI GÌ bạn cần thế hệ ngẫu nhiên, nhưng đây là cách của tôi.

Đầu tiên, hãy tạo một phương pháp độc lập để tạo số ngẫu nhiên. Đảm bảo cho phép các giới hạn.

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

Tiếp theo, bạn sẽ muốn tạo một cấu trúc quyết định rất đơn giản để so sánh các giá trị. Điều này có thể được thực hiện theo một trong hai cách. Nếu bạn có một số lượng rất hạn chế để xác minh, một câu lệnh IF đơn giản sẽ đủ:

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;    }

Ở trên so sánh int1 với int2 đến int5, cũng như đảm bảo rằng không có số 0 trong các ngẫu nhiên.

Với hai phương pháp này, chúng ta có thể làm như sau:

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

Theo dõi bởi:

        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);

Nếu bạn có một danh sách dài hơn để xác minh, thì một phương pháp phức tạp hơn sẽ mang lại kết quả tốt hơn cả về độ rõ ràng của mã và tài nguyên xử lý.

Hi vọng điêu nay co ich. Trang web này đã giúp tôi rất nhiều, tôi cảm thấy ít nhất cũng phải THỬ để giúp đỡ.



0

Cách dễ dàng nhất là sử dụng Nano DateTime dưới dạng định dạng dài. System.nanoTime ();

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.