【数据结构】时间复杂度与空间复杂度

本专栏使用C语言编写数据结构
C语言专栏:C语言基本语法、基本操作、相关库函数的编写,相关内存分析
C++专栏:C++基本语法C++相关结构剖析,详细例题,相关小型Demo的编写
数据结构专栏:基本数据结构原理介绍,代码实现,相关Leetcode例题讲解剖析
干货满满,陆续更新ing

目录

  • 数据结构
    • 算法的时间复杂度与空间复杂度
      • 时间复杂度
        • 示例一
        • 示例二
        • 示例三
        • 示例四
        • 示例五:strchr函数的时间复杂度
        • 示例六:冒泡排序的时间复杂度
        • 示例七:折半查找时间复杂度
        • 示例八:递归算法时间复杂度
        • 示例九:斐波那契算法时间复杂度
      • 空间复杂度
        • 示例一:冒泡排序空间复杂度
        • 示例二:变长数组的空间复杂度
        • 示例三:斐波那契使用循环变形
        • 示例四:递归函数的空间复杂度
        • 常见复杂度


数据结构

数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。

算法(Algorithm)就是定义良好的计算过程,它取一个或一组的值为输入,并产生出一个或一组的值作为输出。

算法的时间复杂度与空间复杂度

时间复杂度

算法的时间复杂度是一个函数,算法中的基本操作的执行次数,为算法的时间复杂度

时间复杂度取所有情况中最坏的情况,最坏预期的估算

示例一

//请计算Func1中++count共执行多少次
void Func1(int N){
    int count = 0;
    for(int i = 0; i < N; i++){
        for(int j = 0; j < N;j++){
            ++count;
        }
    }
    for(int k = 0;k < 2 * N;++k){
        ++count;
    }
    int M = 10;
    while(M--){
        ++count;
    }
    printf("%d\n",count);
}

上述代码的时间复杂度函数F(N)=N*N+2*N+10,由函数可得:随着N的变大,后两项对整个结果的影响变小
大O渐进表示法:O(N^2)
1. 用常数1取代运行时间中的所有加法常数
2. 在修改后的运行次数函数中,只保留最高阶项
3. 如果最高阶项存在且不是1,则去掉与这个项相乘的常数,得到的结果就是大O阶

示例二

void Func2(int N){
    int count = 0;
    for(int i = 0;i < 100;i++){
        ++count;
    }
    printf("%d\n",count);
}//O(1) --- 常数次

上述函数中:变量i循环执行了100次,count自增100次,为常数项,故为O(1)的时间复杂度。

示例三

void Func3(int N){
    int count = 0;
    for(int i = 0;i < 2 * N;i++){
        ++count;
    }
    int M = 10;
    while(M--){
        ++count;
    }
    printf("%d\n",count);
}//O(N)

上述函数中:变量i循环执行了2N次,count自增2N次,N为变量,故时间复杂度为线性阶,为O(N)的时间复杂度。

示例四

void Func4(int M,int N){
    for(int i = 0;i < M;i++){
        ++count;
    }
    for(int j = 0;j < N;j++){
        ++count;
    }
}//O(M+N)
//M远大于N:O(M)
//M与N差不多大:O(N)或者O(M)
//N远大于M:O(N)

上述函数中:变量i循环执行了M次,count自增M次,j循环执行了N次,count自增N次,M、N为变量,故时间复杂度为线性阶

  • 当M远大于N时:为O(M)的时间复杂度。
  • 当N远大于M时:为O(N)的时间复杂度。
  • 当M与N差不多大:为O(N)或者O(M)

示例五:strchr函数的时间复杂度

const char* strchr(const char * str,int character);
while(*str){
    if(*str == character){
        return str;
    }else{
        ++str;
    }
}
return NULL;
//O(N)

上述函数中:将str遍历一次,str长度未知,设为变量N,故时间复杂度为线性阶。为O(N)的时间复杂度。

示例六:冒泡排序的时间复杂度

void BubbleSort(int * a,int n){
    assert(a);
    for(size_t end = n;end > 0;--end){
        int exchange = 0;
        for(size_t i = 1; i < end;++i){
            if(a[i-1] > a[i]){
                Swap(&a[i-1],&a[i]);
                exchange = 1;
            }
        }
        if(exchange == 0){
            break;
        }
    }
}
//n n-1 n-2 n-3 n-4...2 1
//F(n) = (n-1+1)*n/2 --- 最坏情况
//F(n) = n-1
//O(n^2)

上述冒泡排序函数中:时间复杂度函数为::F(N)=(n-1+1)*n/2
时间复杂度考虑算法的最坏情况,对于冒泡排序,举例:若期望达到一个从左到右递增的数组序列,当原始数组序列为从左到右递减序列时,算法时间复杂度最大,将下标为0位置的最大元素右移至下标为n-1的位置时,Swap函数执行n-1次,依次将下标为1位置的元素右移至下标为n-2的位置时,Swap函数执行n-2次…直至得到期望数组序列,故时间复杂度为等差数列:(n-1) (n-2) (n-3) … 1求和。为O(N^2)的时间复杂度。

