of {$slidecount} ½ {$title} ATZJG.NET {$author}

首页






算法分析
Algorithm Analysis


Haifeng Xu


(hfxu@yzu.edu.cn)

This slide is based on Mark Allen Weiss's book.
中译本是《数据结构与算法分析 C++ 描述》张怀勇等译

目录

数学基础

一些定义

注意这里的定义可能与数学上的有所不同.

$f(N)=O(g(N))$

如果存在正常数 $c$ 和 $n_0$, 使得当 $N\geq n_0$ 时, 有 $f(N)\leq cg(N)$, 则记为 $f(N)=O(g(N))$.

\[ \lim_{N\rightarrow\infty}\frac{f(N)}{g(N)}\leq c,\quad c>0. \]


$f(N)=\Omega(g(N))$

如果存在正常数 $c$ 和 $n_0$, 使得当 $N\geq n_0$ 时, 有 $f(N)\geq cg(N)$, 则记为 $f(N)=\Omega(g(N))$.

\[ \lim_{N\rightarrow\infty}\frac{f(N)}{g(N)}\geq c,\quad c>0. \]


$f(N)=\Theta(g(N))$

如果存在正常数 $c_1$ 和 $c_2$, 以及 $n_0$, 使得当 $N\geq n_0$ 时, 有 $c_1 g(N)\leq f(N)\leq c_2 g(N)$, 则记为 $f(N)=\Theta(g(N))$.

即 $f(N)=\Theta(g(N))$ 当且仅当 $f(N)=O(g(N))$ 且 $f(N)=\Omega(g(N))$.

\[ \lim_{N\rightarrow\infty}\frac{f(N)}{g(N)}= c,\quad c>0. \]


$f(N)=o(g(N))$

如果 $f(N)=O(g(N))$, 但是 $f(N)\neq\Theta(g(N))$, 则记为 $f(N)=o(g(N))$.

\[ \lim_{N\rightarrow\infty}\frac{f(N)}{g(N)}=0. \]

一些例子

一些例子

一些简单的法则

一些简单的法则

  1. 如果 $T_1(N)=O(f(N))$ 并且 $T_2(N)=O(g(N))$, 则
    • $T_1(N)+T_2(N)=O(f(N))+O(g(N))=O(\max(f(N),g(N)))$;
    • $T_1(N)T_2(N)=O(f(N)g(N))$.
  2. $T(N)$ 是一个 $k$ 次多项式, 则 $T(N)=\Theta(N^k)$.
  3. 对任意常数 $k$, $\log^k N=O(N)$. (因为 $\lim\limits_{N\rightarrow\infty}\frac{\log^k N}{N}=0$. 这告诉我们对数增长不如线性增长快.)

典型增长率

典型增长率

函数 名称
$c$ 常量
$\log N$ 对数
$\log^2 N$ 对数的平方
$N$ 线性
$N\log N$
$N^2$ 二次
$N^3$ 三次
$2^N$ 指数

运行时间

运行时间是要分析的最重要的资源

影响程序运行的时间有好多因素:

后两者是我们要关心的.

一些原则

一些原则

例子

最大的连续子序列和问题

给定整数 $A_1,A_2,\ldots,A_N$(可能有负数), 求 $\sum_{k=i}^{j}A_k$ 的最大值.

最好能输出和是最大的那个子序列(可能不止一个).

为简单起见, 如果所有整数均为负数, 则最大子序列和定义为 0.

算法一

Fig 2.5 算法一

/**
 * Cubic maximum contiguous subsequence sum algorithm.
 * O(N^3)
 * Time: 1.747s
 */
int maxSubSum1( const vector<int> & a )
{
    int maxSum = 0;

    for( int i = 0; i < a.size( ); i++ )
    {
        for( int j = i; j < a.size( ); j++ )
        {
            int thisSum = 0;

            for( int k = i; k <= j; k++ )
                thisSum += a[ k ];

            if( thisSum > maxSum )
                maxSum = thisSum;
        }
    }

    return maxSum;
}

算法二

Fig 2.6 算法二

算法一中最里层的 for 循环进行了很多不必要的计算, 因为 \[ \sum_{k=i}^{j}A_k=A_j+\sum_{k=i}^{j-1}A_k \]

/**
 * Quadratic maximum contiguous subsequence sum algorithm.
 * O(N^2)
 */
