抽牌算法
打听到明天中午的密码学实验内容,今天晚上在寝室先行完成Java版,明天上完课再补上C++版。
实验要求很简单:生成一个元素不重复的随机序列,长度由用户输入,但是元素大小需在0~10000之间,生成这种序列的算法有个比较简单易懂的名字:抽牌算法。
显然,这个算法的核心在两点:随机 & 不重复。因此可以先创建一个有10000个元素的数组,随机取出一个元素,将其放在一边,而放在一边的方式就是与最后一个元素对换,之后取随机数的范围就排除对换至数组后端的元素,同时将选出的元素存入一个新的数组,即为生成序列。
这里我就先直接附上源码:
/**
* @author 鞠文婷
* 抽牌算法
*/
package getRandomInt;
import java.util.Scanner;
public class getRandomInt {
public static int[] getRandomInt(int n) {
int[] result = new int[n]; //用于存储输出序列
int[] intArray = new int[10000];
// 初始化原数组
for(int i=0;i<intArray.length;i++) {
intArray[i] = i+1;
}
// 获取不重复的随机数数组
for(int i=0;i<n;i++) {
int c = getRandom(0,10000-i,n);
int index = c;
swap(intArray,index,10000-i-1);
result[i] = intArray[10000-i-1];
// System.out.println(i+" "+result[i]);
// if(result[i] == 9999)
// System.out.println("--------------------------------");
}
// 判断是否有重复元素
for(int m=0;m<result.length;m++) {
for(int a=m+1;a<result.length;a++) {
if(result[m] == result[a])
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"+result[m]+" m = "+m+" a = "+a);
}
}
return result;
}
// 交换数组内元素
public static void swap(int[] array,int x,int y) {
int temp = array[x];
array[x] = array[y];
array[y] = temp;
}
// 获取随机数(方法其一~)
public static int getRandom(int min,int max,int n) {
int result = min + new Double(Math.random() * (max - min)).intValue();
// System.out.println("result = "+result);
return result;
}
public static void main(String args[]){
System.out.println("Please enter an integer between 0 to 10000:");
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println("The random list is: ");
getRandomInt(n);
}
}
鉴于抽牌算法没有一个固定的套路,因此我这里再附上两个思路:
I、每次抽取一个随机数后,定义一个新的数组,存储剩余元素,显然这种方法效率太低,而且大致思路与上面相同,没有必要;
II、假设输出序列为n,则将0~10000分成n部分,从每部分中随机抽取一个元素,组成最终的序列。关于这个方法,可能很多人乍眼看上去会觉得所取元素不够随机,但实际上,从概率论的角度来说,这种情况下每个元素被抽到的概率是一样的,只不过可能的情况变少了。如果大家心里还是有隔阂的话,也可以在原数组上做点改动再分组,毕竟没有一个固定的格式,只要符合随机&不重复的要求,抽牌算法怎样捣腾都是可以的。
这里就不附上源码了,如果有更好的思路,欢迎大家讨论围观,相互学习~
算法思想很简单,但还是有些东西值得学习的,比如说,生成随机序列。这里介绍地挺详细,我只是采用了一种很简单的方法。
Java版的算法收获了不少人的建议,因此C++版我采用了一个新的算法思想:每获取一个元素,则将其值变为范围之外的值,比如我的范围是1~x,则将取过的值变为x+1,之后重新取随机数,如果它对应的元素是范围之外的则重新取,这也算是标志法的一个变种吧,源码如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
int getRandomInt(int n);
int getRandom(int min,int max);
int x; // 定义总数组长,即范围上限,默认下限为1(10000)
// 生成随机序列
int getRandomInt(int n) {
cout<<"Please enter x:"<<endl;
cin>>x;
cout<<"Please enter the length of list you want to print:"<<endl;
cin>>n;
int *array = new int[x];
for(int i=1;i<=x;i++) {
array[i] = i;
}
int *result = new int[n]; //存储输出序列
for(int j=0;j<n;j++) {
int index = getRandom(1,x);
if(array[index] < x+1) {
result[j] = array[index];
cout<<j+1<<"\t"<<result[j]<<endl;
}
else j--;
array[index] = x+1;
}
return 0;
}
// 选取min~max的随机数
int getRandom(int min,int max) {
int random = rand() % (max-min+1) + min;
return random;
}
int main() {
int n;
cout<<getRandomInt(n);
return 0;
}
最后附上程序流程图一份儿:
想要“正宗”的标记法做法,请看评论~
欢迎批评指正!~