示例七:折半查找时间复杂度

int BinarySearch(int* a,int n,int x){
    assert(a);
    int begin = 0;
    int end = n;
    while(begin < end){
        int mid = begin + ((end-begin)>>1);//右移=除2,防止溢出
        if(a[mid] < x){
            begin = mid+1;
        }else if(a[mid] > x){
            end = mid;
        }else{
            return mid;
        }
    }
    return -1;
}
//二分查找
//最好情况:O(1)
//最坏情况:假设折半了x次 1*2*2*2*...*2=n 共x个2 x=log2(n)
//O(logN)

上述折半查找函数中:假设数组中共有n个数据,折半进行了x次,时间复杂度函数为:x=log2(n)
时间复杂度考虑算法的最坏情况,对于折半查找,最坏情况为折半查找未查找到数据,即数组中全部元素全部折半查找一遍未查找到期望数据,不停的折半(数据个数除以2),将一组数据最终折半剩下最后一个数据都未查找到假设折半次数为x次 1*2*2*2*...*2=n 共x个2 x=log2(n)。为O(logN)的时间复杂度。

示例八:递归算法时间复杂度

long long Fac(size_t N){
    if(0 == N){
        return i;
    }
    //for(size_t i = 0; i < N; i++){
    //    printf("%d\n",i);
    //}O(N*N)
    //N N-1 N-2 ...
    return Fac(N-1)*N;
}//O(N)

上述递归函数中:每次Fac调用时,N简易,直至N==0,故N自减了N次,为O(N)的时间复杂度。

递归算法的时间复杂度:

  • 如果每次函数调用是O(1),就看函数的递归次数
  • 如果每次函数调用不是O(1),就看函数递归调用次数及函数内部调用次数的累加

示例九:斐波那契算法时间复杂度

//斐波那契
long long Fib(size_t N){
    if(N < 3){
        return 1;
    }
    return Fib(N-1)+Fib(N-2);
}
//1+2+4+8....+2^(N-2)
//级别为:2^N

斐波那契图:
【数据结构】时间复杂度与空间复杂度_第1张图片

空间复杂度

空间复杂度也是一个数学函数表达式,是对一个算法在运行过程中临时占用存储空间大小的量度

空间复杂度算的是变量的个数

使用大O渐进表示法

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定,空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

示例一:冒泡排序空间复杂度

void BubbleSort(int * a,int n){
    assert(a);
    for(size_t end = n;end > 0;--end){
        int exchange = 0;
        for(size_t i = 1; i < end;++i){
            if(a[i-1] > a[i]){
                Swap(&a[i-1],&a[i]);
                exchange = 1;
            }
        }
        if(exchange == 0){
            break;
        }
    }
}
//3个空间:exchange end i
//O(1) --- 常数

上述冒泡排序函数中:函数执行过程中共开辟三个变量空间:变量i,变量exchange,变量end,3为常数,故为O(1)的时间复杂度。

示例二:变长数组的空间复杂度

int missingNumber(int* nums,int numsSize){
    int a[numsSize];//变长数组
    //...
}
//O(N) --- 线性变化,所以为O(N)量级

上述函数中:函数执行过程中共开辟numsSize个变量空间,但numsSize的值不确定,为线性变化,故为O(N)的时间复杂度。

示例三:斐波那契使用循环变形

long long* Fibonacci(size_t n){
    if(n==0){
        return NULL;
    }
    long long* fibArray = (long long*)malloc((n+1) * sizeof(long long));
    fibArray[0] = 0;
    fibArray[1] = 1;
    for(int i = 2;i <= n;i++){
        fibArray[i] = fibArray[i-1] + fibArray[i-2];
    }
    return fibArray;
}
//n+1 指针fibArray i
//O(N)

上述函数中:函数执行过程中共开辟n+1个变量空间,但n的值不确定,为线性变化,故为O(N)的空间复杂度。

示例四:递归函数的空间复杂度

long long Fac(size_t N){
    if(N==1){
        return 1;
    }
    return Fac(N-1)*N;
}
//建立栈帧
//O(N)
long long Fib(size_t N){
    if(N < 3){
        return 1;
    }
    return Fib(N-1) + Fib(N-2);
}
//O(N)

上述函数中:函数执行过程中建立栈帧,递归调用结束后释放空间,故为O(N)的空间复杂度。

时间是累计的,不可复用,空间可以重复利用
递归函数中空间使用栈帧,每次递归调用后空间释放

常见复杂度

5201314 O(1) 常数阶
3n+4 O(N) 线性阶
3n^2+4n+5 O(N^2) 平方阶
3log(2)n+4 O(logN) 对数阶
2n+3nlog(2)n+14 O(NlogN) NlogN阶
n^3 +2n^2+4n+6 O(N^3) 立方阶
2^n O(2^N) 指数阶

你可能感兴趣的:(数据结构,数据结构,算法,c语言,编程语言,开发语言)