int maxSubSum2( const vector<int> & a )
{
    int maxSum = 0;

    for( int i = 0; i < a.size( ); i++ )
    {
        int thisSum = 0;
        for( int j = i; j < a.size( ); j++ )
        {
            thisSum += a[ j ];

            if( thisSum > maxSum )
                maxSum = thisSum;
        }
    }

    return maxSum;
}

算法三

Fig 2-7 算法三

算法三使用了递归的思想.

/**
 * Recursive maximum contiguous subsequence sum algorithm.
 * Finds maximum sum in subarray spanning a[left..right].
 * Does not attempt to maintain actual best sequence.
 */
int maxSumRec( const vector<int> & a, int left, int right )
{
    if( left == right )  // Base case
        if( a[ left ] > 0 )
            return a[ left ];
        else
            return 0;

    int center = ( left + right ) / 2;
    int maxLeftSum  = maxSumRec( a, left, center );
    int maxRightSum = maxSumRec( a, center + 1, right );

    int maxLeftBorderSum = 0, leftBorderSum = 0;
    for( int i = center; i >= left; i-- )
    {
        leftBorderSum += a[ i ];
        if( leftBorderSum > maxLeftBorderSum )
            maxLeftBorderSum = leftBorderSum;
    }

    int maxRightBorderSum = 0, rightBorderSum = 0;
    for( int j = center + 1; j <= right; j++ )
    {
        rightBorderSum += a[ j ];
        if( rightBorderSum > maxRightBorderSum )
            maxRightBorderSum = rightBorderSum;
    }

    return max3( maxLeftSum, maxRightSum,
                    maxLeftBorderSum + maxRightBorderSum );
}

/**
 * Driver for divide-and-conquer maximum contiguous
 * subsequence sum algorithm.
 */
int maxSubSum3( const vector<int> & a )
{
    return maxSumRec( a, 0, a.size( ) - 1 );
}

时间复杂度

\[ \begin{cases} T(1)=1\\ T(N)=2T(N/2)+O(N) \end{cases} \]

使用数学归纳法可以证明, $T(2^k)=(k+1)2^k$, 从而 \[T(N)=O(N\log N)\].

算法四

Fig 2.8 算法四

/**
 * Linear-time maximum contiguous subsequence sum algorithm.
 */
int maxSubSum4( const vector<int> & a )
{
    int maxSum = 0, thisSum = 0;

    for( int j = 0; j < a.size( ); j++ )
    {
        thisSum += a[ j ];

        if( thisSum > maxSum )
            maxSum = thisSum;
        else if( thisSum < 0 )
            thisSum = 0;
    }

    return maxSum;
}

vector-211-413-5-2
thisSum-2$\rightarrow$0117201513
maxSum01111202020

该算法的优点

仅需常量空间并以线性时间运行的联机算法几乎是完美的算法.

作业

作业

如果我们要求输出达到最大和的连续子序列, 请完善上面的程序.

改进此程序, 要求对于所有数是负数时也能找出最大子序列.

二分法搜索

二分法搜索(binary search)

给定一个整数 $X$ 和整数数组 $A_0,A_1,\ldots,A_{N-1}$, 后者已经预先排序并存储在内存中, 求下标 $i$ 使得 $A_i=X$, 如果 $X$ 不在所给数组中, 则返回 $i=-1$.

/**
 * Performs the standard binary search using two comparisons per level.
 * Returns index where item is found or -1 if not found.
 */
template <typename Comparable>
int binarySearch( const vector<Comparable> & a, const Comparable & x )
{
    int low = 0, high = a.size( ) - 1;

    while( low <= high )
    {
        int mid = ( low + high ) / 2;

        if( a[ mid ] < x )
            low = mid + 1;
        else if( a[ mid ] > x )
            high = mid - 1;
        else
            return mid;   // Found
    }
    return NOT_FOUND;     // NOT_FOUND is defined as -1
}

运行时间复杂度

不妨假设开始时 high-low=128=$2^7$, 则在各次迭代后 high-low 的最大值为 \[ 2^6,\ 2^5,\ 2^4,\ 2^3,\ 2^2,\ 2^1,\ 1,\ 0, -1 \]

因此, 运行时间是 $O(\log N)$.

二分法搜索的意义

欧几里得算法

欧几里得算法

End






Thanks very much!

This slide is based on Jeffrey D. Ullman's work, which can be download from his website.