【Java算法(一)】时间复杂度与空间复杂度

导 读 部 分 \color{blue}导读部分
前面的1节2节可跳过,不影响后面阅读)。
本章重点讲解时间复杂度&空间复杂度

1.什么是算法?

算 法 \color{red}算法 (Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用 空 间 复 杂 度 与 时 间 复 杂 度 \color{#f1c232}空间复杂度与时间复杂度 来衡量。

1.1 高斯公式

题目:计算从1加到10000的和,结果是?利用程序求解!

1.1.1 使用for循环

package gauss_algorithm;

public class SumTo100 {

    public static void main(String[] args) {

        int initNum = 1;//初始值
        int lastNum = 10000;//最后1个值
        int sum = 0;//总值

        for(int i = initNum;i <= lastNum;i++){//遍历到每一个值
           sum += i; //加上每一个值
        }

        System.out.println("从" + initNum + "加到" + lastNum + "结果为:" + sum);//打印输出结果

    }

}

在这里插入图片描述

1.1.2 使用高斯公式求解

等差数列求和公式: (首项 + 尾项)x项数÷2
根据公式,我们可以用代码求解:

package gauss_algorithm;

public class GaussTest {

    public static void main(String[] args) {

        int initNum = 1;//初始值
        int lastNum = 10000;//最后1个值
        int sum = 0;//总值

        System.out.println("高斯公式:");
        sum = (initNum + lastNum) * (lastNum - initNum + 1) / 2;//高斯公式求解

        System.out.println("从" + initNum + "加到" + lastNum + "结果为:" + sum);//打印输出结果

    }
}

在这里插入图片描述

1.1.3 两者区别

相 同 点 : \color{red}相同点:

  1. 两者都是从1加到10000
  2. 计算出来的结果一样,都为50005000

不 同 点 : \color{red}不同点:

  1. 它们所用的代码是不一样的
  2. 它们计算出结果的花费时间也是不一样的

在这里,通过代码实践来看看,它们差在哪里?
在这里插入图片描述
从图中可以看出,用高斯公式,效率吊打for循环。这就是两个不同的算法的区别。(算法有高效的,同样也有拙劣的

1.2 学习算法相关问题

这些问题先留着(带着问题去学习算法
当系统学习了相关知识后,再回过头来看这些问题。看看自己是否能够利用所学知识,去一步步的将代码写出来。

1.2.1 一组整数求最大值

如果给出一组整数,如何去求最大值?
在这里插入图片描述

1.2.2 背包问题

如何在一个空间有限的背包下,使背包里的物品总价值最大?
在这里插入图片描述

1.2.3 求最短路径

通过给出的地图,如何找出从一个城市到另一个城市的最短路线?
在这里插入图片描述

1.2.4 运算

  1. 求出两个数的最大公因数
  2. 计算两个超大整数的和(正常方式会溢出)
  3. 用Java求矩阵运算
  4. 极限、级数和圆周率
  5. 如何解多元一次线性方程组
  6. 解一元n次方程

1.2.5 排序

排序链接:点击此处,深入了解排序
在这里插入图片描述
在这里插入图片描述

1.2.6 刷题!大量刷题

将所有知识系统学习后,就大量刷题。

刷题网站:

  1. 力扣官方网站刷题库
  2. 牛客网上面经
  3. CSDN也有刷题的地方

2.数据结构

数据结构
线性结构
数组
链表

2.1线性结构

线性结构包含两个部分:数组&链表
在这里插入图片描述

2.2 树

树:最典型的就是二叉树了。
在这里插入图片描述

2.3 图

图:更加复杂,它反应了多对多之间的关系。
在这里插入图片描述

2.4 其他数据结构

不仅仅只有线性结构、树和图,还有很多的千奇百怪的数据结构。但万变不离其宗,它们都是基本数据结构演变过来的。

3.时间复杂度(本章重点)

正 式 进 入 本 章 正 题 ! \color{#9900ff}正式进入本章正题!

时间复杂度的定义:
若存在函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于0的常数,则称f(n)是T(n)的同数量级函数。记作T(n) = O(f(n)),称为O(f(n))。O为算法的渐进时间复杂度,简称时间复杂度

在这里插入图片描述

通 俗 理 解 法 : \color{red}通俗理解法: 时间复杂度越高,基本执行次数越高!(执行的次数越高,花费的时间肯定更长!)

首先来介绍时间复杂度有哪些?
图片链接地址:这里有专门讲解时间复杂度,点击此处了解详情!
在这里插入图片描述

补充: 还有一种时间复杂度,O(mn) 比O(n2)要小。

这里也有一些关于时间复杂度的信息,点击此处查询!(下面的图来源)
在这里插入图片描述

时间复杂度关系(不是绝对的):
O(1) < O(log2n) < O(n) < O(mn) < O(n2) < O(n3)
在这里插入图片描述
用直角坐标系来表示时间复杂度的关系(这是理想情况下,正常应该会有交点
在这里插入图片描述
在这里插入图片描述

接 下 来 介 绍 4 中 比 较 常 见 的 时 间 复 杂 度 \color{#00ff00}接下来介绍4中比较常见的时间复杂度 4

3.1 线性时间复杂度O(n)

线性时间复杂度,相对来说比较简单。
简单来说,线性就是指一元一次方程
函 数 表 达 式 : \color{red}函数表达式: T(n) = n
在这里插入图片描述
数学上是一元一次方程,那么在Java程序中如何表示

    //线性的时间复杂度 -> 时间复杂度为:n
    public static void test1(int n){
        for(int i = 0;i < n;i++){
            System.out.println(i);
        }
    }

简单点说,就是利用for循环顺序执行下来。
能够打印多少行代码取决于参数n的大小! 如 图 所 示 \color{red}如图所示
在这里插入图片描述

3.2 对数时间复杂度O(log2n)

对数:初、高中时学过,这里简单的提一下。(函数:y = log2x)
函 数 表 达 式 : \color{red}函数表达式: T(n) = log2n
在这里插入图片描述
数学用对数函数表示,那么在Java程序中如何表示

   //对数型的时间复杂度 -> 时间复杂度为:log n
    public static void test2(int n){       
        //对数型:每执行一次,取一半再一次进行
        for(int i = n;i > 1;i /= 2){
            System.out.println(i);
        }
    }

在这里插入图片描述

3.3 常量时间复杂度O(1)

常量:这个可能是时间复杂度最简单的吧!
在数学上,就是y = C (C表示常数)
函 数 表 达 式 : \color{red}函数表达式: T(n) = C(常数)
在这里插入图片描述
数学用常数表示,那么在Java程序中如何表示

    //常数的时间复杂度 -> 时间复杂度:固定
    public static void test3(int n){
        System.out.println(1);//始终执行一次,不由参数n决定
    }

在这里插入图片描述

3.4 平方时间复杂度O(n2)

平方时间复杂度,可能是这四个当中比较难懂的。
还是老样子,讲讲数学上是如何定义平方的。
数 学 上 的 函 数 : \color{#e06666}数学上的函数: y = x2
在这里插入图片描述
这是数学上最典型的一元二次方程,也是最简单的。
接下来,我将细细讲解如何在Java程序中创建一个类似一元二次方程。

这里Java程序的一元二次方程,只是类似!真的去用Java来写一元二次方程较麻烦,这个属于后面的章节。

在这里如何显示的是y = 0.5n2 + 0.5n,用来打印输出语句基本执行的次数。

    /*
        具体思路:
            1.打印n行(参数n)
            2.第i行打印i个数。比如:第2行打印2个数
            3.根据以上,可以总结规律==>每一行打印都比前一行多1个,符合等差数列
        拓展:
            1.根据第3点可知,可知执行次数(等差数列的求和公式)
            2.通过最后的图形可知,这是一个三角形!(以后碰到打印三角形,就可以通过这种方式)
     */
    //多项式的时间复杂度 -> 时间复杂度:n^2
    public static void test4(int n){
        int num = 1;//记录次数
        for(int i = 0;i < n;i++){//打印n行数据
            for(int j = 0;j <= i;j++,num++){//每一行最多打印i个数据
                System.out.print(num + "\t");
            }
            System.out.println();
        }
    }

因为涉及到O(n2),两个for循环都有。
在这里插入图片描述
这 个 其 实 符 合 高 斯 公 式 \color{red}这个其实符合高斯公式 (前面第1节有讲到)

  1. 第一行打印 1 \color{#0024ff}1 1
  2. 第二行打印 2 \color{#0024ff}2 2
  3. 第三行打印 3 \color{#0024ff}3 3
  4. …等
  5. 第n行打印 n \color{#0024ff}n n

总 结 规 律 : \color{#0024ff}总结规律: :每一行都比前一行多一个
这就符合等差数列,它们的公差都是1。
从1行到n行,求共打印多少个数据?就用高斯公式!
通过上面代码输出的结果的最后一个数,我们也就更加证明了这个可以采用高斯公式。(这里采用的n值:10
在这里插入图片描述

3.5 时间复杂度也有很大的差异

在本小节当中,说明 为 什 么 重 视 时 间 复 杂 度 ! \color{red}为什么重视时间复杂度!
一般看来,我们认为O(n) < O(n2),但实际情况不是这样的。
我们能写时间复杂度为O(n)的程序,绝不写两个for循环!
在数学上用直角坐标系来表达二者关系,如下图。
在这里插入图片描述
再来用Java代码举例,用程序将数据列出来。

假设输入规模为:n
算法A的执行次数是T(n) = 100n * 100 时间复杂度O(n) ==>高效算法
算法B的执行次数是T(n) = 5n2 时间复杂度O(n2) ==> 低效算法

在这里插入图片描述
这里贴上代码:

public class TestComplexity {

    public static void main(String[] args) {
        System.out.println("\t\t\t" + "T(n) = 100*n*100" + "\t" + "T(n) = 5*n*n");
        System.out.println("n = 1" + "\t\t\t" + test1(1) + "\t\t\t" + test2(1));
        System.out.println("n = 5" + "\t\t\t" + test1(5) + "\t\t\t" + test2(5));
        System.out.println("n = 10" + "\t\t\t" + test1(10) + "\t\t\t" + test2(10));
        System.out.println("n = 100" + "\t\t\t" + test1(100) + "\t\t\t" + test2(100));
        System.out.println("n = 1000" + "\t\t" + test1(1000) + "\t\t" + test2(1000));
        System.out.println("n = 10000" + "\t\t" + test1(10000) + "\t\t" + test2(10000));
        System.out.println("n = 100000" + "\t\t" + test1(100000) + "\t\t" + test2(100000));
        System.out.println("n = 1000000" + "\t\t" + test1(1000000) + "\t\t" + test2(1000000));
        System.out.println();
        System.out.println("当n = 1000时,两者之间大小");
        if(test1(1000) < test2(1000)){
            System.out.println("T(n) = 100*n*100  <  T(n) = 5*n*n");
        }else{
            System.out.println("T(n) = 100*n*100  >  T(n) = 5*n*n");
        }
        System.out.println();
        System.out.println("当n = 10000时,两者之间大小");
        if(test1(10000) < test2(10000)){
            System.out.println("T(n) = 100*n*100  <  T(n) = 5*n*n");
        }else{
            System.out.println("T(n) = 100*n*100  >  T(n) = 5*n*n");
        }
    }

    //研究T(n) = 100n * 100
    public static long test1(long n){//参数n:输入规模
        return 10000 * n;
    }

    //研究T(n) = 5*n*n
    public static long test2(long n){//参数n:输入规模
        return 5*n*n;
    }

}

4.空间复杂度

4.1 例题

了解时间复杂度后,接着干空间复杂度!
我们可以通过一个例题来细细讲解利用空间能够带来什么好处。

题目:假设这里有一组数据:3 1 2 5 4 9 7 2
问:找出其中重复的两个数。

4.1.1 方法一:利用双重循环求解

【此处放视频】
视频讲解地址(无声版),动态演示如何求重复数字

代码:

public class SearchRepeat {

    public static void main(String[] args) {

        //给定一个数组
        int[] array = new int[]{3,1,2,5,4,9,7,2};

        //找出重复的数字
        System.out.println("重复数字:" + test1(array));
    }

    public static int test1(int[] array){

        int repeatNumber = 0;//重复数字

        for(int i = 0;i < array.length;i++){//遍历完整个数组,做到”不漏“
            for(int j = 0;j < i;j++){//回顾比较,看是否有相同的数字
                if(array[i] == array[j]){//判断回顾的数(即已经遍历过的数),是否有相等的数
                    repeatNumber = array[i];//如果找到相同的数,将该值定义为重复的数
                }
            }
        }
        return repeatNumber;//返回重复数字(这个程序有局限性,不能判断重复的数字是0)
    }
}

4.1.2 方法二:利用散列表

上一种方法典型的属于牺牲时间换取空间(不可取!)
在这里将要采用中间变量 (其中用到的就是空间复杂度的知识)
在这里插入图片描述

import java.util.Hashtable;
import java.util.Map;

public class HashTable {

    public static void main(String[] args) {

        //给定一个数组
        int[] array = new int[]{3,1,2,5,4,9,7,2};

        //找出重复的数字
        System.out.println("重复数字:" + test1(array));
    }

    public static int test1(int[] array){

        int repeatNumber = 0;
        Map<Integer, Integer> map = new Hashtable<>(array.length);

        //遍历整个数组
        for (int i : array) {
            //判断遍历的数,是否包含在map里
            if (map.containsKey(i)) {
                map.put(i, 2);//重复元素,将值改为2
            } else {
                map.put(i, 1);//添加元素进入散列表
            }
        }

        //判断其中的值是否有2
        if(map.containsValue(2)){
            //有重复数字
            for (int i : array) {
                if (map.get(i) == 2) {//找到重复数字
                    repeatNumber = i;//将该值赋值给repeatNumber
                }
            }
        }
        return repeatNumber;
    }
}

4.2 了解空间复杂度

空 间 复 杂 度 \color{red}空间复杂度 (Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。同样适用大O表示法!
在这里插入图片描述算法的存储空间包括三大部分:

1.程序本身所占空间。

2.输入数据所占空间。

3.辅助变量所占空间。

空 间 复 杂 度 : \color{blue}空间复杂度:
在这里插入图片描述

4.3 空间复杂度O(1)

程序本身也占有一定的内存空间。比如声明一个变量。

int a;

4.4 空间复杂度O(n)

int[] array = new int[10];

4.5 空间复杂度O(n2)

int[][] array = new int[10][10]

4.6 递归

递归虽然没有明面上声明变量或者数组,但还是会分配一定的内存空间,用来存储“方法调用栈”。

public class Recursive {

    private static int count = 0;

    public static void main(String[] args) {

//        System.out.println(func(5));
        int c = func(5);
        System.out.println("count = " + c);

    }

    //递归思想
    public static int func(int n){

        //调用一次func(int n)方法,就增加一次
        count++;

        if(n <= 1){
            return count;
        }
        System.out.println("n = " + n);
        //递归
        return func(n - 1);
    }
}

在这里插入图片描述
递归算法的空间复杂度与递归深度有关,在上面程序当中与参数n的大小有关。

5.复习与总结

在这里插入图片描述

posted @ 2022-03-29 16:03  辰梦starDream  阅读(10)  评论(0编辑  收藏  举报  来源