dragonraje

导航

数据结构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  阅读(167)  评论(0编辑  收藏  举报