数据结构与算法学习笔记day1

代码效率优化方法论:

1.复杂度
1.1复杂度是一个关于输入数据量n的函数
假设你的代码复杂度是f(n) ->O(f(n))
O(n)表示的是,复杂度与计算实例的个数n线性相关
O(logn)表示的是,复杂度与计算实例的个数n对数相关
1.2复杂度计算方法遵循的原则
复杂度与具体的常系数无关
O(n)和O(2n)表示的是同样的复杂度
多项式级的复杂度相加的时候,选择高者作为结果
O(n^2)+O(n)表示的是同样的复杂度
O(n^2)+O(n) = O(n^2+n)
只需要通过更大变化率的二阶多项式来表征复杂度就可以
注:O(1)也是表示一个特殊复杂度,表示复杂度与输入数据无关。
1.3时间复杂度与代码结构的关系
1.3.1经验性结论:
一个顺序结构的代码,时间复杂度为O(1)
二分查找,或者更通用的说是采用分而治之的二分策略,时间复杂度都是O(logn)
一个简单的for循环,时间复杂度是O(n)
两个嵌套的循环,时间复杂度是O(n^2)
1.3.2降低时间复杂度的必要性
实际的在线环境中,用户的访问请求可以看作一个流式数据
假设这个数据流中,每个访问的平均时间间隔是t
如果代码无法在t时间内处理完单词的访问请求,那么这个系统最终被大量积压的任务给压垮
这就要求工程师必须通过优化代码,优化数据结构,来降低时间复杂度
假设某个任务需要处理10万条数据
编写的代码如果是O(n^2)的时间复杂度,那么计算的次数就大概是100亿次左右
如果是O(n),那么计算的次数就是10万次左右
如果能在O(logn)的复杂度下完成恩物,那么计算的次数就是17次左右2
1.4时间复杂度转换为空间复杂度
1.4.1时间昂贵,空间廉价
1.4.2数据结构连接时空
如果暴力解法复杂度比较高,就需要考虑用程序优化的方法去降低复杂度
常用的降低时间复杂度方法有递归、二分法、排序算法、动态规划等
降低空间复杂度的方法,要围绕数据结构做文章。
在程序开发中,连接时间和空间的桥梁就是数据结构。采用合理的数据结构,就可以实现降低时间复杂度。但同时,空间复杂度也会增加。
核心思路:
第一步:暴力解法
在没有任何时间、空间的约束下,完成代码任务的开发
第二步:无效操作处理
将代码中的无效计算、无效存储剔除,降低时间或空间复杂度
第三步:时空转换
合理设计数据结构,完成时间复杂度向空间复杂度的转移
举例:查找出一个数组中,出现次数最多的那个元素的数值。例如,输入数组 a = [1,2,3,4,5,5,6 ] 中,查找出现次数最多的数值。从数组中可以看出,只有 5 出现了 2 次,其余都是 1 次。显然 5 出现的次数最多,则输出 5。
工程师小明的解决方法是,采用两层的 for 循环完成计算。第一层循环,对数组每个元素遍历。第二层循环,则是对第一层遍历的数字,去遍历计算其出现的次数。这样,全局再同时缓存一个出现次数最多的元素及其次数就可以了。具体代码如下:

public void s2_3() {

    int a[] = { 1, 2, 3, 4, 5, 5, 6 };

    int val_max = -1;

    int time_max = 0;

    int time_tmp = 0;

    for (int i = 0; i < a.length; i++) {

        time_tmp = 0;

        for (int j = 0; j < a.length; j++) {

            if (a[i] == a[j]) {

            time_tmp += 1;

        }

            if (time_tmp > time_max) {

                time_max = time_tmp;

                val_max = a[i];

            }

        }

    }

    System.out.println(val_max);

}
在这段代码中,小明采用了两层的 for 循环,很显然时间复杂度就是 O(n²)。而且代码中,几乎没有冗余的无效计算。如果还需要再去优化,就要考虑采用一些数据结构方面的手段,来把时间复杂度转移到空间复杂度了。
我们先想象一下,这个问题能否通过一次 for 循环就找到答案呢?一个直观的想法是,一次循环的过程中,我们同步记录下每个元素出现的次数。最后,再通过查找次数最大的元素,就得到了结果。
具体而言,定义一个 k-v 结构的字典,用来存放元素-出现次数的 k-v 关系。那么首先通过一次循环,将数组转变为元素-出现次数的一个字典。接下来,再去遍历一遍这个字典,找到出现次数最多的那个元素,就能找到最后的结果了。
public void s2_4() {

    int a[] = { 1, 2, 3, 4, 5, 5, 6 };

    Map<Integer, Integer> d = new HashMap<>();

    for (int i = 0; i < a.length; i++) {

        if (d.containsKey(a[i])) {

            d.put(a[i], d.get(a[i]) + 1);

        } else {

            d.put(a[i], 1);

        }

    }

    int val_max = -1;

    int time_max = 0;

    for (Integer key : d.keySet()) {

        if (d.get(key) > time_max) {

            time_max = d.get(key);

            val_max = key;

        }

    }

    System.out.println(val_max);

}
我们来计算下这种方法的时空复杂度。代码结构上,有两个 for 循环。不过,这两个循环不是嵌套关系,而是顺序执行关系。其中,第一个循环实现了数组转字典的过程,也就是 O(n) 的复杂度。第二个循环再次遍历字典找到出现次数最多的那个元素,也是一个 O(n) 的时间复杂度。
因此,总体的时间复杂度为 O(n) + O(n),就是 O(2n),根据复杂度与具体的常系数无关的原则,也就是O(n) 的复杂度。空间方面,由于定义了 k-v 字典,其字典元素的个数取决于输入数组元素的个数。因此,空间复杂度增加为 O(n)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值