数据结构篇(一):

数据结构篇(一):

​   这里不多废话直接进入正题.

算法(Algorithm):

​   一堆有限的指令集,就是一堆指令放在一起,去做一件事情,有时候会接收输入,有时候也不会接收输入,但是不管有没有输入,算法至少要产生一种输出,否则算法写出来就等于无效了,并且每一条指令必须有充分的目标,不可以有歧义,且要在计算机的计算范围以内。

​   正如我们所知道的,算法有分好的算法和坏的算法,那么我们如何区分他们呢?

​   答:空间复杂度S(n)—执行时占用存储单元的长度。

​   时间复杂度T(n)—根据算法写成的程序在执行时耗费时间的长度。但是因为不同机子对同一代码执行的时间总是存在差异,所以我们一般不算代码的执行时间,而是算执行次数。

当空间复杂度过大时,程序容易非正常中断,如下:

void prin_t(int N)
{
    if(N)
    {
        print(N-1);
        print("%d\n",N);
    }
    return;
}

​   以上代码,在输入N为10000时,print的数据为10000时占据了一个空间,print的数据为9999的时候又占据了一个空间,以此类推,此代码占据的空间将会越来越大,而如果只如下代码:

void prin_t(int N)
{
    int i = N;
	if(i ;i>0;i--)
    {
        print("%d",i);
    }
}

​   如果输入10000时,因为函数只运用了一个变量i和for循环,所以不管程序怎么跑,占用的空间依旧是i的空间,所以占据空间不会很大。

当然时间复杂度也不可过大:

代码1:

double f(int n,double a[],double x)
{
    int i;
    double p = a[0];
    for(i = 1;i<=n;i++)
    {
        p+=(a[i]*pow(x,i));
        return p;
    }
}

代码二:

double f(int n, double a[], double x)
{
    int i;
    double p = a[n];
    for(i = n;i>0;i--)
        p = a[i-1]+x*p;
    return p;
}

乍一看是不是代码一和二没什么区别?

​   注意,代码一中,pow(x,i)的操作让函数进行了n次乘法再加上与前面的系数相乘,则乘法的总次数为(n+1)n/2次,但是代码二的操作,每次for循环中只乘一次,所以乘法次数只有n次。二者的时间复杂度由此就大大体现出来,尤其时n很大的时候n²远远大于n本身。

​   一般在分析算法效率的时候,我们经常关注下面两种复杂度:

​   最坏情况复杂度和平均复杂度。

​   在算复杂度时我们有必要一步一步的去算算法的执行了多少次吗?

​   答案是没必要,我们考虑的只是随着N(数据规模的增大),算法复杂度增长的性子会如何。

就以上面代码一和代码二做例子:

​   代码一以n²做为增长的基数,代码二以n为增长的基数,当n很大时,n²远大于n的增长速率,所以第一个代码运行的时间一定会比第二个算法大超级多。

以下列出学习网课中的一张图片:
在这里插入图片描述

​   上图是不同函数的增长变化,当我们的函数复杂度增长速率为2的n次方时,我们最好能将它降级,比如到nlogn的增长速率。

设两个算法的复杂度为T1(n) = O(f1(n)),T2(n) = O(f2(n)),则:

​ T1(n)+T2(n) = max(O(f1(n)),O(f2(n)))

​ T1(n)×T2(n) = O(f1(n)×f2(n))

​   两个算法拼接在一起,则复杂度上界是二者间最大的那个,当两个算法嵌套起来的时候,两个复杂度上限相乘。

应用实例;

求给定N个整数的序列,求函数最大的子列和

算法一:

int MaxSubseqSum1(int A[],int N)
{
    int ThisSum,MaxSum = 0;
    int i,j,k;
    for(i = 0;i<N;i++)
    {
        for( j=i;j<N;j++)
        {
            ThisSum = 0;
            for(k = i;k <= j; k++)
            {
                ThisSum+=A[k];
                if(ThisSum>MaxSum)
                    ThisSum = MaxSum;
            }
        }
    }
    return MaxSum;
}

由上我们可知,算法1的复杂度增长为n³—三层嵌套的for循环。

算法二:

int MaxSubseqSum2(int A[],int N)
{
    int ThisSum,MaxSum = 0;
    int i,j;
    for(i = 0;i<N;i++)
    {
        ThisSum = 0;
        for( j=i;j<N;j++)
        {
            ThisSum+=A[k];
            if(ThisSum>MaxSum)
                ThisSum = MaxSum;
        }
    }
    return MaxSum;
}

由上可知,算法二的复杂度增长率为n²—两层for循环,但是代码效果还是一样的。

算法三

​   算法三应用了一种比较常见的方法这里与大家介绍一下:分而治之。

​   什么叫分而治之呢?首先我们得分,将数组一分为二,递归的解决左右两边的问题。递归的解决左边的问题我们能得到左边的一个最大子列和,递归的解决右边的问题,我们能得到右边的一个最大子列和。最后将二者”治“起来,即将二者最大子列和结合。

以下用张图来具体讲解:
在这里插入图片描述

​   正如我们所看到的子列是由8个任意的数字组合成的,其中有正有负,此时我们怎么样才能知道他们的最大子列和呢?

​   按照分而治之的思想,我们首先在-2 -1 之间来一刀,将子列分割为前4个数和后四个数,再分别求前后四个数的最大子列和,然而对于前后四个数求最大子列和,我们也可以将前后四个数再进行二分。

​   就比如图示中前四个数中,再二分成4 -3和5 -2,其中4 -3中,最大数肯定是4则我们将其返回,5 -2中,最大数为5则我们将其返回:
        在这里插入图片描述

​   这样我们就得出“分”的两个最大子列和,现在我们要算“治”的:

​   我们由上述可以知道,分的两个最大子列和为4和5,要进行治操作的话,还需要将二者间的所有元素对应相加,就比如图示中,4 5之间和隔着一个-3,那么我们“治”的最大子列和为:4-3+5 = 6

    在这里插入图片描述

​   然后我们再依次类推后四个数的最大子列和,再将其相加起来:

在这里插入图片描述

​   最后的最大子列和:6-2-1+8 = 11

对于这个算法我们的复杂度又是如何呢?

​   对于前后四个数中,他们内部复杂度的计算分别为T(N/2),然后最后治操作又是T(cN)(c为常数).

以下给出复杂度的推导:

在这里插入图片描述

注意:我们提过,复杂度的相加等于取二者间最大的那个。

​   第一行,列数前、后四个数分和治的复杂度。

​   第二行,将前后四个数的复杂度,再拓展出来成为各自内部再次二分时的分和治的复杂度。

​   第三行,展开。

​   第四行,复杂度相加取最大那个。

业精于勤,荒于嬉,行成于思,毁于随。

下一篇介绍堆栈和队列。

12.5学习笔记。

数据结构篇(二):_风声向寂的博客-CSDN博客
(2条消息) 数据结构篇(三):_风声向寂的博客-CSDN博客
数据结构篇(四):_风声向寂的博客-CSDN博客
数据结构篇(五):_风声向寂的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值