数据结构与算法:复杂度分析

1.常用数据结构与算法

数据结构是指一组数据的存储结构。算法就是操作数据的一组方法。
数据结构与算法相辅相成,数据结构为算法服务,算法要作用在特定的数据结构上。

  • 常用数据结构:
    • 数组、链表、栈、队列
    • 散列表
    • 二叉树、堆、 图
    • 跳表
    • Trie树
  • 常用算法:
    • 递归
    • 排序
    • 二分查找
    • 搜索
    • 哈希算法
    • 贪心算法
    • 分治算法
    • 回溯算法
    • 动态规划
    • 字符串匹配算法

对于以上常用的数据结构预算法,在这里仅作简单记录,后续学习过程中会对每一种数据结构及算法单独写文章记录。

2.复杂度分析

设计优异的数据结构能够节省更多的空间,而优异的算法能够节省更多的时间。因此,学习数据结构与算法的核心就是尽可能让代码运行的更快、更省存储空间。

大O复杂度表示法

  • 公式:T (n) = O (f(n)) ,其中T(n) 代表的是代码的执行总时间,f(n)表示的是每行代码执行的次数总和。而公式中的O 表示代码的执行时间T (n) 与 f(n)表达式成正比。ps:n代表的是每行代码的执行次数
  • 大O时间复杂度并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,也叫做渐进时间复杂度,简称时间复杂度。
  • 示例:
 public static int calSum(int n) {
        int sum = 0;	//执行1次
        int i = 1;		//执行1次
        int j = 1;		//执行1次
        for (; i <= n; i++) {	//执行n次
            j = 1;		//执行n次
            for (; j <= n; j++) {	//执行n*n次
                sum += i * j;	//执行n*n次
            }
        }
        return sum;
    }

假设每行代码执行都需要1个unit_time时间,则以上代码执行所需时间T(n) = (2n2 + 2n + 3)*unit_time,即f(n) = 2n2 + 2n + 3

时间复杂度

如何分析一段代码的时间复杂度?

  1. 只关注循环执行次数最多的一段代码
 public static void calSum2(int n) {
        int sum = 0;	//只执行1次
        for (int i = 0; i < n; i++) {	//执行n次
            sum += i;	//执行n次
        }
    }

如上代码中,int sum = 0; 只执行1次且与n无关,for循环中的代码执行了n次,所以总的时间复杂度就是 O(n).

  1. 加法法则:总复杂度等于量级最大的那段代码的复杂度
 public static int calSum(int n) {
        int sum = 0;
        int i = 1;
        int j = 1;
        // 循环1
        for (int k = 0; k < 1000; k++) {
            sum += k;
        }
        //循环2
        for (int k = 0; k < n; k++) {
            sum += k;
        }
        //循环3
        for (; i <= n; i++) {
            j = 1;
            for (; j <= n; j++) {
                sum += i * j;
            }
        }
        return sum;
    }

上述代码中,循环1执行了1000次,是一个常量的执行时间,与n的规模没有关系。循环2的时间复杂度为O(n),循环3的时间复杂度伪O(n2)总复杂度等于量级最大的那段代码的复杂度,因此以上代码的时间复杂度为O(n2)。
即:T1(n) = O(f(n)),T2(n) = O(g(n));
则T(n)=T1(n)+T2(n)=max(O(fn),O(g(n)))=O(max(f(n),g(n)));
3.乘法法则: 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

参照加法法则,如果T1(n)=O(f(n)),T2(n)=O(g(n));
则 T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(f(n)*g(n));

 public static void calSum2(int n) {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += getSum(i);
        }
    }
    public static int getSum(int n){
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum+=i;
        }
        return sum;
    }

上述代码中calSum2() 方法时间复杂度为O1(n),方法getSum() 时间复杂度也是O2(n),由于方法getSum()被嵌套在calSum2()方法中调用,因此calSum2()方法的时间复杂度为O(n) = O1(n)*O2(n) = O(n2);
4. 常见时间复杂度

  • 时间复杂度表
常见时间复杂度自上而下递增
常量阶O(1)
对数阶O(logn)
线性阶O(n)
线性对数阶O(nlogn)
平方阶O(n2)
立方阶O(n3)
k次方阶O(nk)
指数阶O(2n)
阶乘阶O(n!)
  • 常见复杂度分析
    • 常量阶O(1)
      如下代码中,执行时与数量级n无关,则其时间复杂度为O(1);只要代码的执行时间不随n的增大而增大,则其时间复杂度都是O(1);一般情况下,只要代码中没有循环语句、递归语句,即是代码量再多,其时间复杂度也是O(1);
int m = 10;
int k = 6;
int sum = m * k;
  • 对数阶O(log(n))
public static int getSum(int n) {
        int sum = 0, i = 1;
        while (i < n) {
            sum += i;
            i = i * 3;
        }
        return sum;
    }

在上述代码中,当i>=n时推出循环,每次循环i都乘3,假设循环次数为k,则i=3k;达到循环退出的临界值时i >= n ,即3k >= n,等号两边同时取以3为底的对数,可得 k = l o g 3 n log_3{n} log3n,即以上代码的时间复杂度为 l o g n log{n} logn

ps:对数之间是可以互相转换的, l o g 3 n log_3{n} log3n = l o g 3 2 log_3{2} log32 * l o g 2 n log_2{n} log2n = l o g 3 2 log_3{2} log32 * l o g n log{n} logn ,而在计算时间复杂度的时候,系数是可以忽略不计的,所以 l o g 3 n log_3{n} log3n 可以看做是 l o g n logn logn;同理,在计算时间复杂度的时候,不管是以几为底的对数,都可以写作O( l o g n logn logn)

  • 线性对数阶O(nlogn)
    如果一段代码的时间复杂度是 l o g n logn logn,而这段代码又执行了n遍,那么总的时间复杂度就是 n l o g n nlogn nlogn.如下代码所示:
 public static int getSum(int n) {
        int sum = 0, i = 1;
        for (int j = 0; j < n; j++) {
            while (i < n) {
                sum += i;
                i = i * 3;
            }
        }
        return sum;
    }
  • O(m+n)、O(m * n)
    这种形式的时间复杂度是由两个数据规模决定的。我们无法判断m和n哪一个数据量级大,因此在计算时间复杂度的时候无法使用加法法则省略掉其中一个,因此时间复杂度会出现O(m+n)的情况。但是乘法法则同样是适用的.

补充:

  • 最好情况时间复杂度:在最理想的情况下,执行这段代码的时间复杂度
  • 最坏情况时间复杂度:在最糟糕的情况下,执行这段代码的时间复杂度
  • 平均时间复杂度:最好、最坏情况出现的概率极低,为了将各种情况都考虑进去,引入平均时间复杂度。
  • 均摊时间复杂度

空间复杂度

  • 空间复杂度分析可以参照时间复杂度分析方法使用大O表示法表示;空间复杂度表示算法的存储空间与数据规模之间的增长关系。
  • 常见的空间复杂度有:O(1)、O(n)、O(n2),对数阶复杂度基本用不到

文章仅用作学习过程记录,如有不当,欢迎交流指正~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
算法数据结构涵盖了以下主要内容: 数据结构(Data Structures): 逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值