算法问题:怎样得到n个元素的随机排列?

wzhiyuan 2005-03-17 03:20:46
大家学过排列组合的都知道,n个元素的全排列共有n!(n的阶乘)个。那么给定一个集合,怎样得到一个随机排列呢?这里要求写成 function getRndArray(array1){}的形式,array1是个数组,比如{0,1,2,a,b,c},返回的是元素随机排列后的数组,比如{2,a,c,0,1,b}。每次调用,得到的返回值不同。
方法应该很多,关键是效率。大家可以将数组取大一点(这里取为100吧),10000次循环,看看需要多长时间。
<script language=jscript>
<!--
function getRndArray(arr){
//这里写你的函数
}

var array1=new Array();
for(var i=0;i<100;i++){
//为了方便,这里将元素设为了0-99,100个全部是数字,
//而实际这些元素可以是任意字符串或对象,
//所以不要试图从这些元素本身的特点下手
array1[i]=i;
}

var jsBegin = new Date().getTime();
var jstest = 10000;
for(var i=0;i<jstest;i++){
result = getRndArray(array1);
}

var jsEnd = new Date().getTime();
document.write("做完这件事需要" + (jsEnd - jsBegin) + "豪秒<br/>");
-->
</script>
...全文
620 38 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
38 条回复
切换为时间正序
请发表友善的回复…
发表回复
JK_10000 2005-03-28
  • 打赏
  • 举报
回复
i+1
--->>>>
i+0.99999
JK_10000 2005-03-28
  • 打赏
  • 举报
回复
fason的代码也可以稍作修改,
效率不见得会提高,
但是算法合理性的证明可能要简单一点(直接用数学归纳法就可以了)

<script language="JavaScript">
<!--

var array1=new Array(10000);

Array.prototype.Random=function() {
for(var i=0;i<this.length;i++){
var rnd=Math.floor(Math.random()*(i+1))
this[i]=this[rnd];
this[rnd]=i;
}
return this
}
document.write(array1.Random())

-->
</script>
JK_10000 2005-03-28
  • 打赏
  • 举报
回复
回复人: LGEN() ( ) 信誉:86 2005-3-18 9:59:39 得分: 45
----
这个洗牌算法是(平均分布)随机的,(感觉是这样的,没有详细证明)
不过没理由会比fason的方法快

------------------

回复人: LGEN() ( ) 信誉:86 2005-3-18 10:02:32 得分: 0
”既然是两两交换,那100个元素只要交换50次就可以了,不用100次“
----
指导思想错误,
如果只换50次,那么0在洗牌后的位置一定不是第一个元素,
但是洗牌的结果是0在洗牌后的位置得第一个数的可能性是存在的(为:1/牌数)

------------------


回复人: xuzuning(唠叨) ( ) 信誉:671 2005-3-17 16:44:54 得分: 16
----
时间复杂度大,
JK_10000 2005-03-28
  • 打赏
  • 举报
回复
同意:“ 回复人: fason(咖啡人生) ( ) 信誉:705 2005-3-17 17:26:59 得分: 30 ”
这种算法每一次调换同时是两个随机操作:
1.取某个数,(平均分布的)随机扔向任意位置
2.(平均分布的)随机取任意一数,放到某个位置
相当于一次随机”抽”+“调“

一个循环下来,每一个数至少被随机或抽或调过一次,
抽调过一次的数的位置是(平均分布)随机的,不管它以后再被抽调多少次。

个人看法,以咨参考
JK_10000 2005-03-25
  • 打赏
  • 举报
回复
洗牌算法

这么久还没结贴!!
wzhiyuan 2005-03-25
  • 打赏
  • 举报
回复
明天结贴
wzhiyuan 2005-03-25
  • 打赏
  • 举报
回复
今天有空又看了一下,觉得LGEN() 的算法才是对的。
他的同 fason(咖啡人生) 的不同就在于取随机数。
fason(咖啡人生)的是这样取的,
var rnd=Math.floor(Math.random()*this.length)
(事实上在循环里象this.length这样每次都用属性是一种低效的用法,应该先赋给变量)
LGEN() 的取随机数是这样取的,
tIdx=Math.floor(Math.random()*tMax)
其中tMax是个每次循环递减的变量,说明每次交换到后面的元素相当于已经取出。这种思想和我原来的splice()是一样的。不过就象LGEN()解释的,splice的做法要分配内存,所以比较慢。

LGEN()后来的折半之类,则只是交换,没有了抽取的思想,所以我认为是不正确的。

flyskytoday(路要靠自己走.光风) 的算法,老实说,没看懂。比如里面有这样的句子:
if(array1[i]==nn)
这不是拿元素和随机数比较吗?我说过了,这里数组元素取为纯数字,只是为了构造方便,不应该从元素的特点着手。

