• 全部
  • 问答

讨论一个算法问题的优化

FancyMouse Microsoft SDE  2012-11-02 06:01:05
给定两个有序正整数数组a,b,长度都为n(n大概在20k~200k左右)。要找i,j,k使得a[i]+a[j]=b[k]。只求速度最快。优化复杂度最好,优化常数也行。
我现在是O(n^2)的。
for(i=0,k0=0;i<n;i++)
{
while(b[k0] < a[i]) k0++;
for(j=i,k=k0;j<n;j++)
{
while(b[k] < a[i] + a[j]) k++;
if(b[k] == a[i] + a[j]) return result;
}
}

实际数据的话a,b都是__uint128_t。最后是要在3930上跑,可以12线程。但是n>64k的时候会爆L3(32*200k=6.4M>2M)。所以如果能优化成方便多线程并且cache friendly的话最好。
...全文
731 点赞 收藏 13
写回复
13 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
还有多远 2013-11-03
引用 楼主 FancyMouse 的回复:
给定两个有序正整数数组a,b,长度都为n(n大概在20k~200k左右)。要找i,j,k使得a[i]+a[j]=b[k]。只求速度最快。优化复杂度最好,优化常数也行。 我现在是O(n^2)的。 for(i=0,k0=0;i<n;i++) { while(b[k0] < a[i]) k0++; for(j=i,k=k0;j<n;j++) { while(b[k] < a[i] + a[j]) k++; if(b[k] == a[i] + a[j]) return result; } } 实际数据的话a,b都是__uint128_t。最后是要在3930上跑,可以12线程。但是n>64k的时候会爆L3(32*200k=6.4M>2M)。所以如果能优化成方便多线程并且cache friendly的话最好。
问题:假设数组a[]和b[]都是从小到大的数序排的,要找到a[i] + a[j] = b[k]; 考虑: a[i] + a[j] = b[k];等价于a[i] = b[k] - a[j]; 对于每一个b[k],我们可以以log(n)的复杂度定位第一个比b[k]小的a[j],可以用upper_bound(a, a+n, b[k]),这样a[i]就得在a[j]的左边,从而可以以log(n)的复杂度定位a[i],可以用equal_bound(a, a+j, b[k]-a[j]); 则整体复杂度就是O(n * logn * logn)了,n很大的时候,应该还是有明显的改善的
回复
啊超29 2013-05-05
引用 4 楼 Prairial_0 的回复:
常数?
while(b[k0] < a[i]) k0++;
改成
while(b[k0] < a[i]*2) k0++;
还有怎么看都是O(n^3)的复杂度啊。。。
我也有这种感觉 有点像O(n^3)
回复
FancyMouse 2012-11-05
这两天一个大优化是把a[]通过mod一个适当的p进行分组。我的数据特征是a数组mod p的数量,要么是0,非零的部分数量比较平均。所以如果有k个部分非零,本来是O(n^2)的查找现在可以O(n^2/k+k^2)找出来。这个优化已经能把10小时的计算砍到3个半小时了。 不过编译速度开始有公司代码的倾向了。显然n越大p也该越大。而因为有%运算所以p最好是编译时常数。所以结果就是,整个代码就类似于公司里那种到处template的场景。编译耗时一下就好几倍。嘛不过编译多几秒省个几小时的计算时间还是划算的。 接下来是看对于每个n选什么p是最优的。
回复
showjim 2012-11-04
用汇编语言的话,试试把两次比较结果状态寄存器CF和ZF取出来,适当的位运算变成跳转地址,这样可以只有一个跳转,不知道可不可行。
回复
FancyMouse 2012-11-04
>我的理解是,等价于在a中找3个数使它们的和为M。 我好不容易把问题从这里转化过来你又转化回去了www 换一下搜索顺序倒是必须得做的。定下b[k]然后找a[i],a[j]的话b部分放不进cache也没关系,等于空下来一半的cache资源。这部分我一定得去写。 int128的加法很快的。慢主要慢在比较。我看能不能优化下这里。
回复
showjim 2012-11-04
引用 6 楼 litaoye 的回复:
期望这么低的话,是不是无解判断也挺重要的? 有个常数级的小优化就是,只用Int32来算,如果Int32中找到了,再到Int128中验证,不知这样减少数据量对缓存是否有些好处。
2^(15*3)=2^45,感觉int32冲突概率好大,至少得用int64验证。 a[i]+a[j]=b[k]=M-a[n-1-k] =>a[i]+a[j]+a[k1]=M =>(a[i]-M/3)+(a[j]-M/3)+(a[k1]-M/3)=M-(M/3*3) 我的理解是,等价于在a中找3个数使它们的和为M。 将a[i]-M/3,使得M=0/1/2,a分为正(V)负(S)两部分。 如果k1<>i/j那么有三种情况: 1.一正两负,这个验证是O(V*S) 2.两正一负,这个验证是O(V*S) 3.三正,这个验证应该是O(1)的 如果k1==i/j,这两种情况的验证都是O(n)。 我想的就这样,不知道又没有问题。
回复
FancyMouse 2012-11-04
>按说寻找下一个k时是能二分的,但估计还不如直接扫来的快,对复杂度也没什么帮助 嗯这点我写的时候就分析到了所以我就没改。 >期望这么低的话,是不是无解判断也挺重要的? 准确的说,一般情况下都是无解,存在一个解我这个问题就算做完了。 >有个常数级的小优化就是,只用Int32来算,如果Int32中找到了,再到Int128中验证 我这个也想过。不过现在想一想,换int32/64的好处应该是不用做128bit算术,这个可能能省不少时间。我过会试试。 另外数据还有个特点是存在M使得a[i]+b[n-1-i]=M。不过这个估计没啥用
回复
绿色夹克衫 2012-11-03
期望这么低的话,是不是无解判断也挺重要的? 有个常数级的小优化就是,只用Int32来算,如果Int32中找到了,再到Int128中验证,不知这样减少数据量对缓存是否有些好处。
引用 2 楼 FancyMouse 的回复:
只要找到一个就可以。找到所有的我觉得也差不到哪里去。我的数据是期望找到0个解的。
回复
Prairial_0 2012-11-03
引用 4 楼 Prairial_0 的回复:
常数? C/C++ code1while(b[k0] < a[i]) k0++; 改成 C/C++ code1while(b[k0] < a[i]*2) k0++; 还有怎么看都是O(n^3)的复杂度啊。。。
看错了,是平方级的。那么内层循环结束条件是j<n&&k<n... 按说寻找下一个k时是能二分的,但估计还不如直接扫来的快,对复杂度也没什么帮助
回复
Prairial_0 2012-11-03
常数?
while(b[k0] < a[i]) k0++;
改成
while(b[k0] < a[i]*2) k0++;
还有怎么看都是O(n^3)的复杂度啊。。。
回复
FancyMouse 2012-11-03
嗯数据还满足一个条件。存在一个M使得a[i]+b[n-1-i]=M for all i。不知道能不能用得上。我估计是没啥用的。
回复
FancyMouse 2012-11-03
只要找到一个就可以。找到所有的我觉得也差不到哪里去。我的数据是期望找到0个解的。
回复
绿色夹克衫 2012-11-02
只要找到1个就可以么?还是全找出来?
回复
发帖
数据结构与算法
创建于2007-08-27

3.2w+

社区成员

数据结构与算法相关内容讨论专区
申请成为版主
帖子事件
创建了帖子
2012-11-02 06:01
社区公告
暂无公告