算法与数据结构之算法复杂度

目录

一、前言

二、时间复杂度

1. 概念

2. 符号表示

3. 常见种类

        1) 复杂度大小关系

        2)常数 O(1)

        3) 线性O(N)

        4)平方O(N^2)

        5)指数O(2^N)

        6)阶乘O(N!)

        7)对数O(logN)

        8)其他时间复杂度

三、空间复杂度

1. 概念与定义

        1)涉及到的空间类型

        2)定义

        3)根据来源,可以分为三类

2. 符号表示

        1)最差输入数据

        2)最差运行节点

3. 常见种类

        1)复杂度大小关系

        2) 常数 O(1)

        3)线性O(N)

        4)平方O(N^2)

        5)指数O(2^N)

        6)对数O(logN)

四、总结


一、前言

本系列主要参考和学习leetcode的《图解算法数据结构》一书,如有需要,可以跳转此链接https://leetcode-cn.com/leetbook/detail/illustration-of-algorithm/

如若涉及侵权,请告知作者删改。

本文主要探讨算法的复杂度,分时间和空间两个角度

二、时间复杂度

1. 概念

指输入数据大小为N时,算法运行的时间。但是却不是算法的绝对时间,而是统计算法计算操作数量。计算操作数量与运行绝对时间呈正相关。运行时间受编程语言、编译环境、使用的CPU和GPU的影响。

计算操作数量具体的是指操作数随数据大小N变化的变化数量。即与N的关系,如1次操作,10次操作都是O(1),常数性操作。而N次和100N次都是O(N),线性复杂度。

2. 符号表示

有最差、平均、最优三种情况,对应有O、Θ 和 Ω 三种符号。一般有O来表示

3. 常见种类

        1) 复杂度大小关系

O(1)< O(log N) < O(N) < O(N^2) < O (2^N) < O(N!)

        2)常数 O(1)

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

int alogorithm(int N)
{
    int count = 0;
    int a = 10000;
    // 计算操作次数与N无关,为常数性,无论多大都为O(1)
    for(int i = 0; i < a; i ++)
    {
        count++;
    }
    
    return count;
}

        3) 线性O(N)

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

int alogorithm(int N)
{
    int count = 0;
    int a = 10000;
    // 与N有关
    for(int i = 0; i < N; ++ i)
    {
        // 第二层循环与N,无关
        for(int j = 0; j < a; ++ j)
        {
            count++;
        }
    }
    // 综上,只有一重循环与N有关,故O(N*1),即O(N)

    return count;
}

        4)平方O(N^2)

只需将上述代码第二重循环略加修改即可

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

int alogorithm(int N)
{
    int count = 0;
    int a = 10000;
    // 与N有关
    for(int i = 0; i < N; ++ i)
    {
        // 第二层循环与N也有关
        for(int j = 0; j < N; ++ j)
        {
            count++;
        }
    }
    // 综上,两重循环独立,都与N相关,故为O(N*N),即O(N^2)

    return count;
}

此处浅谈以下,常见的一种排序,冒泡排序的时间复杂度即为O(N^2)

代码如下:

vector<int> bubbleSort(vector<int>& nums)
{
    // 获取数组的大小
    int N = nums.size();
    for(int i = 0; i < N - 1; i ++)
    {
        for(int j = 0; j < N - 1; ++ j)
        {
            if(nums[j] > nums[j + 1])
            {
                // 交换两个元素
                swap(nums[j], nums[j + 1])
            }
        }
        
    }
}

        5)指数O(2^N)

常用于递归

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

int alogorithm(int N)
{
    if(N <= 0)
    {
        return 1;
    }
    // 当N大于0的时候,每次递归都会出现2次操作
    int count_1 = alogorithm(N - 1);
    int count_2 = alogorithm(N - 1);
    return count_1 + count_2;
}

        6)阶乘O(N!)

对应高中数学提到的全排列,常用递归实现,其原理:第一层分裂N个,第二层N-1个,……,直至第N层终止并回溯

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

int alogorithm(int N)
{
    if(N <= 0)
    {
        return 1;
    }
    int count = 0;
    // 第一层分裂N次(N次循环),每次循环都是调用alogorithm(N-1)
    for(int i = 0; i < N; ++ i)
    {
        count += alogorithm(N - 1);
    }
    return count;
}

        7)对数O(logN)

与指数阶相反,指数时每次分裂出两倍,而对数则是每次排除一半,常用于二分、分支算法

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

int alogorithm(int N)
{
    int count = 0;
    float i = N;
    // 每次循环,范围都变为原来的一半
    while(i > 1)
    {
        i = i / 2;
        count ++;
    }
    return count;
}

        8)其他时间复杂度

