首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 不重複隨機抽樣算法全解析:從暴力到 Floyd 算法

     編輯:Java 相關知識

問題定義

在區間 [start, end] 中隨機抽取 count 個不重複整數,要求時間/空間效率最優。

六大算法演進與複雜度對比

算法思路時間複雜度空間複雜度缺點
1. 暴力隨機直接 random() 放入 SetO(count²) 最壞無窮O(count)重複極多,極慢
2. 存在性檢查List + contains() 去重O(count²)O(count)隨 count 增大急劇變慢
3. 元素移除法預建 List,隨機索引後 removeO(count·n) 因 remove 導致數組位移O(n) n=end-start+1大範圍內存爆炸
4. 狀態轉移法數組模擬鏈表,被選元素指向下一可用O(count) 期望O(n)需全範圍數組
5. 遞歸 Floyd遞歸構建結果集,衝突時取 endO(count)O(count)遞歸棧溢出風險
6. 迭代 Floyd ✅單循環實現 Floyd,無遞歸O(count)O(count)最優選

核心代碼:迭代 Floyd 算法(生產級推薦)

import java.util.*;

public class FloydSampling {
    private static final Random RAND = new Random();

    /** 
     * @param start 區間起始(含)
     * @param end   區間結束(含)
     * @param count 抽樣數量,必須 ≤ end-start+1
     * @return 不重複隨機數列表,順序即抽樣順序
     */
    public static List<Integer> sample(int start, int end, int count) {
        if (count > end - start + 1) throw new IllegalArgumentException("count exceeds range");
        List<Integer> result = new ArrayList<>(count);
        for (int i = end - count + 1; i <= end; i++) {
            int r = start + RAND.nextInt(i - start + 1); // [start, i]
            if (result.contains(r)) {
                result.add(i); // 衝突:直接加入當前最大值 i
            } else {
                result.add(r);
            }
        }
        return result;
    }

    public static void main(String[] args) {
        List<Integer>> sample = sample(1, 100, 10);
        System.out.println(sample); // 例:[7, 42, 15, 89, 3, 56, 22, 91, 11, 68]
    }
}

算法證明直觀理解

Floyd 算法本質是保留已選元素的有序集合,第 k 輪從 [start, end-count+k] 隨機,若衝突則選 end-count+k。數學歸納法可證明每個組合等概率 1/C(n,count)。

大數據場景優化:Reservoir Sampling

若總體 N 未知或極大(如流式數據),使用 水塘抽樣:

public static <T> List<T> reservoirSample(Iterator<T> data, int k) {
    List<T> reservoir = new ArrayList<>(k);
    int i = 0;
    while (data.hasNext()) {
        T item = data.next();
        if (i < k) reservoir.add(item);
        else {
            int r = RAND.nextInt(i + 1);
            if (r < k) reservoir.set(r, item);
        }
        i++;
    }
    return reservoir;
}

總結建議

  • 已知範圍、count 較小 → 迭代 Floyd(O(count) 時間/空間)。
  • 範圍極大(百萬級以上)但 count 小 → 仍用 Floyd,但改用 HashSet 存結果,contains 為 O(1)。
  • 流式/未知總量 → Reservoir Sampling。
  • 嚴禁 在生產環境使用暴力隨機或存在性檢查(O(n²) 會導致延遲抖動)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved