一、首先由一个简单的问题讲一下算法
1+...+100= 这是小学时候的一道比较难的算法题,那么怎样解决呢?
下面给大家写一个比较简单的代码
int i,sum = 0, n = 100; /*定义一个整数型字符i,sum 的初始值为0,n赋值为100*/
for(i = 1; i < = n; i++) /*i 开始循环的初始值为1,i++是i每次自增1,直到增加到i<=100*/
{
sum = sum + i; /*sum是所有值的总和*/
}
printf("%d" ,sum); /*最后打印出sum的值*/
OK,这个问题完美的用6行代码解决了,但是是不是还不够短呢?
那让我们再想想怎样剪短呢?高斯解释道1+到100这之间有很多个数加起来得到100
直接套入公式就好了,
int i, sum = 0; n = 100; /*定义i为整型,sum为0,即最终结果用sum来存储,n赋值为100*/
sum = (1+n) * n / 2;
printf("%d", sum); /*打印输出5050*/
二、算法的定义
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
它的特征是:输入、输出、有穷性、确定性和可行性。
输入输出:
算法具有零个或多个输入。算法至少有一个或多个输出,算法是一定需要输出的。
有穷性:
算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。
确定性:
算法的每一步骤都具有确定的含有,不会出现二义性。
可行性:
算法的每一步都必须是可行的,也就是说,每一步都能通过执行有限次数完成。
正确性:
算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案。
可读性:
算法设计的另一目的是为了便于阅读、理解和交流。
健壮性:
当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结构。
设计算法应该尽量满足时间效率高和存储量低的需求。
三、算法效率的度量方法
1、通过设计好的测试程序和数据,利用计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
一个程序的运行时间,依赖于算法的好坏和问题的输入规模。所谓问题输入规模是指输入量的多少。
2、某个算法,随着n的增大,它会越来越优于另一算法,或者越来越差于另一算法。
四、算法时间复杂度
在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并决定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n) =O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模的n 的某个函数。
1)推导大O阶方法
1.用常数1取代运行时间中的所有加法常数。
2.在修改后的运行次数函数中,只保留最高阶项。
3.如果在最高阶项存在且不是1,则去除与这个项相乘的常数。得到的结果就是大O阶。
2)常数阶
顺序结构的时间复杂度。
int sum = 0,n = 100; /*执行一次*/
sum = (1+n) * n/2; /*执行一次*/
printf("%d, sum"); /*执行一次*/
另外,我们试想一下,如果这个算法当中的语句 sum = (1+n) *n/2 有10句,
即:
int sum = 0, n = 100; /*执行1次*/
sum = (1+n) *n/2; /*执行第1次*/
sum = (1+n) *n/2; /*执行第2次*/
sum = (1+n) *n/2; /*执行第3次*/
sum = (1+n) *n/2; /*执行第4次*/
sum = (1+n) *n/2; /*执行第5次*/
sum = (1+n) *n/2; /*执行第6次*/
sum = (1+n) *n/2; /*执行第7次*/
sum = (1+n) *n/2; /*执行第8次*/
sum = (1+n) *n/2; /*执行第9次*/
sum = (1+n) *n/2; /*执行第10次*/
五、线性阶
要确定某个算法的复杂度,关键就要分析循环结构的运行情况。
下面这段代码,它的循环的时间复杂度为 O(n),因为循环中的代码须要执行n次。
int i;
for (i = 0; i < n; i++)
{
/*时间复杂度为O(1)程度步骤序列*/
}
1)对数阶
下面这段代码,时间复杂度是?
int count = i;
while (count < n)
{
count = count * 2;
/*时间复杂度为O(1)的程序步骤序列*/
}
2)平方阶
下面例子是一个循环嵌套,时间复杂度为O(n)。
int i,j;
for (i = 0; i < n; i++)
{
/*时间复杂度为O(1)的程序步骤序列*/
}
}
for (j = 0; j < n; j++)
{
/*时间复杂度为O(1)的程序步骤序列*/
}
所以我们可以总结出,循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。
那么下面这个循环嵌套,它的时间复杂度是多少呢?
int i,j;
for (i = 0; i < n; i++)
{
for (j = i; j < n; j++) /*注意j = i 而不是0*/
{
/*时间复杂度为O(1)的程序步骤序列*/
}
}
由于当i = 0 时,内循环执行了n次,当i = 1时,执行了n-1次,.......当i=n-1时,执行了1次。所以总的执行次数为。
这样就可以得到一个经验:其实理解大O推理不算难,难的是对数列的一些相关运算,这更多的是考察数学知识能力。
我们继续看例子,对于方法调用的时间复杂度又如何分析。
int i,j;
for (i = 0;i <n;i++)
{
function(i);
}
上面这段代码调用一个函数function。
void function (int count)
{
print(count);
}
下面这段相对复杂的语句:
n++; /*执行次数为1*/
function(n); /*执行次数为n*/
int i,j;
for (i = 0; i < n; i++) /* 执行次数为n^2(n的平方) */
{
function (i);
}
for (i = 0; i < n; i++) /* 执行次数为n(n+1)/2 */
{
for (j = i;j < n;j++)
{
/*时间复杂度为O(1)的程序步骤序列*/
}
六、常见的时间复杂度
七、最坏的情况与平均的情况
1、最坏情况运行时间是一种保证,那就是运行时间将不会再坏了。在应用中,这是一种最重要的需求,通常,除非特别设定,我们提到的运行时间都是最坏情况的运行时间。
2、平均运行时间是所有情况中最有意义的,因为它是期待的运行时间。
八、算法空间复杂度
算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
完结撒花