zoukankan      html  css  js  c++  java
  • 算法笔记-动态规划

       动态规划:通过不停的缩小问题规模最终找到解。几乎都是求极限值。有以下几个特征:

          ①规模n问题和规模为n-1的问题,两个最优解可能相同也可能只差第n个元素,换句话说就是第n个元素是否属于最优解。如果不属于最优解那么规模为n的问题就可以换算为规模n-1的问题;如果属于最优解,那么规模为n的问题就可以换算为规模n-1的问题再补上第n个元素(可能有时候是n-k,但大部分是n-1)   

          ②规模缩减到一定程度(一般是0或者1的时候)有明确解,或者求解的方法 ,此类问题大部分用递归 

          ③缩减规模的方式可能有多种,再加上有时并不能直接判断第n个元素是否属于最优解,一般要用min或max取最优

    (1)最长公共子序列

      说明:假如有一个字符串是abcde。那么他的子序列包括a,ab,bc,ad,abc,abe等等(这个不一定非要连续,只要每个字符取自该字符串并且保持前后顺序就可以)。现在给定两个字符串cnblogs,belong,他们的公共的序列包括bl,bo等等,求其最长公共子序列

      思路:其最长的子序列是blog。先理解一下书上的给的几个结论:

        

      最后的总结很重要,逻辑就是根据这个来的。

      代码:

     1 <?php
     2 $str1 = 'cnblogs';
     3 $str2 = 'belong';
     4 function findStr($str1, $str2)
     5 {
     6     if ($str1 == '' || $str2 == '')     //i=0或j=0         
     7         return '';              
     8     
     9     while ($str1 != '' && $str2 != '')
    10     {
    11         if ($str1[strlen($str1) - 1] == $str2[strlen($str2) - 1]) {     //X(i) = Y(j)
    12             $res = substr($str1, -1) . $res;
    13             list($str1, $str2) = [substr($str1, 0, -1), substr($str2, 0, -1)];            
    14         } else {            //X(i) != Y(j)
    15             list($r1, $r2) = [findStr(substr($str1, 0, -1), $str2), findStr($str1, substr($str2, 0, -1))];
    16             $res = strlen($r1) > strlen($r2) ? $r1 . $res : $r2 . $res;     //取max
    17             break;
    18         }
    19     }
    20     return $res;  
    21 }
    22 echo findStr($str1, $str2);    

       补充:忽然发现有些情况可能有多解(上面的逻辑只会返回其中一个解),比如cnb1a2ogs和beaon12g,最长公共子序列不仅有n12g还有b12g等等。补充下面的代码(主要16行解决多个解的问题,之前只是取了一个值)。

      代码:

     1 <?php
     2 $str1 = 'cnb1a2ogs';
     3 $str2 = 'beaon12g';
     4 function findStr($str1, $str2)
     5 {
     6     if ($str1 == '' || $str2 == '')     //i=0或j=0         
     7         return [];              
     8     
     9     while ($str1 != '' && $str2 != '')
    10     {
    11         if ($str1[strlen($str1) - 1] == $str2[strlen($str2) - 1]) {     //X(i) = Y(j)
    12             $res = substr($str1, -1) . $res;
    13             list($str1, $str2) = [substr($str1, 0, -1), substr($str2, 0, -1)];            
    14         } else {                                                        //X(i) != Y(j)
    15             list($r1, $r2) = [findStr(substr($str1, 0, -1), $str2), findStr($str1, substr($str2, 0, -1))];
    16             $tmp = strlen($r1[0]) > strlen($r2[0]) ? $r1 : (strlen($r1[0]) == strlen($r2[0]) ? array_merge($r1, $r2) : $r2);  //取max
    17             foreach ($tmp as $v) {
    18                 $ret[] = $v . $res;
    19             }
    20             break;
    21         }
    22     }
    23     return isset($ret) ? array_unique($ret) : array_filter([$res]);
    24 }
    25 print_r(findStr($str1, $str2));

     (2)编辑距离

      说明:如图(他那有个地方笔误了,第2种对齐是插入F,不是插入R)

      思路:最短编辑距离为4。先理解一下书上的给的几个结论(画红框部分为重点,是主要逻辑)

       代码:

     1 <?php
     2 $str1 = 'family';
     3 $str2 = 'frame';
     4 function editDistance($str1, $str2)
     5 {
     6     if ($str1 == $str2) return 0;                           //相等的话距离肯定为0
     7     list($len1, $len2) = [strlen($str1), strlen($str2)];    //如果有一个字符串长度小于2,我们也可以计算出长度来
     8     if ($len1 < 2 || $len2 < 2) {
     9         list($long, $short) = $len1 > $len2 ? [$str1, $str2] : [$str2, $str1];
    10         return strpos($long, $short) === false ? strlen($long) : strlen($long) - strlen($short);
    11     }
    12     $distance = substr($str1, -1) == substr($str2, -1) ? 0 : 1;
    13     return min([                                            //这就是那三个方式了
    14         $distance + editDistance(substr($str1, 0, -1), substr($str2, 0, -1)), 
    15         1 + editDistance($str1, substr($str2, 0, -1)),
    16         1 + editDistance(substr($str1, 0, -1), $str2),
    17         ]);
    18 }
    19 echo editDistance($str1, $str2);

     (3)游艇租赁

      说明:如下图

      思路:假如有5个出租站,求第1个到第5个的最少租金。可以求(1直接到5的租金),和(1-2最少租金+2-5最少租金),和(1-3最少租金+3-5最少租金),和(1-4最少租金+4-5最少租金)的最小值。然后第1个为例(1-2最少租金+2-5最少租金),1-2是可以直接求出来的(问题规模缩减到可求解),2-5的最小租金还是按照1-5的方案求。如下:

      代码:

    <?php
    $arr = [
        [0, 2, 6, 9, 15, 20],
        [null, 0, 3, 5, 11, 18],
        [null, null, 0, 3, 6, 12],
        [null, null, null, 0, 5, 8],
        [null, null, null, null, 0, 6],
        [null, null, null, null, null, 0],
    ];
    
    function minTravel($arr, $s, $e)
    {
        if ($s + 1 == $e) return $arr[$s][$e];
        $res[] = $arr[$s][$e];
        for ($i=$s; $i<$e - 1; $i++) {
            $res[] = minTravel($arr, $s, $i + 1) + minTravel($arr, $i + 1, $e);
        }
        return empty($res) ? 0 : min($res);
    }
    echo minTravel($arr, 0, 5);

       补充:上面的代码只是算出最少的租金,下面的代码会算出路径和最少租金

     1 <?php
     2 $arr = [
     3     [0, 2, 6, 9, 15, 20],
     4     [null, 0, 3, 5, 11, 18],
     5     [null, null, 0, 3, 6, 12],
     6     [null, null, null, 0, 5, 8],
     7     [null, null, null, null, 0, 6],
     8     [null, null, null, null, null, 0],
     9 ];
    10 
    11 function minTravel($arr, $s, $e)
    12 {
    13     if ($s + 1 == $e) return [$s . '-' . $e => $arr[$s][$e]];
    14     $res[$s . '-' . $e] = $arr[$s][$e];
    15     for ($i=$s; $i<$e - 1; $i++) {
    16         list($p1[], $p2[]) = [minTravel($arr, $s, $i + 1) , minTravel($arr, $i + 1, $e)];
    17     }
    18     array_map(function($v1, $v2) use (& $res){
    19         $res[key($v1) . '-' . key($v2)] = current($v1) + current($v2);
    20     }, $p1, $p2);
    21     asort($res);
    22     return [key($res) => current($res)];
    23 }
    24 print_r(minTravel($arr, 0, 5));

     (4)矩阵连乘

      说明:

      

       思路:

      代码:如果想观察到合并过程可以在函数结尾的地方打印newArr(是倒叙的)

     1 <?php
     2 $arr = [[3, 5], [5, 10], [10, 8], [8, 2], [2, 4]];
     3 function matrix($arr)
     4 {
     5     if (($c = count($arr)) < 3)
     6             return ($arr[0] ? array_product($arr[0]) : 0) * ($arr[1][1] ?: 1);
     7 
     8     for ($i=0; $i<$c-1; $i++) {
     9         $param = array_product($arr[$i]) * $arr[$i+1][1];
    10         $newArr = $arr;
    11         array_splice($newArr, $i, 2, [[$arr[$i][0], $arr[$i+1][1]]]);
    12         $res[] = $param + matrix($newArr);
    13     }
    14     return min($res);
    15 }
    16 echo matrix($arr);

     (5)最优三角剖分

      说明:以最短的切割路径之和,把一个凸多边形切分为多个三角形

      思路:如上图,假如v0-v3是最优解的第一步,那么问题转化为1+多边形(v0,v3,v4,v5)+多边形(v0,v1,v2,v3);第一也可能是v0-v2等等,返回最小值就可以了。

      代码:

     1 <?php
     2 $map = [
     3     [0, 2, 3, 1, 5, 6],
     4     [2, 0, 3, 4, 8, 6],
     5     [3, 3, 0, 10, 13, 7],
     6     [1, 4, 10, 0, 12, 5],
     7     [5, 8, 13, 12, 0, 3],
     8     [6, 6, 7, 5, 3, 0],
     9 ];
    10 $points = [0, 1, 2, 3, 4, 5];
    11 function cutting($points) {
    12     global $map;
    13     list($pointsCount, $lines, $res) = [count($points), array(), array()];
    14     for ($i=0; $i<$pointsCount; $i++) {
    15         for ($j=0; $j<$pointsCount; $j++) {                                     //不是相同的点,不是重复的连线:相连  lines用于记录连接过得点,避免重复计算
    16             if ($i != $j && ! in_array($points[$i] . '_' . $points[$j], $lines)) {
    17                 $blocks = array();                                              //连接不相邻的两个点,将多边形分为两块  $twoPoints是这两个点
    18                 $lines = array_merge($lines, [$points[$i] . '_' . $points[$j], $points[$j] . '_' . $points[$i]]);
    19                 $twoPoints = $points[$i] < $points[$j] ? [$points[$i], $points[$j]] : [$points[$j], $points[$i]];
    20                 foreach ($points as $point) {
    21                     if (! in_array($point, $twoPoints))
    22                         $point > $twoPoints[0] && $point < $twoPoints[1] ? $blocks[0][] = $point : $blocks[1][] = $point;
    23                 }
    24                 if (count($blocks[0]) == 0 || count($blocks[1]) == 0) continue; //判定为相邻的两个点
    25                 list($path, $distance) = [array($points[$i] . '_' . $points[$j]), $map[$points[$i]][$points[$j]]];         
    26                 foreach ($blocks as $block) {                                   //path用于记录路径,distance用于记录距离
    27                     if (count($block) > 1) {                                    //等于1为三角形,没必要再分
    28                         $blockRes = cutting(array_merge($block, $twoPoints));
    29                         $distance += current($blockRes); $path[] = key($blockRes);
    30                     }
    31                 }
    32                 $res[join(':', $path)] = $distance;
    33             }
    34         }
    35     }
    36     asort($res);
    37     return [key($res) => current($res)];
    38 }
    39 print_r(cutting($points));    //'3_5:0_3:2_0' => 9

     (6)石子合并

      说明:假如有6堆石子,个数分别是5, 8, 6, 9, 2, 3;将这6堆石子合并成一堆,一次合并两堆,只有相邻的两堆才能合并。比如:

          第一次合并  13,6,9,2,3      5,8合并代价是13

          第二次合并  13,15,2,3       6,9合并代价是15

          第三次合并  13,15,5       2,5合并代价是5

          第四次合并   28,5        13,15合并代价是28

          第四次合并   33         28.5合并代价是33

      总代价:13+15+5+28+33,求总代价最小的合并方法,这个和上面矩阵相乘的差不多。

      代码:

     1 <?php
     2 $arr = [5, 8, 6, 9, 2, 3];
     3 function unionNum($arr) {
     4     if (($numCount = count($arr)) < 3) return array_sum($arr);
     5     for ($i=0; $i<count($arr) - 1; $i++) {
     6         $newArr = $arr;
     7         array_splice($newArr, $i, 2, $arr[$i] + $arr[$i + 1]);
     8         $res[] = $arr[$i] + $arr[$i + 1] + unionNum($newArr);
     9     }
    10     return min($res);
    11 }
    12 echo unionNum($arr);

     (7)0-1背包问题

      说明:假如有5个物品,质量分别是2, 5, 4, 2, 3;价值分别是6, 3, 5, 4, 6。背包承载的最大重量是10。装哪些物品可以价值最大化。

       代码:

     1 <?php
     2 $arr = [[2, 6], [5, 3], [4, 5], [2, 4], [3, 6]];
     3 function zeroOne($arr, $limit) {
     4     $res = [];
     5     for ($i=0; $i<count($arr); $i++) {
     6         if ($limit > $arr[$i][0]) {
     7             $nextArr = $arr; unset($nextArr[$i]);
     8             $others = zeroOne(array_values($nextArr), $limit - $arr[$i][0]);
     9             $key = key($others) ? join('-', $arr[$i]) . ':' . key ($others) : join('-', $arr[$i]);
    10             $res[$key] = $arr[$i][1] + current($others);
    11         }
    12     }
    13     arsort($res);
    14     return $res ? [key($res) => current($res)] : [0];
    15 }
    16 print_r(zeroOne($arr, 10));     //[3-6:4-5:2-6] => 17

     (8)最优二叉搜索树

      说明:有点长。。。

     

       思路:假如最优树的根是20(第5个点,定义为s5,搜索概率为p5,左支搜索不到概率为q4,右支搜索不到概率为q5),那么问题转化为p5 * 深度 + [9, 5, 12, 15]最优树 + [24]最优树 ,后面两个分别是比20小的数组和比20大的数组,如果没有比20小的数组则替换为q4*深度;如果没有比20大的数组则替换为q5*深度;

      代码:

     1 <?php
     2 $nums = [5, 9, 12, 15, 20, 24];
     3 $prs = [[6, 4, 8], [8, 9, 10], [10, 8, 7], [7, 2, 5], [5, 12, 5], [5, 14, 10]];
     4 
     5 function bestTree($nums, $prs, $level = 1) {
     6     $numsCount = count($nums);
     7     if ($numsCount == 1) return array_sum($prs[0]) * $level;
     8     for ($i=0; $i<$numsCount; $i++) {
     9         list($max, $min) = [array(), array()];
    10         for ($j=0; $j<$numsCount; $j++) {
    11             if ($nums[$i] > $nums[$j]) {
    12                 list($min['nums'][], $min['prs'][]) = [$nums[$j], $prs[$j]];
    13             } elseif ($nums[$i] < $nums[$j]) {
    14                 list($max['nums'][], $max['prs'][]) = [$nums[$j], $prs[$j]];
    15             }
    16         }
    17         $res[] = $prs[$i][1] * $level
    18                 + ($max['nums'] ? bestTree($max['nums'], $max['prs'], $level + 1) : $prs[$i][2] * $level)
    19                 + ($min['nums'] ? bestTree($min['nums'], $min['prs'], $level + 1) : $prs[$i][0] * $level);
    20     }
    21     return min($res);
    22 }
    23 echo bestTree($nums, $prs);    //252
  • 相关阅读:
    mp4v2 基本知识
    iperf3.0 hisi uclib 交叉编译
    几个性能测试工具
    单元测试和测试驱动开发的一些常见问题总结
    线程上下文切换的性能损耗测试
    TDD中的单元测试写多少才够?
    AOP学习总结
    ATDD和TDD的区别是什么?
    [转帖]双剑合璧:CPU+GPU异构计算完全解析
    推荐《程序员的四种类型》
  • 原文地址:https://www.cnblogs.com/wangjianheng/p/11725987.html
Copyright © 2011-2022 走看看