数据结构_Chapter1.3 算法

浙大陈越老师主编《数据结构》(第2版)学习笔记

目录

算法定义

算法复杂度

 最坏情况复杂度

 渐进表示法

应用实例:最大子列和问题


算法定义

算法是一个有限的指令集,接受一些输入(有些情况下没有输入),产生输出,并一定在有限步骤之后终止。

算法复杂度

衡量算法优劣的指标:

1、空间复杂度S(n)

根据算法写成的程序执行时占用的存储单元长度(与输入数据的规模n有关)

2、时间复杂度T(n)

根据算法写成的程序执行时花费时间的长度(与输入数据的规模n有关)

chapter 1.1 例1.2中printN函数的递归实现,在N比较大时非正常中断的原因是什么?

        函数A调用函数B时,必须先保存A当前状态,当B调用完成后,再释放内存,恢复状态,继续执行。

        例1.2中printN函数迭代实现中:执行printN(N)时调用printN(N - 1),需要保存printN(N)状态;执行printN(N - 1)时调用printN(N - 2),需要保存printN(N - 1)状态;……依次类推,直到执行完printN(0),才能开始逐级释放内存。

        假设存储每个函数的状态占用1个单位内存空间,则执行printN(N)需要N个单位的内存空间,当N非常大时,内存不足导致非正常中断。

根据定义,例1.2中printN递归实现的空间复杂度S(n)=C*n,C是一个固定常数,n是要打印整数的个数。

chapter 1.1 例1.3中计算多项式的简单直接算法、秦九昭算法的时间复杂度T(n)分别是多少?

简单直接算法:执行了n+1次result += (a[i] * pow(x, i))语句——每次涉及i次乘法和1次加法,所以总共涉及n+1次加法和(1 + 2 +...+n)=(1 + n) * n / 2次乘法。T_{1}(n)=C_{1}n^{2}+C_{2}n,n是多项式的阶数。

秦九昭算法:执行了n次result = a[i -1] + (x * result)语句,总共涉及n次加法和n次乘法。T_{2}(n)=C*n,n是多项式的阶数。

两者比较,对于充分大的n,T_{1}(n)总会比T_{2}(n)大,即秦九昭算法比简单直接算法快,且n越大,快得越明显。

 最坏情况复杂度

分析算法效率时,我们经常关注两种复杂度:

1、最坏情况复杂度T_{worst}(n)

2、平均复杂度T_{avg}(n)

例:用顺序查找法在一排混乱无序的书架上找一本书

最好情况:1次找到

最坏情况:找了n本书,没有找到

T_{worst}(n)=C*n,C是查看一本书的时间。

要得到平均查找次数,则略麻烦,要把每一种可能的情况都考虑到(可能查找两次,也可能需要三次),把所有情况下的查找次数加起来,除以情况个数。

显然,T_{avg}(n)\leqslant T_{worst}(n)

 渐进表示法

精确地比较程序执行的步数是没意义的,因为每步执行时间可能不同,比如递归调用的1步,实际上涉及系统堆栈的很多处理,比循环中的1步计算慢得多。

所以,比较算法优劣时,只考虑宏观渐近性质,即当输入规模n“充分大”时,观察不同算法复杂度的“增长趋势”。

计算多项式例子中,我们只要知道:n很大的时候,简单直接算法的时间复杂度,基本上就是 n^{2}在起主要作用。而秦九昭算法,则是n在起主要作用。当n充分大的时候,前者肯定比后者要慢。

于是就有了复杂度的渐进表示法:
T(n)=O(f(n)) 表示存在常数C> 0, n_{0}> 0,使得当n\geq n_{0} 时,有T(n)\leqslant C\cdot f(n)O(f(n))就表示f(n)T(n)的某种上界.
T(n)=\Omega (g(n))表示存在常数C> 0, n_{0}> 0,使得当n\geq n_{0} 时,有T(n)\geq C\cdot g(n)
\Omega (g(n))就表示g(n)T(n)的某种下界.
T(n)=\Theta (h(n))表示同时有T(n)=O(h(n))T(n)=\Omega (h(n))。对于这个\Theta里面的函数来说,O\Omega是同时成立的,也就是说,它既是上界也是下界

计算多项式例子中,简单直接算法的时间复杂度是O(n^{2}),秦九昭算法的时间复杂度是O(n)

通过下图可以直观地看到不同函数随着n的增长,它的增长速度:

面对O(n^{2})复杂度的算法时,计算机科学的本能反应是将之优化为一个O(n\cdot log n)的算法,后者效率高很多。

对给定算法做渐近分析的窍门:

应用实例:最大子列和问题

分析:“子列”为原始序列中连续的一段数字,要找具有最大和的“子列”,并返回它的和;如果这个最大和是负数,则返回0。

算法1.1 穷举所有子列和,从中找到最大值

/* 穷举所有子列和,从中找到最大值 */
int max_subseq_sum(int List[], int N)
{
    int i;
    int j;
    int k;
    int max_sum = 0;
    int sum;

    if(N <= 0)
    {
        printf("param error. \n");
        return -1;
    }

    /* i是子列左端位置,j是子列右端位置 */
    for(i = 0; i < N; i++)
    {
        for(j = i; j < N; j++)
        {
            sum = 0;
            for(k = i; k <= j; k++)
            {
                sum += List[k];
            }
            
            if(sum > max_sum)
            {
                max_sum = sum;
            }
        }
    }

    return max_sum;
}

