初识数据结构与算法(二)
[2020年7月6号]
继昨天提到的什么是数据结构之后,今天我们一起学习的是第二章的内容:什么是算法?还是希望大家能给我一下意见和建议在提升博客质量方面上。
二.什么是算法
1. 基本概念
我第一次接触编程的时候,总是听一些大佬提起用什么什么算法来解决这个问题更好一些,当时觉得算法就是一种高逼格的东西。其实不然,你写的每个程序其实大体上都可以称作是算法。算法在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
但是同样都是算法,肯定就会有好坏之分。我们用下面这个题目来举个例子:写一个程序求1+2+3+…+100的和。
刚学编程的人会大概率会这么写:
int sum = 0; //执行了一次
for(int i =0;i < 100;i++) //执行了n+1次
{
sum = sum + i; //执行了n次
}
std::cout<<sum<<endl; //执行了一次
当然有些小伙伴可能看出这其实是个等差数列,那么我们就可以利用等差数列的性质来求解:
int sum = 0,n = 100; //执行了一次
sum = (1 + n) * n / 2; //执行了一次
std::cout<<sum; //执行了一次
由上述代码我们可以看出两种解决方案的执行效率,方案A执行了2n+3次,方案B只执行了3次,显而易见算法B来的更加有效。
2. 算法特性
算法具有基本的五个基本特性:
- 输入:零个或多个输入
- 输出:一个或多个输出
- 有穷性:执行有限步骤后自动结束,不会出现死循环
- 确定性:每一个步骤都有确定的含义,没有二义性
- 可执行性:每一步都能在有限次数内完成
2. 算法的设计要求
- 正确性:没有正确一切都是空谈
- 可读性:大家一定记住代码是给人看的
- 异常处理能力:输入不合法数据时能做出相关处理
- 高效率:时间效率高和存储量低
3. 算法时间复杂度
我们通常将算法的时间复杂度记为:
- T(n)= O(f(n))
其中T(n)是语句总的执行次数,f(n)是问题规模n的某个函数,整个式子表示随着问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同。
以下例举一些常见的时间复杂度
执行次数函数 | Value | 非正式术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n+3 | O(n) | 线性阶 |
3n^2+2n+1 | O(n2n^2n2 ) | 平方阶 |
5log2n\log_2^nlog2n +20 | O(lnn\ln nlnn) | 对数阶 |
2n+3nlog2n\log_2^nlog2n+19 | O(nlnn\ln nlnn) | nlogn阶 |
6n36n^36n3 +2n2n^2n2+3n+4 | O(n3n^3n3) | 立方阶 |
2n2^n2n | O(2n2^n2n) | 指数阶 |
常用的时间复杂度所耗费的时间从小打到依次是:
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n) |
但是生活总是会给我们不断地制造惊喜,就好比对于一些算法,情况往往总是在不断变化的。就那排序算法来举例子,给你一个打乱的序列,要求排序成一个从大到小有序序列,那么这样的算法时间复杂度又又应该如何计算呢?
4. 算法时间复杂度的最坏情况和最好情况
除非特别指定,否则我们提到的运行时间都是指最坏的情况下所运行的时间。
就拿刚才的排序算法来比较,我们用大家最熟悉的冒泡排序算法来做例子,给定一个序列,要求排列成从小到大的有序序列。那么最好的情况下就是这个序列本身就是个从小到大的有序序列,那么时间复杂度为0(1),那么最坏的情况下就是这个序列是从大到小排序的,那么时间复杂度为0(n2n^2n2)。
5. 算法空间复杂度
算法的空间复杂度是通过算法所需的存储空间实现,注意若输入数据所占空间只取决于问题本身,和算法无关,这样只需要分析该算法在实现时所需要的辅助单元即可。若算法执行时所需要的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为0(1)。
举个简单的例子,就拿冒泡排序来说,冒泡排序本身就是在原有的空间中运作,并不需要额外的空间来实现算法,所以冒泡排序的空间复杂度为0(1)。