在学校里,我们有一项任务,我们必须创建一个有20个插槽的数组,并用随机数生成器填充1到100之间的任意数字,类似于这样的东西:
int arr[] = new int[20];
for (i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 100) + 1
}
然后我们必须找到这个随机数生成器制作的阵容中最大的一个。类似:
int maximum = arr[0];
/*a random integer that's initialized after the line-up is made,
*since initializing ahead of that would be problematic,
*and then starts with the first value in the array
*/
for (i = 0; i < arr.length; i++) {
if (maximum < arr[i]) {
maximum = arr[i]
}
}
然后我们只打印数组的内容,以及数组中新找到的最大数字。我在这里也有点创意;纯粹出于审美原因,你会看到:
System.out.println("The line-up of random numbers generated into the array:");
for (i = 0; i < arr.lenghth; i++) {
if (i == arr.length - 1) {
System.out.println(arr[i]);
} else {
System.out.print(arr[i] + ", ");
}
}
(如果你没有抓住它,它会将数组的所有元素打印在同一行,用逗号和空格分隔。然后,当它到达末尾时,它会在同一行都打印它,但任何超出它的内容都会在下一行,而且它也不会打印逗号和/或空格。纯粹是为了美观。)
这就是我变得更有创造力的地方;我想把所有的整数按增长顺序排列。经过一些研究,插入排序是相当琐碎的。(如果你不介意的话,我会跳过为此键入的代码,因为1)它起作用了,2)它有一些非英语短语,因为我不是土生土长的英国人,因此我的变量命名也不是英语的,尤其是如果这是要发回给老师的话,3)坦率地说,Stack Overflow中内置的代码采样器不太好——来自NetBeans——但不管怎样。重点是,这里没有麻烦。)
我的麻烦开始了,当我看到那个一大堆数字都是一样的。我想"不如我们改变一下">和彻底的灾难。它不仅没有改变数字,而且做得更多。如果你问我的话,那就是因果报应。
无论如何,我去了"好吧,让我们做一些调查">我的研究机器谷歌没有正确的解决方案,或者说没有完全相同的解决方案。即:
for (i = 0; i < arr.length; i++) {
for (j = i+1; j < arr.length; j++)
if (arr[i] == arr[j]) {
arr[i] = (int) (Math.random() * 100) + 1;
}
}
}
现在,在你去"只需更改>gt;arr[i]<lt;至>gt;arr[j]<lt"> 就在我思考的时候,我想;为什么我不能一个接一个地浏览所有的值,然后再次浏览所有值,并将它们与我最初选择的值进行比较">
此外,如果有人感兴趣,这里是整个代码;但没有损坏的代码:
package searchformax;
public class SearchForMax {
public static void main(String[] args) {
//initialization
int arr[] = new int[20]; //array, and it's set length
//random number generation
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 100) + 1;
}
int maximum = arr[0];
/*a random integer that's initialized after the line-up is made,
*since initializing ahead of that would be problematic,
*and then starts with the first value in the array
*/
//the search for the actual maximum number
for (int i = 0; i < arr.length; i++) {
if (arr[i] > maximum) {
maximum = arr[i];
}
}
//userfriendliness
System.out.println("The line-up of random numbers generated into the array:");
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
System.out.println(arr[i]);
} else {
System.out.print(arr[i] + ", ");
}
}
//presenting the largest nummber
System.out.println("The largest number from these: " + maximum);
}
}
再说一遍,这并不是绝对必要的,只是为了满足我对更多知识的渴望——说真的,我们太慢了,尤其是在我的国家,每个高中生都被送回家在家上学;是的,你可以说;小心自我教育",但是,别担心。而且,到了发送的时候,我不会把它和额外的代码一起发送,当我发送的时候就把它去掉了。尽管如此,我还是很好奇,我的问题在哪里,或者是由于Java的性质,它是不能解决的,还是什么。
滥用Math.random()
arr[i] = (int) (Math.random() * 100) + 1
实际上,这不是正确的方法。问题是:如果我给你一个标准的骰子(有6个边),我要求你给我一个在1到4之间的均匀分布的随机数,然后你做这个算法:"好吧,我会掷骰子,然后如果我得到5或6,我会把这些"溢出"变成1和2,所以如果我掷1或5,我会回答1,2/6我会回答2,对于3我会回答3,对于4我会回答4;。您可以清楚地看到这是而不是统一的。同样的问题,除了哪个大得多的数字,也适用于你正在做的事情:最终,计算机是二进制的,Math.random()
可能会给出特定的非无限数量的答案。如果这不能完全被100整除(事实并非如此),那么有些数字会比其他数字出现得更频繁。这不会是很多,但它不是真正的随机。
这就是为什么这是正确的方式:
arr[i] = rnd.nextInt(100) + 1
其中rnd
是Random rnd = new Random();
的结果(您创建Random的一个实例一次,然后不断调用它以获得越来越多的随机数)。
实际问题——随机但唯一的数字
深入到你的故事中,我们得到了实际的问题,它似乎是:
我想用从1到R(包括1和R)之间选择的随机数填充一个N大小的数组,但不能重复。
其中N=20,R=100。
您的算法存在问题(重新滚动重复项)
你(糟糕地)实现的策略是不断检查你得到的数组是否是唯一的,如果不是,则重新滚动数字。不幸的是,您没有考虑到重新滚动20大小数组中的第15个数字可能会导致它等于第1个数字。从理论上讲,你可能需要很长时间才能得到一个独特的数组。假设N=100和R=90,那么它将永远运行(当然,你不能生成100个介于1和90之间的唯一数字)。像您写的这样的for循环不能永远运行,因此这是必然错误的:您的方法需要一些理论上可以永远运行的循环构造。
正确的方法不是先把20个数字累加起来,然后再开始检查。它是滚动1个数字,然后检查这个数字是否是可加的(唯一的),这很简单:你还没有数字,它是可加。然后继续:滚动第二个插槽的数字,但继续滚动,直到数字是唯一的。
请注意,当N等于R或只小一点时,这种算法效率很低:如果N=1000且R=1000,那么对于最后一个数字,你实际上要滚动大约1000次(每次都要对照999个条目进行检查),直到你运气好,滚动出一个仍然可用的数字。当然,如果N低于R,它将永远运行。您可能应该在某个地方编写一个if
来提前中止。
还有其他更简单的方法:
枚举和混洗
枚举"pick range"中的每个数字(因此,对于您的问题,请列出[1, 2, 3, 4, 5, .... 100]
。然后使用例如Collections.shuffle
打乱此列表,然后只从中选择前N个数字,这将为您提供N个均匀分布的和介于1和R之间的唯一随机数。
除非"挑选范围"非常大(比如100k以上),否则这一方法效果很好,因为如果它非常大,你需要制作一个非常大的列表,并等待相对较长的时间来打乱该列表。
构建和洗牌
另一种算法有相反的效果:如果R大而N小,它效果很好:
其目的是以智能的方式保持有效选项的实际"数字线":让我们回到一个非常简单的R=6,N=2的情况。对于第一个数字,有效选项为[1, 2, 3, 4, 5, 6]
。假设它是4。对于第二个数字,有效选项仅为[1, 2, 3, 5, 6]
。换言之,第二卷需要.nextInt(5)
,而不是nextInt(6)
,但需要映射nextInt(5)
的结果。可能的输出是[0, 1, 2, 3, 4]
,并且需要映射到[1, 2, 3, 5, 6]
。
为了有效地做到这一点,你需要按排序顺序存储你已经选择的数字(假设R=100,N=20,所以你有18和10。如果nextInt()
生成16,则需要将其调整为19;你不知道,在已经选择的号码列表中出现18意味着你需要增加下一个Int调用的结果,直到你增加它之后,因为已经选择了10,这就是为什么需要对它进行排序)。你可以有效地保持列表的"自排序"(它会插入正确的位置),而不是使用插入排序-请参阅TreeSet<Integer>
,这是一种有效的数据结构。它涉及到树,变得非常复杂。
然后,在最后,您有一个从1-R中随机选择的N个数字的排序列表,没有重复。要使其"完全随机",只需打乱此列表即可。
这个算法比以前的算法复杂得多。
如果N小且R大,则完全灵活的"始终有效"实现使用第二种算法,如果R小或N和R在同一范围内,则使用第一种算法,并且如果N>R.