xuzuning(唠叨) 的数组排序我不大懂。
function getRndArray(arr){
//这里写你的函数
return arr.sort(foo);
}
function foo(a,b) {
return Math.random()>0.5 ? 1 : -1;
}
各位有懂的热心人,加我qq28368672给我讲解一下,谢谢。

在上面各位,主要是LGEN()的基础上,我又将算法写了一下,算作总结,各位还有其它方法的可以继续跟贴。没有的话我17:00结贴。
function getRndArray2(arr0){
//对输入数组进行随机排列
len=arr0.length;
var rnd,swap;
for(var i=0;i<len;i++){
rnd=Math.floor(Math.random()*(len-i))+i;
//比之LGEN()的写法省了一个变量tMax,并相应少了一句tMax-=1,时间又少了一点,呵
swap=arr0[i];arr0[i]=arr0[rnd];arr0[rnd]=swap;
}
return arr0;
}
cxz7531 2005-03-18
  • 打赏
  • 举报
回复
to LGEN() ( )
-----------------------
但是折半后,第一个元素再也无法走到第二个位置
比如 原始数组是 0 1 2 3 4 5 6 7 8 9 在这个情况下 0不会落在 1 2 3 4的位置,而只会落在 5 6 7 8 9 的位置
flyskytoday 2005-03-18
  • 打赏
  • 举报
回复
写出来了
哈哈~~~~
<script language="javascript">

var array1=new Array();
var temp,n='';
for(var i=0;i<10;i++)
{
array1[i]=i;
}
var len=array1.length;
for(var j=0;j<len;j++)
{
var m=Math.random()*len;
var nn= Math.ceil(m);
for(var i=j;i<len;i++)
{
if(array1[i]==nn)
{
temp=array1[i];
array1[i]=array1[j];
array1[j]=temp;
}
}
}
for(var i=0;i<10;i++)
{
n+=array1[i];
}
alert(n)
</srcript>
zyjken 2005-03-18
  • 打赏
  • 举报
回复
在相同结果的情况下,当然要高效率的做法了.
杨米格 2005-03-18
  • 打赏
  • 举报
回复
to cxz7531(大花猫)
我的方法和fason(咖啡人生)最大的不同是我多个一个变量tMax.
这个变量,起到的作用就和你的splice一样.

比如有1,2,3,4四个数,一开始tMax=4
那末随机序号的范围是1..4,我随机挑一个数放到最后.
第二次时,tMax=3
那末随机序号的范围是1..3,我随机挑一个数放到最后.
第二次时,tMax=2
那末随机序号的范围是1..3,我随机挑一个数放到最后.
......
这样就不会产生被移动过的元素再次被移动的可能.就和splice的作用一样.可惜splice要重新分配内存,这就是瓶颈.
这也可以说明,交换次数为Array.length/2是正确的,否则每个元素要被移动2次,既然第一次就是随机的,那第2次就没有意义了


cxz7531 2005-03-18
  • 打赏
  • 举报
回复
我的办法虽然慢,但是可以通过严格分析证明:任意一个元素落在每个位置的概率是相等的。

fason(咖啡人生) 的办法,表面是可以的,但证明很困难

LGEN() ( )的办法跟fason(咖啡人生)本质是一样的,但是折半后,概率就不均等了,所以折半是不行的
杨米格 2005-03-18
  • 打赏
  • 举报
回复
function getRndArray(array1){
var tMax=array1.length
var tLen=Math.floor(tMax/2)
var i
var tSwap
var tIdx

for(i=0;i<tLen;i++){
tIdx=Math.floor(Math.random()*tMax)
tSwap=array1[tMax-1]
array1[tMax-1]=array1[tIdx]
array1[tIdx]=tSwap
tMax-=1
}

return array1
}
杨米格 2005-03-18
  • 打赏
  • 举报
回复
我的方法还有改进的地方,既然是两两交换,那100个元素只要交换50次就可以了,不用100次,呵呵,运行时间再次减半!
function getRndArray(array1){
var tLen=Math.floor(array1.length/2)
var tMax=tLen
var i
var tSwap
var tIdx

for(i=0;i<tLen;i++){
tIdx=Math.floor(Math.random()*tMax)
tSwap=array1[tMax-1]
array1[tMax-1]=array1[tIdx]
array1[tIdx]=tSwap
tMax-=1
}

return array1
}
杨米格 2005-03-18
  • 打赏
  • 举报
回复
只要不重新分配内存,速度就上来了,删除数组元素其实就在重新分配内存.我的方法是重新不分配内存的.

