加权随机排序

时间:2014-05-31 14:47:39

标签: java random priority-queue weighted

问题:

我有重量的物品。重量越高,他们获得物品的机会就越大。我需要有一个干净,简单的方法来做这个基于核心java(没有第三方库,罐子等)。

我已经为2个项目做了这个,通过对权重求和然后使用该范围内的Math.random()随机选择一个数字。非常简单。但是对于大于2的项目,我可以在相同的范围内做更多的样本,或者我可以重新计算剩余项目的权重之和并再次选择(递归方法)。我认为可能有一些东西可以做得更快/更清洁。这段代码将被反复使用,所以我正在寻找一种有效的解决方案。

本质上,它就像随机重量排列一样。

一些例子:

  1. A的权重为1,B的权重为99.如果我使用此模拟运行模拟,我预计会有BA 99%的时间和{1}有1%的时间。{/ p>

  2. AB的权重为10,A的权重为10,而B的权重为80.如果我运行模拟,我会期待C成为排序中第一个项目的80%,在这些情况下,CA将有同等机会成为下一个角色。

    < / LI>

    任何有关正确方向的帮助或指示都将受到赞赏。

    额外详情:

    对于我的特定问题,有少量物品具有可能较大的重量。假设20到50个项目的权重以长形式存储,其中最小权重至少为1000.项目数量可能会增加很多,所以如果我们能找到不需要的解决方案要小的项目,这将是首选。

4 个答案:

答案 0 :(得分:4)

您有重量项目:

  • 项目A,重量42
  • 项目B,重量5
  • 项目C,重量96
  • 项目D,重量33

首先加总所有权重:42 + 5 + 96 + 33 = 176

现在选择一个随机数r,从0到权重之和:0&lt; = r&lt;我使用了整数,但如果需要你可以使用实数。

将r与权重定义的范围进行比较:

  • 0&lt; = r&lt; 42:选择项目A.
  • 42&lt; = r&lt; 47(= 42 + 5):选择项目B.
  • 47&lt; = r&lt; 143(= 47 + 96):选择项目C.
  • 143&lt; = r&lt; 176(= 143 + 33):选择项目D.

当您选择了第一个项目时,然后使用剩余的三个项目和所有权重的减少的总和重复该过程。继续重复,直到没有更多的项目可供选择。

答案 1 :(得分:2)

这似乎工作正常:

// Can do a weighted sort on weighted items.
public interface Weighted {
    int getWeight();
}

/**
 * Weighted sort of an array - orders them at random but the weight of each
 * item makes it more likely to be earlier.
 *
 * @param values
 */
public static void weightedSort(Weighted[] values) {
    // Build a list containing as many of each item to make up the full weight.
    List<Weighted> full = new ArrayList<>();
    for (Weighted v : values) {
        // Add a v weight times.
        for (int i = 0; i < v.getWeight(); i++) {
            full.add(v);
        }
    }
    // Shuffle it.
    Collections.shuffle(full);
    // Roll them out in the order required.
    int i = 0;
    do {
        // Get the first one in the shuffled list.
        Weighted next = full.get(0);
        // Put it back into the array.
        values[i++] = next;
        // Remove all occurrences of that one from the list.
        full.remove(next);
    } while (!full.isEmpty());
}

// A bunch of weighted items.
enum Heavies implements Weighted {

    Rare(1),
    Few(3),
    Common(6);
    final int weight;

    Heavies(int weight) {
        this.weight = weight;
    }

    @Override
    public int getWeight() {
        return weight;
    }
}

public void test() {
    Weighted[] w = Heavies.values();
    for (int i = 0; i < 10; i++) {
        // Sort it weighted.
        weightedSort(w);
        // What did we get.
        System.out.println(Arrays.toString(w));
    }
}

基本上对于每个要排序的项目,我根据需要将它添加到新列表中多次。然后我将列表洗牌并拉出顶部列表并从剩余的列表中清除所有出现的内容。

最后一次试运行:

[Rare, Common, Few]
[Common, Rare, Few]
[Few, Common, Rare]
[Common, Few, Rare]
[Common, Rare, Few]
[Few, Rare, Common]

这似乎是正确的。

注意 - 此算法至少在以下条件下会失败:

  1. 原始数组中不止一次包含相同的对象。
  2. 物品的重量非常大。
  3. 零或负重量几乎肯定会影响结果。
  4. 这实现了Rossum的想法 - 请务必给他这个算法的功劳。

    public static void weightedSort2(Weighted[] values) {
        // Calculate the total weight.
        int total = 0;
        for (Weighted v : values) {
            total += v.getWeight();
        }
        // Start with all of them.
        List<Weighted> remaining = new ArrayList(Arrays.asList(values));
        // Take each at random - weighted by it's weight.
        int which = 0;
        do {
            // Pick a random point.
            int random = (int) (Math.random() * total);
            // Pick one from the list.
            Weighted picked = null;
            int pos = 0;
            for (Weighted v : remaining) {
                // Pick this ne?
                if (pos + v.getWeight() > random) {
                    picked = v;
                    break;
                }
                // Move forward by that much.
                pos += v.getWeight();
            }
            // Removed picked from the remaining.
            remaining.remove(picked);
            // Reduce total.
            total -= picked.getWeight();
            // Record picked.
            values[which++] = picked;
        } while (!remaining.isEmpty());
    }
    

答案 2 :(得分:0)

public class RandomPriorityQueue {

    private TreeMap<Integer, List<WeightedElement>> tree = new TreeMap();
    private Random random = new Random();

    public void add(WeightedElement e) {
        int priority = random.nextInt(e.getWeight());
        if (tree.containsKey(priority)) {
            List<WeightedElement> list = new LinkedList();
            list.add(e);
            tree.put(priority, list);
        } else {
            List<WeightedElement> list = tree.get(priority);
            list.add(random.nextInt(list.size()), e);
        }
    }

    public WeightedElement poll() {
        Map.Entry<Integer, List<WeightedElement>> entry = tree.lastEntry();
        if (entry == null){
            return null;
        }
        List<WeightedElement> list = entry.getValue();
        if (list.size() == 1){
            tree.remove(entry.getKey());
        }
        return list.remove(0);
    }
}

当然,如果我们重写TreeMap以便它们允许我们添加复制键,我们会有更好的性能,我们会有更好的性能。

答案 3 :(得分:-1)

无论如何,对于N个项目,您将需要N-1个随机数(至少)。然后,让我们考虑通过随机数选择项目的有效方法。

如果项目不是太多,我会使用迭代方法,类似于递归方法。我会在项目中添加布尔标志,以便在之前的迭代中跳过所选项。当我在当前迭代中选择一个时,我将其标志设置为true,下一次我将从计算中跳过它。从总和中减去它的权重,然后进行下一次迭代。

如果项目是大数字,并且将多次使用同一组,那么不同的方法会更好。制作它们的排序列表,并在递归方法中使用此列表的副本。并且在每个递归步骤中 - 在其中进行二进制搜索,然后删除所选项目。

实际上,最后一次也可以迭代完成。