先看一道面试题Q?/span>
长度?/span>n的数l,由数?/span>1?/span>nl成Q其中数?/span>a不出玎ͼ数字b出现两次Q其它的数字恰好出现一ơ。怎样通过只读遍历一ơ数l,扑և数字a?/span>b?/span>
׃只能遍历一ơ,在遍历数l?/span>arrӞ出 a?/span>b的差|以及a?/span>b的^方差Q通过解方E,卛_求得a?/span>b。具体做法ؓQ?/span>
设:
s1 = 1 + 2 + ... + n (= n * (n + 1) / 2)
s2 = arr[0] + arr[1] + ... + arr[n - 1]
r1 = 1 + 4 + ... + n^2 (= n * (n + 1) * (2 * n + 1) / 6)
r2 = arr[0]^2 + arr[1]^2 + ... + arr[n - 1]^2
c = a - b = s1 - s2
d = a^2 - b^2 = r1 - r2
昄Q?/span> a + b = (r1 - r2) / (s1 - s2)
Ҏa+b的值和a-b的|很容易就可算?/span>a?/span>b?/span>
法虽然单,但实现v来,却有一个很大问题:计算 s1?/span>s2?/span>r1?/span>r2q?/span>4个数Ӟ计算q程中可能出现溢出,造成l果不准。由于最l目的是Z计算?/span>c?/span>dQ一个改q的Ҏ是:
c = s1 - s2 = (1 - arr[0]) + (2 - arr[1]) + ... + (n - arr[n - 1])
d = (1 - arr[0]^2) + (4 - arr[1]^2) + ... + (n^2 - arr[n - 1]^2)
但这L做法Qƈ不能解决问题Q?/span>nE微大点Q照样存在溢出问题?/span>
那么怎样才能避免计算溢出呢?{案很简单,用模q算Q?/span>每进行一ơ加减运时Q都取结果ؓ原结果除以一个够大的常?/span>M的余数。这样加减运中Q就不会现现溢出问题。最后再?/span> c % M?/span>d % MQ推出c?/span>d的具体倹{比如说Q计?/span>s2改ؓ计算Q?/span>
s2 % M = ((((arr[0] % M) + arr[1]) % M + ...) % M + arr[n - 1]) %M
从表面上看,采用模运后Q计量会增加很多。但实际上,?/span>M取合适的值时Q计量q不会增加!Q?/span>
先回下计算机基本知识:两个?/span>N位(寄存器ؓN位)的二q制无符h?/span>a?/span>b相加Q若l果溢出了,CPU会怎么处理Q当然是溢出的那一位忽略掉Q可能还要设|下溢出标志Q,得到的结果实际上是:(a + b) mod 2^N?strong>无符h间的术q算Q本质上是模运?/span>。现在的CPU?strong>采用二补数来表示负整敎ͼ本质上也是运用模q算Q教U书二补数表示的负整数单定义ؓQ对正整数取反后?/span>1Q,q与无符h间的q算是一致的Q在实现上,比用其它ҎQ比如说一补数Q表C整数Q要优美易实现?/span>
?/span>32位^CQ?/span> -x mod 2^32 = 2^32 – x (x > 0)Q?/span>
因?/span>-1的二q制表示是Q?/span>0xFFFFFFFF
了解了这些,׃会奇?/span>C/C++标准的规定:无符h间的q算是模q算不会溢出Q有W号数{为无W数Q采用模q算后的倹{(Z兼容没采用二补数的机器,无符h转ؓ有符hӞ若无W号数的数DZ有符h可表C的范围Q结果是q_相关的。)
因而,在对32?/span>CPUq_Q可以先有W号数{为无W号敎ͼ再取M = 2 ^32。需要特别注意的是,应该采用多少位的无符h保存计算中用到的数|如何避免模运可能带来的问题Q?/span>
① 无符hcd的选择Q?/span>
a?/span>b的取D围ؓQ?/span>[1, n]Q?/span>
c % M = (a - b) % M 的取D围ؓQ?/span>[1, n] (a > b?/span>)Q?/span> [M - n, M - 1] (a < b?/span>)
q两个范围不能重叠,而因 n < M - n ?/span> 2 * n < M
?/span> M?/span>2^32的话Q且 n < 2^31Q?/span> 可以采用32位无W号数表C?/span>c的倹{?/span>
Ҏc % M值在哪一个范_可以定a > bq是a < bQ?/span>
׃q算q程中都是采用无W号数计,?/span> a < bӞ必须q行如下调整Q?/span>
c % M 调整?/span> (-c) % M
d % M 调整?/span> (-d) % M
q样才能保证l果的正性?/span>
② 用公式计所有数字的和、^方和Ӟ可能出现的问题:
模运满I (a * b) % M = ((a % M) * (b % M)) % M
?strong>不满?/strong>Q?/span> (a / b) % M = ((a % M) / (b % M)) % M
在计?/span> (n * (n + 1) / 2) % MӞ 不能写成Q?/span>
s = ((n * (n + 1)) % M / 2) % MQ?/span>
而应该写成:
if (n % 2 == 0) s = ((n / 2) * (n + 1)) % M
else s = (((n + 1) / 2) * n) % M
或者:s = (INT((n + 1) / 2) * (n + (n + 1) % 2)) % M Q其?/span>INT(x)为取数x的整数部份)?/span>
完整代码Q?/span>
看了几天asio文档Qȝ可以写点程序了。有些细节还是没弄明白,同步IO好像q不能设时Q服务器端采用异步IOQ客L则采用同步IO。传送文Ӟ不得不注意到 C/C++ 2G文g限制Q好像没通用的解x法?br />
先定义下头文Ӟl一下asio和boost_asio?/p>
要将一个数l的所有元素向左旋?/span>k位,通常有三U算法:
法1Q?/span>分组交换Q:
若a长度大于bQ将ab分成a0a1bQ交换a0和bQ得ba1a0Q只需再交换a1 和a0?/span>
若a长度于bQ将ab分成ab0b1Q交换a和b0Q得b0ab1Q只需再交换a 和b1?/span>
不断数l划分和交换Q直C能再划分为止。分l过E与求最大公U数很相伹{?/span>
d内存?n?*n?/span>
法2 (三次反{)
利用ba=(br)r(ar)r=(arbr)rQ先分别反{a、bQ最后再Ҏ有元素进行一ơ反转?/span>
d内存各约2*n?/span>
法3 Q用@环链Q?/span>
假设 n、k的最大公U数为MQ则所有序号ؓ (i + j*k) % n (0<= i < M, 0 <= j < n/M)的元素,构成M个@环链Qi值相同的在同一个@环链上)Q?每个循环链上的元素移动到前一个元素的元素Q就可以交换到最l结果上的位|,因而d只要d内存各nơ。(比如Q?1 2 3 4 5 6Q左U?位, 1 3 5 ?2 4 6分别构成两个循环链。)
事实?/span>C++标准法库提供了现成的函敎ͼrotate函数。按理说Q几U算法都比较单,~译器的库函数又是经q时间检验的Q效率即使比手写的差Q也不会差太多。但如果?/span>rotate函数q行试的话Q可能会发现标准库的版本慢得可不是一点点?/span>
?/span>VC 2010Q运行后面的试E序Q自定义函数Q采用算?/span>2Q要?/span>99msQ?/span>std::rotate却要1656ms。是库的实现者不懂得用这个简单的法吗?查下库的源代码,׃发现Q标准算法库中,?/span>C++的三UP代器Q前向P代器、双向P代器Q随问P代器Q,分别采用了上面三U算法。直接调用其内部的实玎ͼstd::_Rotat函数Q,重新试下,可得C面结果:
q代?/span> | 前向Q算?/span>1Q?/span> | 双向Q算?/span>2Q?/span> | 随机讉KQ算?/span>3Q?/span> |
旉Q?/span>msQ?/span> | 46 | 99 | 1651 |
Q?/span>GCC的,L版本号低?/span>4.5的进行测试)
从结果可以看出,效率是:法1 > 法2 >>> 法3?/span>
从理ZԌ法3只要d内存?/span>nơ,应该是效率最高的法。这在每ơ内存读写的开销相差不大时成立。但实际上,׃g限制Q?/span>CPU对内存的讉K采用分~存机制Q一U缓存容量很但讉K速度最快,存放E序的指令和最常用的数据,而二、三U缓存容量较大但讉K速度要慢很多?/span>CPU是无法绕q缓存直接访问内存数据(某些Ҏ指o可以不用一二三U缓存,但它也要用到其它专用~存Q,对不在缓存中的数据,必须先蝲入到~存中,q个操作是相当昂늚。对大数l来_不可能将所有数据都存放在缓存中Q而对内存的不q箋讉KQ?/span>CPU对内存定位的开销Q各U缓存间数据的调_反复Ud或移出数据到~存Q是巨大的,q就造成了算?/span>3的性能在该情况下非常差。测试发玎ͼk = 3Ӟ该算法的效率已l相当差了。对数l,管该算法读写次数少Q但׃各种法所用时间都很小Q这U优势很难体现出来。可以说Q?strong>法3在数学上是非怼的Q但是在实际应用中,是一U相当差的算法?/span>
对算法的选择Q不应该忽视内存因素?/span>在对随机讉Kq代器版本的roate实现上犯q个错误的,可不仅仅?/span>VCQ还有著名的STL Port?/span>GCCQ?/span>GCC?/span>4.5开?/span>libstdc++改用法1Qƈ做了些优化)Q以及新兴的libc++。(其它的编译器/库没用过Q也没有测试。)
另外Q测试时发现VC 2010的一?/span>bugQ前向P代器的实现版本,?/span>k = 0ӞE序直接挂了?/span>
试代码Q?/p>