标题: [原创] JavaScript 快速组合算法
  本主题由 月影 于 2009-9-26 15:58 加入精华 
lifesinger
小恐龙
Rank: 3Rank: 3



UID 91925
精华 3
积分 465
帖子 84
威望 178
阅读权限 30
注册 2008-10-9
状态 离线
 
发表于 2009-9-25 18:04  资料  个人空间  主页 短消息  加为好友 
JavaScript 快速组合算法




function quick_combine(am) {
        var 
a.length""""= [], itpreg = /(0*)(1*)/;

        for(
0ni++) {
            
+= (0);
            
+= (1);
        }
        
r[0] = s;

        do {
            
t.indexOf("10");
            
t.slice(0p).replace(reg"$2$1") + "01" t.slice(2);
            
r.push(t);
        } while(
!== e);

        return 
r;
    } 



算法思路:

假设场景为从 [a, b, c, d, e] 里, 5 选 3.

先设定第一个和最后一个组合为:
s = 1 1 1 0 0 // a,b,c
e = 0 0 1 1 1 // c,d,e

Step1. 从左到右找出第 i 个组合中的 10, 转换为 01, 并将 01 左边的 1 全部移动到最左边,得到新组合 t
Step2. 如果 t 不为 e, 继续 Step1

按照以上思路,可以得到:

1 1 1 0 0
1 1 0 1 0
1 0 1 1 0
0 1 1 1 0
1 1 0 0 1
1 0 1 0 1
0 1 1 0 1
1 0 0 1 1
0 1 0 1 1
0 0 1 1 1

共 10 种组合

网上流传的递归方案(好像是月影写的?没找到源头):


function combine(arrnum) {
        var 
= [];
        (function 
f(tan) {
            if (
== 0) return r.push(t);
            for (var 
0a.length<= ni++) {
                
f(t.concat(a[i]), a.slice(1), 1);
            }
        })([], 
arrnum);
        return 
r;
    } 



两种方案的性能对比: combine-test.html

在 Firefox, Chrome, Safari 浏览器中,快速组合算法优势非常明显。
在 IE 浏览器中,计算量比较小时,快速组合算法依旧有优势;但计算量大时,无优势,甚至不如递归。

[ 本帖最后由 lifesinger 于 2009-9-25 21:31 编辑 ]


本帖最近评分记录
编程浪子   2010-1-16 15:22  威望  +10   很有创意
月影   2009-9-25 22:45  威望  +10   我很赞同
顶部
winter
超级版主
Rank: 8Rank: 8
5毛发一贴,千里不留行。


UID 65747
精华 11
积分 7582
帖子 3353
威望 3335
阅读权限 150
注册 2007-2-27
状态 离线
 
发表于 2009-9-25 18:09  资料  个人空间  短消息  加为好友 
帮你编辑了一下ubb代码





顶部
hax
无忧元老
Rank: 8Rank: 8



UID 76364
精华 2
积分 5024
帖子 364
威望 4661
阅读权限 90
注册 2007-9-24
来自 Shanghai
状态 离线
 
发表于 2009-9-25 18:13  资料  个人空间  主页 短消息  加为好友 
没来得及仔细看,不过是不是只适合字符串?

顶部
winter
超级版主
Rank: 8Rank: 8
5毛发一贴,千里不留行。


UID 65747
精华 11
积分 7582
帖子 3353
威望 3335
阅读权限 150
注册 2007-2-27
状态 离线
 
发表于 2009-9-25 18:36  资料  个人空间  短消息  加为好友 



function combine(arr,n)
    {
        var 
set=[];
        var 
resault=[];
        for(var 
i=0;i<arr.length;i++)
        {
            var 
l=set.length;
            for(var 
j=0;j<l;j++)
            {
                var 
tmp=set[j].slice();
                
tmp.push(arr[i]);
                if(
tmp.length==n)resault.push(tmp);
                else 
set.push(tmp);
            }
            
set.push([[arr[i]]]);
        }
        return 
resault;
    } 



这是我以前写的 测了一下 大数据量的时候会快一点 但是因为没有针对补集做优化 所以10选8明显慢了不少
楼主这组case写的不错

5 选 3, 循环 100 次:
DP组合算法 13 ms  共有 10 种组合
快速组合算法 7 ms  共有 10 种组合

10 选 3, 循环 100 次:
DP组合算法 66 ms  共有 120 种组合
快速组合算法 71 ms  共有 120 种组合

10 选 5, 循环 100 次:
DP组合算法 171 ms  共有 252 种组合
快速组合算法 149 ms  共有 252 种组合

10 选 8, 循环 100 次:
DP组合算法 254 ms  共有 45 种组合
快速组合算法 28 ms  共有 45 种组合

20 选 5, 循环 3 次:
DP组合算法 155 ms  共有 15504 种组合
快速组合算法 292 ms  共有 15504 种组合


本帖最近评分记录
月影   2009-9-25 23:39  威望  +10   不错




顶部
winter
超级版主
Rank: 8Rank: 8
5毛发一贴,千里不留行。


UID 65747
精华 11
积分 7582
帖子 3353
威望 3335
阅读权限 150
注册 2007-2-27
状态 离线
 
发表于 2009-9-25 18:37  资料  个人空间  短消息  加为好友 


QUOTE:
原帖由 hax 于 2009-9-25 18:13 发表
没来得及仔细看,不过是不是只适合字符串?

输出的是字符串形式的 但传入的参数是数组





顶部
月影
超级版主
Rank: 8Rank: 8



UID 24714
精华 9
积分 4243
帖子 1553
威望 1918
阅读权限 150
注册 2005-3-9
状态 离线
 
发表于 2009-9-25 22:43  资料  个人空间  主页 短消息  加为好友  QQ

顶部
月影
超级版主
Rank: 8Rank: 8



UID 24714
精华 9
积分 4243
帖子 1553
威望 1918
阅读权限 150
注册 2005-3-9
状态 离线
 
发表于 2009-9-25 23:36  资料  个人空间  主页 短消息  加为好友  QQ
不知道这样会不会更快些。。。


function quick_combine(am) {
    var 
a.length= [], itpreg = /(0*)(1*)/;
    var 
<< n;
    var 
= (y|(1<<m)-1).toString(2).slice(1);
    var 
= (y-1^(1<<n-m)-1).toString(2);

    
r[0] = s;

    do {
        
t.indexOf("10");
        
t.slice(0p).replace(reg"$2$1") + "01" t.slice(2);
        
r.push(t);
    } while(
!== e);

    return 
r;



[ 本帖最后由 月影 于 2009-9-26 11:57 编辑 ]

顶部
hax
无忧元老
Rank: 8Rank: 8



UID 76364
精华 2
积分 5024
帖子 364
威望 4661
阅读权限 90
注册 2007-9-24
来自 Shanghai
状态 离线
 
发表于 2009-9-26 02:40  资料  个人空间  主页 短消息  加为好友 
既然输出的是字符串,那性能比较就不公平了,得把从字符串产生数组的开销也算进去。

顶部
lifesinger
小恐龙
Rank: 3Rank: 3



UID 91925
精华 3
积分 465
帖子 84
威望 178
阅读权限 30
注册 2008-10-9
状态 离线
 
发表于 2009-9-26 13:39  资料  个人空间  主页 短消息  加为好友 
@winter:DP 算法性能不错,一个拼写错误 resault -> result
@月影:修改为 Math.power 来构建第一个和最后一个组合,对性能影响极微,主要性能点在循环体内

@hax: 直接输出字符串,是因为 原始数组 + 字符串结果 的信息量已充足
在我的实际使用场景下,可以很容易将需要的组合转换为实际数组,而不需要转换所有

因此我觉得,只要输出的信息充足,性能对比还是公平有意义的

顶部
cloudgamer
无忧元老
Rank: 8Rank: 8



UID 56188
精华 7
积分 5602
帖子 963
威望 3969
阅读权限 90
注册 2006-9-7
来自 顺德
状态 离线
 
发表于 2009-9-26 14:17  资料  个人空间  主页 短消息  加为好友 
很牛的思路啊
不过还是喜欢用数组的方式

[ 本帖最后由 cloudgamer 于 2009-9-26 14:24 编辑 ]





顶部
lifesinger
小恐龙
Rank: 3Rank: 3



UID 91925
精华 3
积分 465
帖子 84
威望 178
阅读权限 30
注册 2008-10-9
状态 离线
 
发表于 2009-9-26 14:23  资料  个人空间  主页 短消息  加为好友 
保持输出一致,在 Chrome 下保持优势,其它浏览器下无优势。

测试页面:combine-test.html


/**
     * 快速组合算法 输出数组版
     */
    
function quick_combine_b(am) {
        var 
a.length""""= [], itpreg = /(0*)(1*)/;

        
// 构建第一个和最后一个组合
        
for(0ni++) {
            
+= (0);
            
+= (1);
        }
        
s;
        
r[0] = b(sm);

        do {
            
// 将 t 中第一个 10 替换为 01, 并将 01 左边的 1 全部移动到最左端
            
t.indexOf("10");
            
t.slice(0p).replace(reg"$2$1") + "01" t.slice(2);
            
r.push(b(tm));
        } while(
!== e);

        return 
r;

        
// 将 01101 转换为 [b,c,e]
        
function b(sm) {
            var 
= [];
            for(var 
0len s.length1leni++) {
                if(
s.charAt(i) === "1") {
                    
r.push(a[i]);
                    if(
j++ === m) return r;
                }
            }
        }
    } 