通过上面的例子,相信你已经了解了一个道理:时间复杂度的加和与复合。类似于高等数学中的无穷小关系,在加和中结结果取较大的如:O(N) + O(1) = O(N)

而复合,则将复杂度做乘积,如:线性对数 O(NlogN) = O(N)* O(logN)

三、空间复杂度

1. 概念与定义

        1)涉及到的空间类型

·输入空间:存储输入数据所需的空间大小

·暂存空间:算法运行过程中,存储所有中间变量和对象所需的空间大小

·输出空间:算法运行返回时,存储输出数据所需的空间大小

        2)定义

通常情况下,指输入数据大小为N时,算法运行所使用的【暂存空间】+【输出空间】的总体大小

        3)根据来源,可以分为三类

·指令空间:编译后,程序指令所使用的内存空间

·数据空间:算法中的各项变量使用的空间,包括:声明的常量、变量、动态数组、动态对象等使用的内存空间。

·栈帧空间:程序调用函数基于栈实现,调用期间,占用常量大小的栈帧空间,直至返回后释放。栈帧空间的累计常用于递归调用函数。

2. 符号表示

和时间复杂度一样采用O表示,为算法最坏情况下所用的内存。最坏情况有如下两层含义:(输入整数N)

        1)最差输入数据

当N <= 10时,数组nums的长度恒为10,空间复杂度为O(1),当N > 10时,空间复杂度为       O(N);因此,空间复杂度为最差输入数据情况下的O(N)。

        2)最差运行节点

在执行 nums = [0] * 10 时,算法仅使用O(1)的大小空间,而当执行 nums = [0] * N 时,算法使用O(N)的空间;因此,空间复杂度应为最差运行节点的O(N)。

3. 常见种类

        1)复杂度大小关系

O(1) < O(logN) < O(N) < O(N^2) < O(2^N)

        2) 常数 O(1)

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

// 定义节点 Node
struct Node
{
    int val;
    Node* next;
    Node(int x) : val(x), next(NULL){}
};

// 函数test()
int test()
{
    return 0;
}

int alogorithm(int N)
{
    // 声明的变量都与N无关,皆使用常数大小的空间,O(1)
    int num = 0;
    int nums[1000];
    Node* node = new Node(0);
    unordered_map<int, string> dic;
    dic.emplace(0, "0");
}

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

// 定义节点 Node
struct Node
{
    int val;
    Node* next;
    Node(int x) : val(x), next(NULL){}
};

// 函数test()
int test()
{
    return 0;
}

int alogorithm(int N)
{
    for(int i = 0; i < N; ++ i)
    {
        // 虽然每次循环都会调用N,但是每轮调用后test()已返回,无栈帧空间的累计
        test();
    }
}

        3)线性O(N)

常用于一维数组、链表、哈希表等

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

// 定义节点 Node
struct Node
{
    int val;
    Node* next;
    Node(int x) : val(x), next(NULL){}
};

// 函数test()
int test()
{
    return 0;
}

int alogorithm(int N)
{
    // 一维数组
    int nums_1[N];
    int nums_2[N / 2 + 1];
    
    // 链表
    vector<Node*> nodes;
    for(int i = 0; i < N; ++ i)
    {
        nodes.push_back(new Node(i));
    }
    
    // 哈希表
    unordered_map<int, string> dic;
    for(int i = 0; i < N; ++ i)
    {
        dic.emplace(i, to_string(i));
    }
}

        4)平方O(N^2)

常见于矩阵以及其他类型的与N呈二次方关系的集合

#include<iostream>
#include<iomanip>
#include<cstdlib>
#include<cmath>
using namespace std;

// 定义节点 Node
struct Node
{
    int val;
    Node* next;
    Node(int x) : val(x), next(NULL){}
};

// 函数test()
int test()
{
    return 0;
}

int alogorithm(int N)
{
    vector<vector<int>> num_martix;
    for(int i = 0; i < N; i ++)
    {
        vector<int> nums;
        for(int j = 0; j < N; ++ j)
        {
            nums.push_back(0);
        }
    }
}

        5)指数O(2^N)

常见于二叉树等

        6)对数O(logN)

常见于分治算法的栈帧空间累计,数据类型转换等。如快速排序,和数字转字符串

四、总结

对于算法的优劣和性能,需从其的时间复杂度和空间复杂度两个角度分析。而在实际解决算法问题的时候,同时优化两个复杂度是困难的,往往有所牺牲,这体现了算法中的时空权衡。

在很多竞赛题(蓝桥、acm、天梯),很多需要牺牲空间来换时间。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值