分治法:将问题分割,从一个大块分解为一个个小块,将问题规模变小。先解决小问题,在不停的整合结果集。
(1)合并排序
思路:把数组(假如长度为8)中每一个元素分为一个数组(因为这个数组只有一个元素,所以可以看成是有序数组)。然后不停的把两个有序组数组成一个有序数组(两个数组谁的第一个小就array_shift谁)。过程图:
代码:
1 <?php 2 $arr = [42, 15, 20, 6, 8, 38, 50, 12]; 3 4 function sliceArr($arr) //切分 5 { 6 $arr = array_chunk($arr, 1); 7 while (count($arr) != 1) 8 $arr = array_chunk ($arr, 2); 9 return $arr; 10 } 11 12 function mergeArr($arr) 13 { 14 if (is_array($arr[0][0])) { 15 $arr = [mergeArr($arr[0]), mergeArr($arr[1])]; 16 } 17 $c = count($arr[0]) + count($arr[1]); 18 for ($i=0; $i<$c; $i++) { //把两个有序数组合并成一个有序 19 $ret[] = (empty($arr[1]) || (! empty($arr[0]) && $arr[0][0] < $arr[1][0])) ? array_shift($arr[0]) : array_shift($arr[1]); 20 } 21 return $ret; 22 } 23 print_r(mergeArr(sliceArr($arr, 1)));
(2)快速排序
思路:不停的根据数组的第一个值把数组分为三部分(大于,小于和等于这个值),然后再拼到一起。
代码:
1 <?php 2 $arr = [42, 15, 20, 6, 8, 38, 50, 12]; 3 4 function quickSort($arr) 5 { 6 if (count($arr) < 2) 7 return $arr; 8 9 $max = $min = $mid = array(); 10 foreach ($arr as $v) { 11 $v <= $arr[0] ? $v == $arr[0] ? $mid[] = $v : $min[] = $v : $max[] = $v; 12 } 13 return array_merge(quickSort($min), $mid, quickSort($max)); 14 } 15 print_r(quickSort($arr));
(3)大数相加
思路:比如3431+235可以转换为(1+5)+(30+30)+(400+200)+(3000),可以用数组保存数组,比如3431可以表示为[1, 3, 4, 1],其中键值表示位数,比如$arr[0]表示个位(10的0次方),$arr[1]表示十位(10的1次方)。注意处理进位问题
代码:
1 <?php 2 function add($n1, $n2) 3 { 4 list($narr1, $narr2) = is_array($n1) ? [$n1, $n2] : [splitNum($n1), splitNum($n2)]; 5 $c = max([count($narr1), count($narr2)]); 6 for ($i=0; $i<$c; $i++) { //各位次依次相加 7 list($res[], $carry) = ($sum = $narr1[$i] + $narr2[$i] + $carry) >= 10 ? [$sum - 10, 1] : [$sum, 0]; 8 } 9 $carry && $res[] = $carry; //进位 10 return $res; 11 } 12 13 function splitNum($n) //例如:将213转换为[3, 1, 2] 14 { 15 $res = []; 16 while($n != 0) { 17 list($res[], $n) = [$n % 10, intval($n / 10)]; 18 } 19 return $res; 20 }
(4)大数相乘
思路:比如3431*235可以转换为(3*10^3 + 4*10^2 + 3*10^1 + 1*10^0)*(2*10^2 + 3*10^1 + 5*10^1),然后乘法分配律。比如计算3*10^3 * 3*10^1,只计算3*3就可以了,转换为数组之后,数组前面填充(3+1)个0,然后再利用上面的加法,把全部项加到一起
代码:
1 <?php 2 function add($n1, $n2) 3 { 4 list($narr1, $narr2) = is_array($n1) ? [$n1, $n2] : [splitNum($n1), splitNum($n2)]; 5 $c = max([count($narr1), count($narr2)]); 6 for ($i=0; $i<$c; $i++) { //各位次依次相加 7 list($res[], $carry) = ($sum = $narr1[$i] + $narr2[$i] + $carry) >= 10 ? [$sum - 10, 1] : [$sum, 0]; 8 } 9 $carry && $res[] = $carry; //进位 10 return $res; 11 } 12 13 function mult($n1, $n2) 14 { 15 list($narr1, $narr2, $res) = [splitNum($n1), splitNum($n2), []]; 16 foreach ($narr1 as $k1 => $v1) { 17 foreach ($narr2 as $k2 => $v2) { 18 //array_merge后面的是相乘的结果,前面的是进位 比如200 * 80 = 2*8 000 表示为[0, 0, 0, 6, 1] 19 $tmp = array_merge(array_fill(0, $k1 + $k2, 0), splitNum($v1 * $v2)); 20 $res = add($tmp, $res); 21 } 22 } 23 return $res; 24 } 25 26 function splitNum($n) //例如:将213转换为[3, 1, 2] 27 { 28 $res = []; 29 while($n != 0) { 30 list($res[], $n) = [$n % 10, intval($n / 10)]; 31 } 32 return $res; 33 } 34 print_r(mult(3278, 29926));
补充:也可以用字符串表示数字,然后以之前列式子的方法计算,比如:314 + 45,乘法同样(和上面不同的是,这次只是拆分了一个数;还有就是用字符串表示的并非数组):
3 1 4
+ 4 5
3 5 9
1 <?php 2 function fadd($p1, $p2) {return $p1 + $p2;} 3 function fmul($p1, $p2) {return $p1 * $p2;} 4 5 function cel($p1, $p2, $oper = 'fadd') 6 { 7 $p1 = str_split($p1); $p2 = str_split($p2); //初始化 8 end($p1); end($p2); 9 10 do { 11 $digit = $oper(current($p1), current($p2)); //这个函数做乘法的时候也可以用到 12 $res = (substr($digit, -1, 1) + $over) . $res; //个位 + 之前的进位 13 $over = substr($digit, 0, strlen($digit) - 1); //现在的进位 14 prev($p1); prev($p2); 15 } while((current($p1) !== false || current($p2) !== false) || $over); 16 return $res; 17 } 18 19 function mul($p1, $p2) 20 { 21 $p1 = str_split($p1); end($p1); //初始化,把p1作为被乘数(下面的那个) 22 $len = strlen($p2); 23 do { 24 $res[] = cel($p2, str_repeat(current($p1), $len), 'fmul'); //根据cel的逻辑 132*5 相当于cel(132, 555, 'fmul') 25 } while(prev($p1) !== false); 26 27 foreach ($res as $k => $v) { //这里把res里的值都加一起,但要注意需要乘上10的$k次方(就是在字符串后面添加k个0) 28 $count = cel($count, $v . str_repeat('0', $k)); 29 } 30 return $count; 31 } 32 33 $p1 = '5462'; $p2 = '56'; 34 echo cel($p1, $p2); //相加:5518 35 echo mul($p1, $p2); //相乘:305872
(5)大文件排序
说明:如何可以把一个1G的文件排序
思路:php的默认分配的内存是是128M,装不下1G的文件的。而且如果如果想排序,需要将其放在数组中,1G的文件转为数组结构要占据更多的空间(自测5M的文件转为数组差不多就100M了),执行sort要再多出5M左右。所以可以先将1G无序文件拆分成若干个5M有序小文件,然后再将小文件聚合。
代码:
1 <?php 2 ini_set('max_execution_time', 6000); 3 $root = 'D:/wangjianheng/'; 4 $sort_files_folder = 'sort_files/'; 5 $nums_filse_name = 'num.txt'; //总大小约1.1G, 共212100210行 6 $limit = 1024000; //php分配内存一般是128M, limit行放到数组中总共大概100M 7 $sort_file_name = "sort_file%d.txt"; 8 $sort_file_num = 1; 9 $res_file_name = 'res.txt'; 10 $step = 0; 11 12 $fp = fopen($root . $nums_filse_name, "r"); 13 while(! feof($fp)) { //拆分成若干个排序好的文件 14 $nums[] = (int) fgets($fp); 15 if ($step++ > $limit) { 16 $step = 0; 17 rsort($nums); //排序大概额外需要4M 18 writeText(sprintf($sort_files_folder . $sort_file_name, $sort_file_num++), $nums); 19 } 20 } 21 22 if ($nums) { //留了点尾巴 23 rsort($nums); 24 writeText(sprintf($sort_files_folder . $sort_file_name, $sort_file_num++), $nums); 25 } 26 fclose($fp); 27 28 $sort_files = scandir($root . $sort_files_folder); 29 foreach ($sort_files as $sort) { 30 if (strpos($sort, 'sort_file') !== false) { 31 $fp_obj = new stdClass(); 32 $fp_obj->fp = fopen($root . $sort_files_folder . $sort, 'r'); 33 $fp_obj->current = (int) fgets($fp_obj->fp); 34 $fps[] = $fp_obj; 35 } 36 } 37 38 $res_fp = fopen($root . $res_file_name, 'w'); //开始把已排序文件聚合 39 while ($fps) { 40 foreach ($fps as $k => & $fp) { 41 if (feof($fp->fp)) { 42 fclose($fp->fp); 43 unset($fps[$k]); 44 continue; 45 } 46 if (! isset($max) || $fp->current > $max->current) { 47 $max = & $fp; //取一个最大值 48 } 49 } 50 fwrite($res_fp, (string) $max->current . " "); 51 $max->current = (int) fgets($max->fp); 52 unset($max); 53 } 54 fclose($res_fp); 55 56 function writeText($fileName, & $nums) 57 { 58 $root = 'D:/wangjianheng/'; 59 $fp = fopen($root . $fileName, "w"); 60 foreach ($nums as $num) { 61 fwrite($fp, $num . " "); 62 } 63 $nums = []; 64 }