function getRndArray(array1){
var tLen=array1.length
var tMax=tLen
var i
var tSwap
var tIdx

for(i=0;i<tLen;i++){
tIdx=Math.floor(Math.random()*tMax)
tSwap=array1[tMax-1]
array1[tMax-1]=array1[tIdx]
array1[tIdx]=tSwap
tMax-=1
}

return array1
}
wzhiyuan 2005-03-18
  • 打赏
  • 举报
回复
不急,我的目的就是寻找高效的算法。各位的算法还没来得及细看。有兴趣的朋友顶一下呀。
flyskytoday 2005-03-18
  • 打赏
  • 举报
回复
再迟点结贴
让我也想想

中午看看自己能写个
cxz7531 2005-03-18
  • 打赏
  • 举报
回复
我这个算法明显比fason(咖啡人生) 的慢,也拿出来参考一下
<script language="JavaScript">
<!--
var array1=new Array(100);
for(var i=0;i<array1.length;i++){
array1[i]=i;
}

Array.prototype.Random=function() {
var ret=new Array(0);
for(var i=this.length;i>0;i--){
var rnd=Math.floor(Math.random()*this.length);
ret[ret.length]=this[rnd];
this.splice(rnd,1);
}
return ret;
}
var tstart=new Date().getTime();
var array2=array1.Random()
var tend=new Date().getTime();
alert(array2+"\n***************花费时间为 "+(tend-tstart)+" 毫秒")
//-->
</script>

wzhiyuan 2005-03-18
  • 打赏
  • 举报
回复
今天事比较多,没仔细研究,只大致看了一下。
cxz7531(大花猫) 的算法和我的几乎一样,大家看一下。
这个是我的,
function getRndArray(arr0){
//这里写你的函数
arr1=new Array();
len=arr0.length;
for(var i=0;i<len;i++){
rnd=Math.floor(Math.random()*arr0.length);
arr1[i]=arr0[rnd];
arr0.splice(rnd,1)
}
return arr1;
}
这个是 cxz7531(大花猫)的,
Array.prototype.Random=function() {
var ret=new Array(0);
for(var i=this.length;i>0;i--){
var rnd=Math.floor(Math.random()*this.length);
ret[ret.length]=this[rnd];
this.splice(rnd,1);
}
return ret;
}
不同仅在于我的是函数,cxz7531(大花猫)写成了数组的方法。效率吗,自然是写成数组方法快一点。但快得有限(不到10%)。但我们都犯了一个错误。只执行一次,代码没任何问题。但我们都是在源参数对象上直接改的(我的arr0.splice(rnd,1),大花猫的this.splice(rnd,1);),执行过一次之后,源参数数组变为了空。所以看似执行了10000次,实际上是一次。我将算法改了一下,加了一个temparray=arr0.slice(0),即复制到临时数组,然后在临时数组上改。经这样改了之后,执行1000次循环,cxz7531(大花猫)的数组方法执行时间是5400豪秒,我的函数是5800豪秒.
fason(咖啡人生) 的洗牌算法也是写成了数组方法,为了统一比较算法优劣,我改成了函数如下;
function getRndArray(arr0){//改自fason(咖啡人生) 的洗牌算法
tempArray=arr0.slice(0);
len=tempArray.length;
for(var i=0;i<arr0.length;i++){
var rnd=Math.floor(Math.random()*len);
var swap=tempArray[i];
tempArray[i]=tempArray[rnd];
tempArray[rnd]=swap;
}
return tempArray
}


同样1000次,执行时间是1100豪秒。

不过,就象cxz7531(大花猫)说的那样,我们的抽取算法(从集合中按顺序随机抽取元素)得到的新排列是和上一次排列完全独立的。而
fason(咖啡人生)的洗牌算法,所得到的新排列,则是在上一次的排列基础上交换而来,从理论上来说,有限次的交换,总不能得到“真正”的随机排列。不过,从应用的角度来讲,比较多次的交换,也可以得到比较满意的结果,而且效率又比较高,所以实用价值可能更大。
下面朋友的算法我还没细看,周末两天在家不能上网,所以只能到下周一再研究了。有兴趣的朋友,请继续支持。谢谢。
cxz7531 2005-03-18
  • 打赏
  • 举报
回复
to tigeryu(吴越小虎) ( ) 信誉:100 2005-03-18 14:05:00 得分: 0
楼上你的函数才执行了一遍,人家是执行一万遍的速度,麻烦你看仔细一点
------------------
我的函数执行一遍得到n个数的一个随机排列,耗时小于1ms。谁的速度是得到10000个随机排列的速度??
加载更多回复(18)

87,997

社区成员

发帖
与我相关
我的任务
社区描述
Web 开发 JavaScript
社区管理员
  • JavaScript
  • 无·法
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