根據權重進行排序,結果為排序后的索引。
限制:
1.入參個數必須大於1
2.所有參數必須大於0(小於等於0的權重無意義,sortByWeightAndRandom方法會將小於等於0的放最后進行隨機排序)
代碼
import java.util.ArrayList; import java.util.Random; /** * 隨機排序或者按權重排序,返回排序后的索引. * * @author Laeni */ public final class AliasMethodKit { private static final Random RANDOM = new Random(); /** * 根據權重進行排序. * * @param weights 需要排序的元素權重. * @return 返回排序后元素的索引 */ public static int[] sortByWeight(int[] weights) { if (weights.length == 0) { return new int[0]; } // 將權重值依次放到 0 開始的數軸上: [數軸起始位置, 數軸結束位置, 原始值所在的索引位置], 起始位置與結束位置的差即為原始權重值 // 如 - [3, 5, 2] => [[0, 3, 0], [3, 8, 1], [8, 10, 2]] int[][] xx = new int[weights.length][]; for (int i = 0; i < weights.length; i++) { if (weights[i] <= 0) { throw new IllegalArgumentException("權重值必須大於0,如果需要排序包含小於等於0的權重列表,請使用 sortByWeightAndRandom()"); } if (i == 0) { xx[0] = new int[]{0, weights[i], i}; } else { final int start = xx[i-1][1]; xx[i] = new int[]{start, start + weights[i], i}; } } final int[] ok = new int[weights.length]; // 每次生成一個索引 for (int i = 0; i < weights.length; i++) { final int random = RANDOM.nextInt(xx[xx.length - 1 - i][1]); for (int j = 0; j < xx.length - i; j++) { // 如果某一段被選中之后,要將其從數軸中去除,並且其后的數據要依次往前移動,使得數軸不間斷 if (random >= xx[j][0] && random < xx[j][1]) { // 記錄選中段的原始索引 ok[i] = xx[j][2]; // 將后面的數據往前移動 for (int k = j; k < xx.length - i - 1; k++) { xx[k][1] = xx[k + 1][1] - (xx[k + 1][0] - xx[k][0]); xx[k][2] = xx[k + 1][2]; xx[k + 1][0] = xx[k][1]; } break; } } } return ok; } /** * 隨機進行排序. * * @param weights 需要排序的元素索引. */ public static int[] sortByRandom(int[] weights) { // 提取索引 int[] ins = new int[weights.length]; for (int i = 0; i < weights.length; i++) { ins[i] = i; } // 隨機排序 randomArray(ins); return ins; } /** * 根據權重進行排序,返回權重對應的索引. * 對於權重小於等於0的將放到最后進行隨機排序 * * @param weights 原始數據對應的權重 * @return 排序后的索引 */ public static int[] sortByWeightAndRandom(int[] weights) { if (weights.length == 0) { return new int[0]; } /// region 分離大於0和非大於0的元素 // 大於0的元素(子元素為長度為2的定長元素,第一個元素是原始權重值,第二個元素為該元素所在的原始索引) final ArrayList<Integer[]> moreWeights = new ArrayList<>(weights.length); // 小於等於0的元素(子元素為長度為2的定長元素,第一個元素是原始權重值,第二個元素為該元素所在的原始索引) final ArrayList<Integer[]> lessWeights = new ArrayList<>(weights.length); for (int i = 0; i < weights.length; i++) { int weight = weights[i]; final Integer[] item = new Integer[]{weight, i}; if (weight > 0) { moreWeights.add(item); } else { lessWeights.add(item); } } /// endregion // 最終排序結果 int[] ok = new int[weights.length]; // 排序大於0的元素 if (moreWeights.size() > 0) { final int[] int1 = new int[moreWeights.size()]; for (int i = 0; i < moreWeights.size(); i++) { int1[i] = moreWeights.get(i)[0]; } final int[] sort = sortByWeight(int1); for (int i = 0; i < sort.length; i++) { ok[i] = moreWeights.get(sort[i])[1]; } } // 排序小於等於0的元素 if (lessWeights.size() > 0) { final int[] int2 = new int[lessWeights.size()]; for (int i = 0; i < lessWeights.size(); i++) { int2[i] = lessWeights.get(i)[0]; } final int[] sort = sortByRandom(int2); for (int i = 0; i < sort.length; i++) { ok[i + moreWeights.size()] = lessWeights.get(sort[i])[1]; } } return ok; } /** * 隨機排序. * * @param vs 需要進行排序的數組. */ private static void randomArray(int[] vs) { for (int i = 0; i < vs.length; i++) { // 這里已經排過的不能再排,否則概率不正確 int p = RANDOM.nextInt(vs.length - i); int tmp = vs[i]; vs[i] = vs[p + i]; vs[p + i] = tmp; } } }