算法问题:怎样得到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>
...全文
617 38 打赏 收藏 转发到动态 举报
写回复
用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)
书名:算法设计与分析 作者:王晓东 图书目录 第1章 算法引论 1.1 算法与程序 1.2 表达算法的抽象机制 1.3 描述算法 1.4 算法复杂性分析 小结 习题 第2章 递归与分治策略 2.1 速归的概念 2.2 分治法的基本思想 2.3 二分搜索技术 2.4 大整数的乘法 2.5 Strassen矩阵乘法 2.6 棋盘覆盖 2.7 合并排序 2.8 快速排序 2.9 线性时间选择 2.10 最接近点对问题 2.11 循环赛日程表 小结 习题 第3章 动态规划 3.1 矩阵连乘问题 3.2 动态规划算法的基本要素 3.3 最长公共子序列 3.4 凸多边形最优三角剖分 3.5 多边形游戏 3.6 图像压缩 3.7 电路布线 3.8 流水作业调度 3.9 0-1背包问题 3.10 最优二叉搜索树 小结 习题 第4章 贪心算法 4.1 活动安排问题 4.2 贪心算法的基本要素 4.2.1 贪心选择性质 4.2.2 最优子结构性质 4.2.3 贪心算法与动态规划算法的差异 4.3 最优装载 4.4 哈夫曼编码 4.4.1 前缀码 4.4.2 构造哈夫曼编码 4.4.3 哈夫曼算法的正确性 4.5 单源最短路径 4.5.1 算法基本思想 4.5.2 算法的正确性和计算复杂性 4.6 最小生成树 4.6.1 最小生成树性质 4 6.2 Prim算法 4.6.3 Kruskal算法 4.7 多机调度问题 4.8 贪心算法的理论基础 4.8.1 拟阵 4.8.2 带权拟阵的贪心算法 4.8.3 任务时间表问题 小结 习题 第5章 回溯法 5.1 回溯法的算法框架 5.1.1 问题的解空间 5.1.2 回溯法的基本思想 5.1.3 递归回溯 5.1.4 迭代回溯 5.1.5 子集树与排列树 5.2 装载问题 5.3 批处理作业调度 5.4 符号三角形问题 5.5 n后问题 5.6 0-1背包问题 5.7 最大团问题 5.8 图的m着色问题 5.9 旅行售货员问题 5.10 圆排列问题 5.11 电路板排列问题 5.12 连续邮资问题 5.13 回溯法的效率分析 小结 习题 第6章 分支限界法 6.1 分支限界法的基本思想 6.2 单源最短路径问题 6.3 装载问题 6.4 布线问题 6.5 0-1背包问题 6.6 最大团问题 6.7 旅行售货员问题 6.8 电路板排列问题 6.9 批处理作业调度 小结 习题 第7章 概率算法 7.1 随机数 .2 数值概率算法 7.2.1 用随机投点法计算л值 7.2.2 计算定积分 7.2.3 解非线性方程组 7.3 舍伍德算法 7.3.1 线性时间选择算法 7.3.2 跳跃表 7.4 拉斯维加斯算法 7.4.1 n后问题 7.4.2 整数因子分解 7.5 蒙特卡罗算法 7.5.1 蒙特卡罗算法的基本思想 7.5.2 主元素问题 7.5.3 素数测试 小结 习题 第8章 NP完全性理论 8.1 计算模型 8.1.1 随机存取机RAM 8.1.2 随机存取存储程序机RASP 8.1.3 RAM模型的变形与简化 8.1.4 图灵机 8.1.5 图灵机模型与RAM模型的关系 8.1.6 问题变换与计算复杂性归约 8.2 P类与NP类问题 8.2.1 非确定性图灵机 8.2.2 P类与NP类语言 8.2.3 多项式时间验证 8.3 NP完全问题 8.3.1 多项式时间变换 8.3.2 Cook定理 8.4 一些典型的NP完全问题 8.4.1 合取范式的可满足性问题 8.4.2 3元合取范式的可满足性问题 8.4.3 团问题 8.4.4 顶点覆盖问题 8.4.5 子集和问题 8.4.6 哈密顿回路问题

87,921

社区成员

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

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