顶部
lifesinger
小恐龙
Rank: 3Rank: 3



UID 91925
精华 3
积分 465
帖子 84
威望 178
阅读权限 30
注册 2008-10-9
状态 离线
 
发表于 2009-9-26 14:26  资料  个人空间  主页 短消息  加为好友 
纯粹从算法本身上讲,快速组合算法的循环次数是最少的,等于 组合数 - 1
性能受实现语言和运行平台的影响

顶部
月影
超级版主
Rank: 8Rank: 8



UID 24714
精华 9
积分 4243
帖子 1553
威望 1918
阅读权限 150
注册 2005-3-9
状态 离线
 
发表于 2009-9-26 15:23  资料  个人空间  主页 短消息  加为好友  QQ
把winter的算法简写了整理下
补集优化另外做就是了,彩票之类的也经常都是n >> m的,所以这个算法适用


function dp_combine(am){
    var 
= [[]];
    var 
= [];
    
    for(var 
0a.lengthni++){
        for(var 
0t.lengthkj++){
            var 
t[j].concat([a[i]]);
            
s.length t.push(s) : r.push(s);
        }
    }
    return 
r;



[ 本帖最后由 月影 于 2009-9-26 15:25 编辑 ]

顶部
月影
超级版主
Rank: 8Rank: 8



UID 24714
精华 9
积分 4243
帖子 1553
威望 1918
阅读权限 150
注册 2005-3-9
状态 离线
 
发表于 2009-9-26 15:33  资料  个人空间  主页 短消息  加为好友  QQ
这个组合的DP算法其实也是遍历一棵BTree(有剪枝)
dp_combine([a,b,c,d,e],3)

t: [[]], r:[]
->[[],[a]]. r:[]
->[[],[a],[b],[a,b]], r:[]
->[[],[c],[a],[a,c],[b],[a,b]], r:[[a,b,c]]
->......

顶部
hax
无忧元老
Rank: 8Rank: 8



UID 76364
精华 2
积分 5024
帖子 364
威望 4661
阅读权限 90
注册 2007-9-24
来自 Shanghai
状态 离线
 
发表于 2009-9-26 15:34  资料  个人空间  主页 短消息  加为好友 
对于组合算法评测的一点意见

主要有两个。

第一是输出要统一,否则不公平。尽管实际输出的信息是等价的,但是在不同输出形式中的转换也是有开销的。建议采取最靠近实际应用中的方式。

第二不要直接输出数组作为结果。因为这导致无法无法评测(也无法实际运用到)较大数字的情况。数字到达一定大小,光数组的存储空间就导致out of memory。

所以建议输出一个遍历器(iterator)。

综合上述两点,我建议统一的api可以是这样的:


var iter combinations(nk)  // n中取k

print('Start.\n')
var 
start = new Date()

try {  
   var 
count 0
   
while (true) {  
     
iter.next()  // 返回序号数组,如5取3,第一次返回[0,1,2],第二次返回[0,1,3]……直到最后一次返回[2,3,4]
     
count++
   }  
} catch (
e) {
  var 
end = new Date()
  if (
instanceof StopIteration)  //如果遍历到最后一个了,下一次调用next()就扔出StopIteration,当然也可以返回null表示没有下一个
    
print("End.\n");  
  else
    print(
"Unknown error: " e.description "\n");  
}


print(
count ', ' + (end start) + '\n'


[ 本帖最后由 hax 于 2009-9-26 15:42 编辑 ]

顶部
月影
超级版主
Rank: 8Rank: 8



UID 24714
精华 9
积分 4243
帖子 1553
威望 1918
阅读权限 150
注册 2005-3-9
状态 离线
 
发表于 2009-9-26 16:10  资料  个人空间  主页 短消息  加为好友  QQ

顶部
winter
超级版主
Rank: 8Rank: 8
5毛发一贴,千里不留行。


UID 65747
精华 11
积分 7582
帖子 3353
威望 3335
阅读权限 150
注册 2007-2-27
状态 离线
 
发表于 2009-9-26 17:18  资料  个人空间  短消息  加为好友 


QUOTE:
原帖由 lifesinger 于 2009-9-26 14:26 发表
纯粹从算法本身上讲,快速组合算法的循环次数是最少的,等于 组合数 - 1
性能受实现语言和运行平台的影响

算法分析应该关注时间复杂度
时间复杂度计算不是数循环次数 正则替换时间复杂度不是O(1)





顶部
lifesinger
小恐龙
Rank: 3Rank: 3



UID 91925
精华 3
积分 465
帖子 84
威望 178
阅读权限 30
注册 2008-10-9
状态 离线
 
发表于 2009-9-26 22:00  资料  个人空间  主页 短消息  加为好友 
感谢月影的总结和 winter 的指正:)
hax 的想法挺好,但够用了就不想折腾了:(

dp 算法还可以精简一行代码:


function dp_combine(am) {
        var 
= [[]], = [];

        for (var 
0a.lengthn; ++i) {
            for (var 
0t.lengthl; ++j) {
                (
t[j].length r).push(t[j].concat([a[i]]));
            }
        }
        return 
r;
    } 




顶部
hax
无忧元老
Rank: 8Rank: 8



UID 76364
精华 2
积分 5024
帖子 364
威望 4661
阅读权限 90
注册 2007-9-24
来自 Shanghai
状态 离线
 
发表于 2009-9-26 22:12  资料  个人空间  主页 短消息  加为好友 
嘿嘿,我就喜欢折腾,比如我觉得要测就测大点的,比如49选6(乐透啊。。。)

顶部
satans17
霸王龙
Rank: 6Rank: 6


UID 31115
精华 0
积分 1089
帖子 540
威望 552
阅读权限 70
注册 2005-6-11
来自 湖北
状态 离线
 
发表于 2009-9-26 22:32  资料  个人空间  短消息  加为好友  添加 satans17 为MSN好友 通过MSN和 satans17 交谈 QQ
马克~





http://bbs.51js.com/
顶部
 



当前时区 GMT+8, 现在时间是 2010-7-31 04:53
苏ICP备05080427号

Powered by Discuz! 5.5.0  © 2001-2007 51JS.COM
Processed in 0.332146 second(s), 10 queries , Gzip enabled

清除 Cookies - 联系我们 - 无忧脚本 - Archiver