数据结构1.2:什么是算法
算法的含义:
- 一个有限指令集
- 接受了一些输入
- 产生输出
- 必须在一定步骤后终止。
- 指令的要求:有充分明确的目标,不可有歧义。可被计算机解决的问题,不依赖于任何一种计算机语言以及具体的实现。
算法的伪码描述:
衡量算法的好坏:
频度和复杂度:频度指算法执行的次数,频度变化的规律(比如指数增长,线性增长等)指复杂度,时间复杂度的大O计法(在不运行代码的情况下进行分析)
空间复杂度S(N):根据算法写成的程序在执行时占用存储单元的长度,
时间复杂度T(N):根据算法写成的程序在执行时运行的时间长度,
大O四则运算法则:
- 加法法则,如果算法的代码是平行增加的,那么就需要加上相应的时间复杂度。
- 乘法法则,如果算法的代码增加的是循环内的嵌套或者函数的嵌套,那么就需要乘上相应的时间复杂度。
- 减法法则,如果算法的代码是平行减少的,那么就需要减去相应的时间复杂度。
- 除法法则,如果算法的代码减少的是循环内的嵌套或者函数的嵌套,那么就需要除以相应的时间复杂度。
- 举例:
#include<stdio.h> int main(){ int i,j,n=10;//执行一次 for(i=0;i<n;i++){// 执行N次 printf("时间复杂度\n");//执行N次 } for(i=0;i<n;i++){// 执行N此 for(j=0;j<n;j++){//执行N*N次 printf("空间复杂度\n");//执行N*N次 } } return 0;//执行一次 }
总共执行为2*N^2+2*n+2;复杂度为O(N^2)
大O一般规律:
- 除去最高阶项,其它次项可以忽略
- 与最高次项相乘的常数可以忽略
- 因为当N的数量足够大的时候,你会发现次项和与最高次项相乘的长度并不会影响准确度的判断,可以自己写个N来判断下。
常数阶O(1),
#include<stdio.h> int main(){ int i=0;//执行一次 printf("a");//执行一次 i++;//执行一次 i--;//执行一次 }
频度为4,时间复杂度为O(1);
对数阶O(log2n):
#include<stdio.h> int main(){ int i=1,n=10; while(i<n){ i=i*2; } }
设该代码运行次数为X,2^X=n,X=log2n;
线性阶O(n):
for(int i = 0; i < n; i++) { // 执行n次 System.out.println(i); // 执行n次 } }
线性对数阶O(nlog2n):将一段时间复杂度为O(logn)的代码执行n次
for(int i = 0; i < n; i++) { // 执行n次 while (count <= n) { // 执行logn次 count = count*2; // 执行nlogn次 } }
平方阶O(n2)
for(i=0;i<n;i++){// 执行N此 for(j=0;j<n;j++){//执行N*N次 printf("空间复杂度\n");//执行N*N次 } }
立方阶O(n3),..., k次方阶O(nk),指数阶O(2n)。
随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低
最好,最坏,平均时间复杂度:
二分查找法举例:
n+1种情况下需要考察数组中的元素个数加起来在除以n+1,就可以得到一个平均情况时间复杂度
均摊时间复杂度:
public class MyVector { private int[] data; private int size; // 数组中已存储的元素格式 private int capacity; // 数组中可容纳的最大元素个数 public MyVector() { data = new int[10]; size= 0; capacity = 10; } // 向数组末尾添加元素 public void pushBack(int e) { // 如果原有数组已满,则扩容为原数组的2倍 if (size == capacity) { resize(2*capacity); } data[size++] = e; } public void resize(int newCapacity) { if (newCapacity < size) { return; } int[] newData = new int[newCapacity]; // 把原有数组中的元素一次复制到新的数组中 for(int i = 0; i < size; i++) { newData[i] = data[i]; } data = newData; capacity = newCapacity; } }
pushBack方法是每次向数组末尾添加一个元素,然后当数组满时,进行扩容,扩容为原有数组的2倍;resize方法是用于扩容。
该函数存在两种情况,1:数组已满,不得不扩容,此时时间复杂度为O(n),
2:数组未满,正常增加,此时复杂度为O(1)。
需要进行的操作次数为n+1次,n次赋值加上1次扩容。操作总量为2n(扩容时需要复制N个数组,赋值时每次赋值1次共计赋值N次)
2n/n+1约等于2,因此该程序的时间复杂度为O(1)。
复杂度中常用最坏复杂度,其他计算方式都是比较特殊和少用的。
对一个数据结构进行一组连续操作中,大部分情况下时间复杂度都很低,只有个别情况下时间复杂度比较高,而且这些操作之间存在前后连贯的时序关系,这个时候,我们就可以将这一组操作放在一块儿分析,看是否能将较高时间复杂度那次操作的耗时,平摊到其他那些时间复杂度比较低的操作上。而且,在能够应用均摊时间复杂度分析的场合,一般均摊时间复杂度就等于最好情况时间复杂度。
例题
计算一下两种类别函数的时间复杂度
double F1(int n,double a[],double x){
int i;
double p=a[0];
for(i=1;i<=n;i++){
p+=pow(x,i)/a[i];
}
}
double F2(int n,double a[],double x){
int i;
double p=a[n];
for(i=n;i>0;i--){
p=a[i-1]+p/x;
return p;
}
}
函数1复杂度为O(n^2),函数2复杂度为O(N);
计算方式
函数1:for循环中pow函数每次循环要自乘到i次方,所以复杂度为1+2+3.....+n=(n^2+n)/2
T(n)=C1*n^2+c2*n;不但要考虑循环的复杂度,还要考虑函数的复杂度。
函数2:for循环每次循环一次,复杂度为n
T(n)=C1*n
空间复杂度:
似乎不怎么重要,开发中时间复杂度更重要
posted on 2022-06-22 10:48 dragonraje 阅读(192) 评论(0) 编辑 收藏 举报