在我的问题中,我有几个数字为1 - 3的数组,
[1,2,3],[1,2,3]
我将数组合并为一个完整的数组,
[1,2,3,1,2,3]
我需要在每次运行时随机化数组,这样就不会重复元素。
例如,这将起作用
[1,2,1,3,2,3]
但这不会。
[1,的 2,2 下,3,1,3]
我选择了1,2,3来简化它,但我的数组将由数字1 - 6组成。但这个想法仍然是相同的。是否有算法或简单的方法来实现这一目标?
答案 0 :(得分:1)
这是随机改组的启发式解决方案,不允许连续重复。它适用于列表,但它很容易将其传输到数组,因为它只进行交换,不需要移位操作。对于由数百万个元素和各种密度因子组成的列表,它似乎适用于大多数情况,但始终要记住,启发式算法可能永远找不到解决方案。它使用来自遗传算法的逻辑,但该版本仅使用一个单独的选择性突变(虽然它很容易将其转换为真正的遗传算法),但它很简单并且工作如下:
如果找到重复项,请尝试使用随机元素进行交换;如果不可能,请尝试使用之前的元素进行交换(反之亦然)。这里的关键点是用于交换元素的随机位置,以便在随机输出上保持更好的均匀分布。
这个问题已经以其他形式提出,但我还没有找到可接受的解决方案。不幸的是,作为大多数提议的答案(除了贪婪"广泛的重新洗牌,直到我们得到匹配或计算每个组合),这个解决方案不提供完美的均匀分布,但似乎最小化一些模式,:(仍然无法删除每个模式,如下所示。尝试并发布任何评论以获得潜在的改进。
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
//Heuristic Non-Consecutive Duplicate (NCD) Shuffler
public class NCDShuffler {
private static Random random = new Random();
//private static int swaps = 0;
public static <T> void shuffle (List<T> list) {
if (list == null || list.size() <= 1) return;
int MAX_RETRIES = 10; //it's heuristic
boolean found;
int retries = 1;
do {
Collections.shuffle(list);
found = true;
for (int i = 0; i < list.size() - 1; i++) {
T cur = list.get(i);
T next = list.get(i + 1);
if (cur.equals(next)) {
//choose between front and back with some probability based on the size of sublists
int r = random.nextInt(list.size());
if ( i < r) {
if (!swapFront(i + 1, next, list, true)) {
found = false;
break;
}
} else {
if (!swapBack(i + 1, next, list, true)) {
found = false;
break;
}
}
}
}
retries++;
} while (retries <= MAX_RETRIES && !found);
}
//try to swap it with an element in a random position after it
private static <T> boolean swapFront(int index, T t, List<T> list, boolean first) {
if (index == list.size() - 1) return first ? swapBack(index, t, list, false) : false;
int n = list.size() - index - 1;
int r = random.nextInt(n) + index + 1;
int counter = 0;
while (counter < n) {
T t2 = list.get(r);
if (!t.equals(t2)) {
Collections.swap(list, index, r);
//swaps++;
return true;
}
r++;
if (r == list.size()) r = index + 1;
counter++;
}
//can't move it front, try back
return first ? swapBack(index, t, list, false) : false;
}
//try to swap it with an element in a random "previous" position
private static <T> boolean swapBack(int index, T t, List<T> list, boolean first) {
if (index <= 1) return first ? swapFront(index, t, list, false) : false;
int n = index - 1;
int r = random.nextInt(n);
int counter = 0;
while (counter < n) {
T t2 = list.get(r);
if (!t.equals(t2) && !hasEqualNeighbours(r, t, list)) {
Collections.swap(list, index, r);
//swaps++;
return true;
}
r++;
if (r == index) r = 0;
counter++;
}
return first ? swapFront(index, t, list, false) : false;
}
//check if an element t can fit in position i
public static <T> boolean hasEqualNeighbours(int i, T t, List<T> list) {
if (list.size() == 1)
return false;
else if (i == 0) {
if (t.equals(list.get(i + 1)))
return true;
return false;
} else {
if (t.equals(list.get(i - 1)) || (t.equals(list.get(i + 1))))
return true;
return false;
}
}
//check if shuffled with no consecutive duplicates
public static <T> boolean isShuffledOK(List<T> list) {
for (int i = 1; i < list.size(); i++) {
if (list.get(i).equals(list.get(i - 1)))
return false;
}
return true;
}
//count consecutive duplicates, the smaller the better; We need ZERO
public static <T> int getFitness(List<T> list) {
int sum = 0;
for (int i = 1; i < list.size(); i++) {
if (list.get(i).equals(list.get(i - 1)))
sum++;
}
return sum;
}
//let's test it
public static void main (String args[]) {
HashMap<Integer, Integer> freq = new HashMap<Integer, Integer>();
//initialise a list
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(1);
list.add(2);
list.add(3);
/*for (int i = 0; i<100000; i++) {
list.add(random.nextInt(10));
}*/
//Try to put each output in the frequency Map
//then check if it's a uniform distribution
Integer hash;
for (int i = 0; i < 10000; i++) {
//shuffle it
shuffle(list);
hash = hash(list);
if (freq.containsKey(hash)) {
freq.put(hash, freq.get(hash) + 1);
} else {
freq.put(hash, 1);
}
}
System.out.println("Unique Outputs: " + freq.size());
System.out.println("EntrySet: " + freq.entrySet());
//System.out.println("Swaps: " + swaps);
//for the last shuffle
System.out.println("Shuffled OK: " + isShuffledOK(list));
System.out.println("Consecutive Duplicates: " + getFitness(list));
}
//test hash
public static int hash (List<Integer> list) {
int h = 0;
for (int i = 0; (i < list.size() && i < 9); i++) {
h += list.get(i) * (int)Math.pow(10, i); //it's reversed, but OK
}
return h;
}
}
这是一个示例输出;很容易理解非均匀分布的问题。
独特输出:6
EntrySet:[1312 = 1867,3121 = 1753,2131 = 1877,1321 = 1365,1213 = 1793,1231 = 1345]
洗牌好了:是的
连续重复:0
答案 1 :(得分:0)
您可以使用Collections.shuffle随机化列表。在while循环中执行,直到列表通过约束。
答案 2 :(得分:0)
如果数组相对较小,那么仅仅将两个数组合并,随机化然后检查数字就不会太难,如果数字太多,只需将一个数移一遍或者再次随机化。< / p>
答案 3 :(得分:0)
我没有预先编写的算法(这并不代表一个不存在的算法),但问题很容易理解,而且实现很简单。
我将提供两个建议,具体取决于您是否要构建有效数组,或者是否要构建数组,然后检查其有效性。
1 - 创建一些集合(Array,ArrayList等),其中包含将包含在最终数组中的所有可能值。获取其中一个值并将其添加到数组中。将该值的副本存储在变量中。从可能的值中获取另一个值,检查它是否与之前的值不相等,如果它有效,则将其添加到数组中。
2 - 创建一个包含所需值数的数组。对于除最后一项之外的所有项目,检查项目n!=项目n + 1。如果您未通过其中一项检查,请为该位置生成新的随机值,或者从该位置的值中添加或减去某个常量。检查完此阵列中的所有值后,您就知道自己拥有一个有效的数组。假设第一个和最后一个值可以相同。
答案 4 :(得分:0)
我能想到的最佳解决方案是计算每个值的出现次数,从逻辑上为每个不同的值创建一个“池”。
然后,您可以从任何不是上一个选择值的池中随机选择一个值。随机选择按池大小加权。
如果池的大小超过所有剩余值的一半,则必须从该池中进行选择,以防止在最后重复。
通过这种方式,您可以快速生成结果而无需任何形式的重试或回溯。
示例(使用字母作为值来澄清与计数的差异):
Input: A, B, C, A, B, C
Action Selected Pools(Count)
A(2) B(2) C(2)
Random from all 3 pools A A(1) B(2) C(2)
Random from B+C pools C A(1) B(2) C(1)
Random from A+B pools (1:2 ratio) A A(0) B(2) C(1)
Must choose B (>half) B A(0) B(1) C(1)
Random from A+C, so C C A(0) B(1) C(0)
Must choose B (>half) B A(0) B(0) C(0)
Result: A, C, A, B, C, B