java.util.Random和Java中的递归

时间:2015-04-13 21:57:35

标签: java recursion

我想首先说这是一个更普遍的问题;没有一个与我给出的具体例子有关,而只是一个概念性主题。

示例#1: 我正在使用UUID.java创建一个真正随机的字符串。让我们说我永远不想生成相同的UUID。这里有一个关于这种情况的想法: (让我们假设我在顶部保存/加载列表 - 这不是重点)

Gist URL (I'm new to StackExchange- sorry!)

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class Example {

    /**
     * A final List<String> of all previous UUIDs generated with
     * generateUniqueID(), turned into a string with uuid.toString();
     */
    private static final List<String> PREVIOUS = new ArrayList<String>();

    /**
     * Generates a truly unique UUID.
     * 
     * @param previous
     *            A List<String> of previous UUIDs, converted into a string with
     *            uuid.toString();
     * @return a UUID generated with UUID.randomUUID(); that is not included in
     *         the given List<String>.
     */
    public static UUID generateUniqueID(List<String> previous) {
        UUID u = UUID.randomUUID();
        if (previous.contains(u.toString())) {
            return generateUniqueID(previous);
        }
        return u;
    }

    /**
     * Generates a truly unique UUID using the final List<String> PREVIOUS
     * variable defined at the top of the class.
     * 
     * @return A truly random UUID created with generateUniqueID(List<String>
     *         previous);
     */
    public static UUID generateUniqueID() {
        UUID u = generateUniqueID(PREVIOUS);
        PREVIOUS.add(u.toString());
        return u;
    }

}

示例#2:好的,也许UUID是一个不好的例子,所以让我们使用Random和double。这是另一个例子:

Gist URL

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Example2 {

    /**
     * A final List<Double> of all previous double generated with
     * generateUniqueDouble(), turned into a string with Double.valueOf(d);
     */
    private static final List<Double> PREVIOUS = new ArrayList<Double>();

    /**
     * The RANDOM variable used in the class.
     */
    private static final Random RANDOM = new Random();

    /**
     * Generates a truly unique double.
     * 
     * @param previous
     *            A List<Double> of previous doubles, converted into a Double
     *            with Double.valueOf(d);
     * @return a UUID generated with UUID.randomUUID(); that is not included in
     *         the given List<Double>.
     */
    public static double generateUniqueDouble(List<Double> previous) {
        double d = RANDOM.nextDouble();
        if (previous.contains(Double.valueOf(d))) {
            return generateUniqueDouble(previous);
        }
        return d;
    }

    /**
     * Generates a truly unique double using the final List<Double> PREVIOUS
     * variable defined at the top of the class.
     * 
     * @return A truly random double created with generateUnique(List<Double>
     *         previous);
     */
    public static double generateUnique() {
        double d = RANDOM.nextDouble();
        PREVIOUS.add(Double.valueOf(d));
        return d;
    }

}

重点:这是做这样事情最有效的方法吗?请记住,我给了你一些例子,所以他们很模糊。我最好不想使用任何库,但如果它们的效率确实很大,请告诉我们。

请在回复中告诉我您的想法:)

3 个答案:

答案 0 :(得分:6)

我建议您生成ID生成的ID序列号,而不是双打或uuids。如果您希望它们对最终用户显示为随机,请在base64中显示该数字的sha1。

答案 1 :(得分:1)

评论中已经讨论了一些问题。在这里总结和阐述它们:

您不太可能两次创建相同的double值。 There are roughly 7*1012 different double values(假设随机数生成器可以提供“大部分”)。对于UUID,自there are 2122 different UUIDs起,创建相同值两次的可能性更低。如果你创造了足够的元素来获得不可忽视的碰撞机会,那么你无论如何都会耗尽内存。

所以这种方法在实践中没有意义。


然而,从纯粹的理论角度来看:

效果

使用List进行此操作并非最佳选择。对您而言,“最佳情况”(以及最常见的情况)是新元素包含在列表中。但是为了检查元素是否被包含,这是最差的情况:你必须检查列表中的每个元素,只是为了检测新元素是否还没有出现。这被称为线性complexity,或简称为 O(n)。您可以使用不同的数据结构来检查是否包含元素可以更快地完成,即在 O(1)中。例如,您可以替换

private static final List<Double> PREVIOUS = new ArrayList<Double>();

private static final Set<Double> PREVIOUS = new HashSet<Double>();

表现和正确性

(一般指这里的递归方法)

<强>性能

从性能的角度来看,当迭代解决方案很容易被替换时,不应该使用递归。在这种情况下,这将是微不足道的:

public static double generateUniqueDouble(List<Double> previous) {
    double d = RANDOM.nextDouble();
    while (previous.contains(d)) {
        d = RANDOM.nextDouble();
    }
    PREVIOUS.add(d);
    return d;
}

(它可以写得更紧凑,但现在没关系)。

<强>正确性

这是更微妙的:当有很多递归调用时,你最终可能会得到一个StackOverflowError。所以你应该从不使用递归,除非你可以证明递归将结束(或更好:它将在几步之后结束) )。

但这是你的主要问题:

该算法存在缺陷。您无法证明它可以创建新的随机数。即使单个新元素已包含在PREVIOUS元素集合中,double(或UUID)值的可能性也非常低。但它不是零。并且没有什么能阻止随机数生成器无限制地创建随机数0.5,连续数万亿次。


(同样:这些纯粹是理论上的考虑因素。但是并不像第一眼看到的那样远离实践:如果你没有创建随机double值,而是随机byte值,然后,在256次调用之后,将不会返回“新”值 - 您实际上会收到StackOverflowError ...)

答案 2 :(得分:0)

使用哈希表比列表更好。生成候选值,检查哈希表中的冲突,如果没有冲突则接受它。如果使用列表,则生成新值是O(n)操作。如果使用哈希表,则生成新值是O(1)操作。