调用:

int main()
{
    int max_sum;
    int List[] = {-2, 11, -4, 13, -5, -2};
    int N = sizeof(List) / sizeof(List[0]);

    max_sum = max_subseq_sum(List, N);
    if(max_sum >= 0)
    {
        printf("max sub sequence sum is %d \n", max_sum);
    }

    return 0;
}

结果:

 算法1.2 部分存储中间值的穷举

/* 部分存储中间值的穷举 */
int max_subseq_sum(int List[], int N)
{
    int i;
    int j;
    int k;
    int max_sum = 0;
    int sum;

    if(N <= 0)
    {
        printf("param error. \n");
        return -1;
    }

    /* i是子列左端位置,j是子列右端位置 */
    for(i = 0; i < N; i++)
    {
        sum = 0;
        for(j = i; j < N; j++)
        {
            sum += List[j];
            
            if(sum > max_sum)
            {
                max_sum = sum;
            }
        }
    }

    return max_sum;
}

 算法1.3 分而治之

/* 返回3个数中最大的数 */
int max3(int A, int B, int C)
{
    if(A > B)
    {
        if(A > C)
        {
            return A;
        }
        else
        {
            return C;
        }
    }
    else
    {
        if(B > C)
        {
            return B;
        }
        else
        {
            return C;
        }
    }
}

/* 分而治之 */
int devide_and_conquer(int List[], int left, int right)
{
    int middle = 0;
    int left_max_sum = 0;
    int right_max_sum = 0;
    int middle_max_sum = 0;

    /* 递归终止条件:子列只有一个数字 */
    if(left == right)
    {
        if(List[left] < 0)
        {
            return 0;
        }
        else
        {
            return List[left];
        }
    }

    /* 找到数列中间位置,将数列分为左右两部分 */
    middle = (left + right) / 2;

    /* 分别计算左、右两部分的最大子列和 */
    left_max_sum = devide_and_conquer(List, left, middle);
    right_max_sum = devide_and_conquer(List, middle + 1, right);

    /* 计算跨越中间位置的子列的最大子列和 */
    int i;
    int tmp_left_sum = 0;
    int tmp_right_sum = 0;
    int tmp_left_max_sum = 0;
    int tmp_right_max_sum = 0;
    for(i = middle; i >= left; i--)
    {
        tmp_left_sum += List[i];
        if(tmp_left_sum > tmp_left_max_sum)
        {
            tmp_left_max_sum = tmp_left_sum;
        }
    }

    for(i = (middle + 1); i <= right; i++)
    {
        tmp_right_sum += List[i];
        if(tmp_right_sum > tmp_right_max_sum)
        {
            tmp_right_max_sum = tmp_right_sum;
        }
    }
    middle_max_sum = tmp_left_max_sum + tmp_right_max_sum;

    /* 结果:左边部分、右边部分、跨越中间位置的最大子列和中的最大值 */
    return max3(left_max_sum, right_max_sum, middle_max_sum);
}

/* 用分而治之的方法计算最大子列和 */
int max_subseq_sum(int List[], int N)
{
    return devide_and_conquer(List, 0, N - 1);
}

算法1.1的时间复杂度由3层for循环嵌套决定,算法复杂度为O(N^{^{3}})

算法1.2的时间复杂度由2层for循环嵌套决定,算法复杂度为O(N^{2})

算法1.3的时间复杂度分析略有难度:记整体时间复杂度为T(N),则函数devide_and_conquer中递归进行“分”的复杂度为2T(N/2)(我们解决了2个长度减半的子问题)。求跨分界线的最大子列和时,有2个for循环,所用步骤不超过N,所以在O(N)时间完成,其他步骤只需要O(1)时间。

综上分析:

T(1)=O(1)

T(N) = 2T(N/2) + O(N) = 2[2T(N/2/2) + O(N/2)] + O(N) = 2^{^{2}}T(N/2^{2}) + 2O(N) = ... = 2^{k}T(N/2^{k}) + kO(N)

不断对分直到N/2^{k}=1,即N=2^{k}时,得到T(N) = N*T(1) + logN*O(N) = O(NlogN)

此算法比算法1.2又快一些,但仍然不是最快的算法。

算法1.4 在线处理

/* 在线处理方法计算最大子列和 */
int max_subseq_sum(int List[], int N)
{
    int i;
    int max_sum = 0;
    int sum = 0;

    for(i = 0; i < N; i++)
    {
        sum += List[i];
        // printf("sum: %d \n", sum);
        if(sum > max_sum)
        {
            max_sum = sum;
            // printf("max sum: %d \n", max_sum);
        }
        else if(sum < 0)
        {
            /* 当前子列和为负,则不可能使后面的部分和增大,抛弃 */
            sum = 0;
        }
    }

    return max_sum;
}

该算法复杂度只有O(N)

此算法中,无论我们停在中间哪一步,返回值都是最大子列和的正确解。

解决同一个问题,不同的算法会有很大的差别。

提高效率的窍门之一:让计算机“记住”一些关键的中间结果避免重复计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值