zoukankan      html  css  js  c++  java
  • 经典《编程珠玑》

    第8章算法设计技术中一维模式识别实例,书中举出了5种不同的解法,解法不断优化,不断的变得高效,不断得变得更优雅,看完感触良深。
     
    【问题描述】

    输入n个数的序列,输出这n个数的任意连续子序列的最大和。在这里我们假设序列的数都为整数(包括正和负)
    如序列: 31 -41 59 26 -53 58 97 -93 -23 84 从[2..6]的总和187为最大

    【解法一:穷举法】
    最简单的方法,穷举法:即对所有满足0 <= i <= j < n 的(i, j)整数对进行迭代。对于每个整数对,程序都要计算x[i..j]的总和,并检验该总和是否大于迄今为止的最大总和。 其PHP实现如下:

    <?php
    /**
     * 一维模式识别算法一,穷举法
     * @package test
     */
    function maxsofar($seq) {
        $maxsofar = 0;
        $len = count($seq);
     
        for ($i = 0; $i < $len; $i++) {
            for ($j = $i; $j < $len; $j++) {
                $sum = 0;
                for ($k = $i; $k <= $j; $k++) {
                    $sum += $seq[$k];
                }
                if($sum > $maxsofar) {
                    $maxsofar = $sum;
                }
            }
        }
        return $maxsofar;
    }
     
    $seq = array(31, -41, 59, 26, -53, 58, 97, -93, -23, 84);
    echo maxsofar($seq);
    die();
    

      


    算法一优点是非常清晰明了,一眼就看出其思路,并且实现简单。
    但是其时间复杂度为O(n的立方),如果数据量稍微大一点,整个程序的效率将会巨慢。

    【解法二:】
    x[i..j]的总和与前面已计算的总和x[i..j-1]密切相关,即x[i..j] = x[i..j-1] + x[j]
    于是我们有了解法二。如下所示PHP代码:

    <?php
    /**
     * 一维模式识别算法二,穷举法的优化算法,将时间复杂度变为n的平方
     * @package test
     */
    function maxsofar($seq) {
        $maxsofar = 0;
        $len = count($seq);
     
        for ($i = 0; $i < $len; $i++) {
            $sum = 0;
            for ($j = $i; $j < $len; $j++) {
                $sum += $seq[$j];
     
                if($sum > $maxsofar) {
                    $maxsofar = $sum;
                }
            }
        } 
        return $maxsofar;
    }
     
    $seq = array(31, -41, 59, 26, -53, 58, 97, -93, -23, 84);
    echo maxsofar($seq);
    die();
    

      

    【解法三:预处理】
    同样是根据x[i..j]的总和与前面已计算的总和x[i..j-1]密切相关,即x[i..j] = x[i..j-1] + x[j]
    不过我们使用另外一种表示:预处理数据,计算cumarr[i] = x[0..i],则x[i..j] = cumarr[j] – cumarr[i - 1]
    然后再如解法二一样遍历比较,算出最大的值。如下所示代码:

    <?php
    /**
     * 一维模式识别算法三,穷举法的优化算法,将时间复杂度变为n的平方
     * @package test
     */
     
    function maxsofar($seq) {
        $maxsofar = 0;
        $len = count($seq);
     
        /* 预处理数据 */
        $cumarr[-1] = 0;
        for ($i = 0; $i < $len; $i++) {
            $cumarr[$i] = $cumarr[$i - 1] + $seq[$i];
        }
     
        for ($i = 0; $i < $len; $i++) {
            for ($j = $i; $j < $len; $j++) {
                $sum = $cumarr[$j] - $cumarr[$i - 1];
     
                if($sum > $maxsofar) {
                    $maxsofar = $sum;
                }
            }
        } 
        return $maxsofar;
    }
     
    $seq = array(31, -41, 59, 26, -53, 58, 97, -93, -23, 84);
    echo maxsofar($seq);
    die();
    

      

    【解法四:分治算法】
    分治原理:要解决规模为n的问题,可递归地解决两个规模近似为n/2的子问题,然后对它们的答案进行合并以得到整个问题的答案。
    首先创建两个子序列a和b,然后递归找出a,b中元素总和最大的子向量,分别称为ma和mb,也许我们可能找到最后的解了,可是也有可能答案所在的子序列一部分在ma,一部分在mb,对于这种跨越边界的序列我们将其称之为mc。
    我们的分治算法将递归地计算ma和mb,并通过其它方法计算mc,然后返回3个总各和中的最大者。其PHP实现如下:

    <?php
    /**
     * 一维模式识别算法四,分治算法
     * @package test
     */
     
    function maxsofar($seq, $left, $right) {
        if($left > $right) {
            return 0;   //  已没有元素 递归返回
        }
     
        if($left == $right) {
            return max(0, $seq[$left]); //  只有一个元素,返回这个元素与0之间的较大值
        }
     
        $middle = floor(($left + $right) / 2);
     
        /* 左边从中间开始的最大子序列和 */
        $lmax = $sum = 0;
        for($i = $middle; $i >= $left; $i--) {
            $sum += $seq[$i];
            $lmax = max($lmax, $sum);
        }
     
        /* 右边从中间开始的最大子序列和 */
        $rmax = $sum = 0;
        for($i = $middle + 1; $i <= $right; $i++) {
            $sum += $seq[$i];
            $rmax = max($rmax, $sum);
        }
     
        return max($lmax + $rmax, maxsofar($seq, $left, $middle), maxsofar($seq, $middle + 1, $right));
    }
     
    $seq = array(31, -41, 59, 26, -53, 58, 97, -93, -23, 84);
    echo maxsofar($seq, 0, count($seq) - 1);
    die();
    

      



    对于mc的计算,通过观察发现,mc在a中包含右边界的最大子序列,而mc在了中包含左边界的最大子序列
    此解法的时间复杂度为O(nlogn)

    【解法五:扫描算法】
    最大总和的初始值设为0,假设我们已解决了x[0..i-1]的问题,那么最大总各子序列要么在前i-1个元素中,要么其结束位置为i

    <?php
    /**
     * 一维模式识别算法五,扫描算法
     * @package test
     */ 
    function maxsofar($seq) {
        $maxsofar = 0;
        $maxendinghere = 0;
        $len = count($seq);
     
        for($i = 0; $i < $len; $i++) {
            $maxendinghere = max($maxendinghere + $seq[$i], 0);
            $maxsofar = max($maxsofar, $maxendinghere);
        } 
        return $maxsofar;
    }
     
    $seq = array(31, -41, 59, 26, -53, 58, 97, -93, -23, 84);
    echo maxsofar($seq);
    die();
    

      


    理解这个程序的关键在于变量$maxendinghere。
    在循环中的第一个赋值语句之前,$maxendinghere是结束位置为i-1的最大子序列的和;赋值语句将其改为结束位置为i的最大子序列的和。
    其时间复杂度为O(n),对于这种时间复杂度为O(n)的算法,一般称其为线性算法。

    好书推荐:

  • 相关阅读:
    WPF 使用 Direct2D1 画图 绘制基本图形
    WPF 使用 Direct2D1 画图 绘制基本图形
    dot net core 使用 IPC 进程通信
    dot net core 使用 IPC 进程通信
    win2d 图片水印
    win2d 图片水印
    Java实现 LeetCode 240 搜索二维矩阵 II(二)
    PHP closedir() 函数
    PHP chroot() 函数
    PHP chdir() 函数
  • 原文地址:https://www.cnblogs.com/haostyle/p/13123970.html
Copyright © 2011-2022 走看看