1.算法
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每一条指令表示一个或多个操作。
1.1算法特性
算法有5个特性:输入、输出、有穷性、确定性和可行性。
- 输入输出:算法具有0个或多个输入,但至少有一个或多个输出。
- 有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每个步骤在可接受时间内完成。
- 确定性:算法的每一个步骤都具有确定的含义,不会出现二义性。
- 可行性:算法的每一步都能通过执行有限次数完成。
1.2算法设计的要求
算法不是唯一的,但是相对好的算法是存在的。
好的算法具有4个特性:正确性、可读性、健壮性、时间效率高存储量低
-
正确性:算法具有正确的输入、输出和处理无歧义性,正确反映需求,得到正确答案。
-
可读性:写代码不仅是为了让计算机运行,还为了方便他人阅读理解,可读性不好时间久了自己也会忘记写了什么。可读性是算法好坏很重要的标志。能看懂一个复杂的程序不能证明你的代码水平很高,但是能说明写代码的人代码水平很高。
-
健壮性:数据不合法时,算法也能做出相关处理,而不是产生异常崩溃或者莫名其妙的结果。
-
时间效率高和存储量低:好的算法对时间效率高和存储量低的追求就像人们总是追求用最少的时间和金钱完成一件事一样。
1.3算法效率的度量方法
提到设计算法要追求效率,要设计好的算法,效率大多数情况指的是执行时间。 如何度量一个算法的执行时间呢?
度量算法执行时间的方法有两种:事后统计方法和事前分析估算方法
- 事后统计方法:这种方法主要是通过测试程序和数据来对运行时间进行比较来确定的。这种方法具有很大的缺陷,需要很大的成本,有这样那样的缺陷,我们不予考虑。
- 事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。主要是通过计算时间复杂度的方法,测定运行时间最可靠的方法就是计算对运行时间有消耗的基本操作的执行次数。
1.4函数的渐进增长
函数的渐进增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么我们说f(n)的增长渐进快于g(n)。
判断一个算法的效率时,函数中的常数和前天次要项尝尝可以忽略,而应该关注主项(最高阶项)的阶数。
例如:一个算法的执行次数T(n)=2n^2+3n+4的时间复杂度为O(n^2).
1.5算法时间复杂度
1.5.1时间复杂度的公式
时间复杂度的公式为:
T
(
n
)
=
O
(
f
(
n
)
)
T(n)=O(f(n))
T(n)=O(f(n))
其中,n是问题的规模,T(n)是语句总的执行次数,f(n)是问题规模的某个函数。用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。
1.5.2推导大O阶方法
- 用常数1取代运行时间中的所有加法常数
如:求出来运行次数为:3n^2+4n+12,加法常数为12,且不管这个常数为多少都变成1。此时记作O(3n^2+4n+1)
. - 在修改后的运行次数函数中,只保留最高阶数
接上面,最高阶数为2,只保留阶数为2的项,即3n^2。此时记作O(3n^2).
- 如果最高阶项存在且系数不是1,则去除与这个项相乘的系数。得到的结果就是大O阶。
最高项系数为3,不为1,不管最高项系数为多少变成1便是大O阶数。即O(n^2).
例1:线性阶
int i;
for(i=0;i<n;i++){
...(时间复杂度为O(1)的程序步骤序列);
}
//这段代码循环的时间复杂度为O(n)
例2:对数阶
int count=1;
while(count<n){
count=count*2;
...(时间复杂度为O(1)的程序步骤序列);
}
//设循环次数为x,2^x=n得到x=log2(n).所以这个循环的时间复杂度为O(logn).
例3:平方阶
int i,j;
for(int i=0;i<n;i++){
for(j=i;j<n;j++){
...(时间复杂度为O(1)的程序步骤序列);
}
}
//总的循环次数为n+(n-1)+...+1=n^2/2+n/2.时间复杂度为O(n^2).
常用时间复杂度所耗费的时间从小到大依次是:
O
(
1
)
<
O
(
l
o
g
n
)
<
O
(
n
l
o
g
n
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
<
O
(
n
!
)
<
O
(
n
n
)
O(1)<O(logn)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)
O(1)<O(logn)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
1.5.3最坏情况与平均情况
我们查找一个有n个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为O(1);最差的情况是这个数字在最后一个位置呆着,那么时间复杂度就为O(n).大多数情况下既不是最好的也不是最坏的。
对算法的分析有两种方法:平均时间复杂度和最坏时间复杂度:
- 平均时间复杂度:由于现实中平均运行时间很难通过分析得到,一般都是通过一定数量实验数据估算的,因此一般不用此法。
- 最坏时间复杂度:一般没有特殊情况所说复杂度都是默认用最坏时间复杂度。
1.6算法空间复杂度
在写代码的时候,完全可以用空间来换取时间。
例如:要判断某年是不是闰年,写了个算法得出时间复杂度。另一个方法就是,事先建立一个有2050个元素的数组,然后把所有的年份按下标的数字对应,如果是闰年下标元素对应的数值就为1,否则为0。这样,要判断某一年是否为闰年就变成了查找这个数组的某一项的值是多少的问题。运算是最小化了,用空间换取时间复杂度。
算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:
S
(
n
)
=
O
(
f
(
n
)
)
S(n)=O(f(n))
S(n)=O(f(n))
其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
通常,我们都使用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求。当不用限定词地使用“复杂度”时,通常都是指时间复杂度。我们主要还是关心时间复杂度。