有趣又简单的算法导论(一):算法分析

算法分析

算法分析是关于计算机程序性能与资源利用的研究。

为什么需要性能

我们在用代码写程序中,有比性能更加重要的东西,例如:

  1. 正确性;
  2. 可维护性;
  3. 健壮性;
  4. 可扩展性;
  5. 模块化;
  6. 安全性;
  7. 用户友好;

看似这些都比性能更加重要,那为什么我们还要关注性能?

  1. 通常性能的好坏,决定着一个方案是可行还是不可行。例如,对于实时的需求,如果它程序不快,就代表它不可行,如果它占用过多的内存,也只能说不可行。
  2. 算法是一种让机器简洁思考的语言,它使实现方式更加的简洁。

总:算法就算是经济中的基础货币,是衡量的标准。例如:人们牺牲了三倍的性能,把原能用C语言写的程序改为用java来写,因为java提供面向对象的特性等好处。

排序问题

我们输入一组序列a1,a2,…直到an,然后把他们按照从小到大的顺序输出

插入排序

定义

插入排序就是找准一个key值,key值前面的都是排过序的,key值后面的都是没有排过序的,将未排序的元素依次与排序元素作比较,按照从小到大或者从大到小的顺序将未排序的元素插入排序元素中,从而组成有序的数组。

自己的思考过程
第一步:错误的代码

一组无序的数组,那么它的第一位肯定是有序的,所以我就假设这个数组的第二位就是这个key值,然后和其他元素一一做比较,得出代码:

public static int[] sort(int[] params){
        for(int i=0;i<params.length;i++){
                int j = i+1;
                while (j<params.length&&params[j]<params[i]){
                        int temp = params[i];
                        params[i] = params[j];
                        params[j] = temp;
                }
        }
        return params;
}

然后运行这段代码,控制台打印结果:

原数组为:[100, 97, 20, 77777, 5, 68, 37, 20]
排序后的数组为:[20, 97, 5, 68, 37, 20, 100, 77777]
第二步:无意中发现冒泡排序法

em…(○´・д・)ノ这是什么鬼?好吧,可能写错了,我继续思考一下。。。。发现了一个即为重要的问题,上段代码只是将key值和它的前一个元素(也就是有序数组的最后一位)作比较,这样是不行的,例如在进行第二次排序时,本来的正确结果应该是[20,97,100, 77777, 5, 68, 37, 20],但是由于上段代码只是将key值和它的前一个元素(也就是有序数组的最后一位)作比较,所以20只会和100交换位置,于是就变成了错误数据[97,20,100, 77777, 5, 68, 37, 20]。所以key值应该是和key值前面的所有有序元素作比较。于是得出代码:

public static int[] sort(int[] params){
        for(int i=0;i<params.length;i++){
                int j = i+1;
                for(int a=0;a<=i;a++) {
                        while (j < params.length && params[j] < params[a]) {
                                int temp = params[a];
                                params[a] = params[j];
                                params[j] = temp;
                        }
                }
        }
        return params;
}

运行出结果

原数组为:[100, 97, 20, 77777, 5, 68, 37, 20]
排序后的数组为:[5, 20, 20, 37, 68, 97, 100, 77777]
第三步:简化算法

写出程序之后,又对比了一下网上的算法,em…发现网上的代码比我的要少很多,emmmm…又思考了一下,第二步是相邻元素俩俩对比,因此是冒泡排序法。无语,算法好难,下星期再研究吧。
重新思考插入排序,应该是假设前面数组的第一位是有序的,然后从第二位至第n位都是无序的,因此应该是循环应该是从1到n,然后再把每一次的key值与前面的有序数值一一作比较。因此得到代码:

public static int[] sort(int[] params){
        for(int i=1;i<params.length;i++){
                int j = i;
                int key = params[j];
                while(j>0 && key<params[j-1]){
                        int temp = params[j-1];
                        params[j-1] = params[j];
                        params[j] = temp;
                        j--;
                }

        }
        return params;
}
插入排序的运行时间
算法的运行时间的因素

算法的运行时间[^1]取决于多个因素,包括
1.输入本身。例如:输入的元素已经有序,那么排序的运行时间就会非常之短;如果输入的元素是逆序的,那么消耗时间就会长。
2.输入规模。例如:处理6个元素和处理60亿个元素,显然是后者消耗时间更长。通常,我们将其输入的元素参数化,将运行时间看作待排列数据规模的函数。

[^1]:运行时间一般会有一个上界,即这个程序最多再多少时间内运行,它代表了对用户的承诺。

输入时最坏情况的分析
定义

T(n)定义为输入规模为n时的最长运行时间;
T(n)也被称为期望时间,即每种输入的运行时间,乘以那种输入出现的频率。

最坏情况

最坏情况就是逆向排序,即最大值在第一个,最小值在最后一个,此时每次做插入操作都要把所有项替换一遍。我们可以假设每种原子操作都耗费常数时间,

T(n) = ∑ j = 2 n Θ ( j ) \sum_{j=2}^nΘ(j) j=2nΘ(j) = Θ(n2)

这个 Θ(n2) 相当于1+2+3+…一直加到n

那么,相对于很小的n, Θ(n2) 是很小的,而相对于很大的n, Θ(n2) 则是很大的。

输入时最好情况的分析

最好情况就是只输入有序的数组一种情况,这种情况我们可以忽略。

算法的大局观

渐进分析:
1.忽略依赖于机器的常量;
2.不是去检查实际的运行时间,而是关注运行时间的增长。

渐进符号

Θ:写个公式,弃去它的低阶项,并忽略前面的常数因子。
例如,公式 3n3+90n2-5n+6046 = Θ(n3)

分析:现在有一种算法的时间T(n) = Θ(n3),而另一种算法的时间是T(n) = Θ(n3),假设他们的函数图是这样的
俩种算法的运行时间比较图从上图我们可以看出总有一个n0能使得 Θ(n3)的算法运行时间小于 Θ(n2)的算法运行时间,不管一开始你给 Θ(n2)这种算法多大的计算机优势都没用,但是从工程视角来看,如果这个n0过大,大到计算机都无法计算它时,我们仍旧会采用 Θ(n2)这种算法。

注:具体的渐进符号以及解法知识将在 有趣又简单的算法导论(二):渐进符号,递归及解法中展示

归并排序

定义

第一步:如果n=1,那么排序结束
第二步:如果n!=1,那么我们做的就是递归的对a1到an/2向上取整这部分排序以及an/2向上取整到an这部分排序。
第三步:然后,我们将排好序的俩个表归并。

子程序的归并时间

首先我们研究子程序的归并时间
例如:现在有俩个排好序的表
5 6 7 8 9
0 1 2 3 4
首先将5和0对比,得出0小,因此将0写在首位,再将5和1对比,得出1小,因此将1写在第二位…以此类推,得出时间应该是 Θ(n),

总时间

T ( n ) = { Θ ( 1 ) x=1 2 T ( n / 2 ) + Θ ( n ) x&gt;1 T(n)= \begin{cases}Θ(1) &amp; \text{x=1}\\2T(n/2)+Θ(n) &amp; \text{x&gt;1} \end{cases} T(n)={Θ(1)2T(n/2)+Θ(n)x=1x>1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值