【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加到10000
- 计算出来的结果一样,都为50005000
不 同 点 : \color{red}不同点: 不同点:
- 它们所用的代码是不一样的
- 它们计算出结果的花费时间也是不一样的
在这里,通过代码实践来看看,它们差在哪里?
从图中可以看出,用高斯公式,效率吊打for循环。这就是两个不同的算法的区别。(算法有高效的,同样也有拙劣的)
1.2 学习算法相关问题
这些问题先留着(带着问题去学习算法)
当系统学习了相关知识后,再回过头来看这些问题。看看自己是否能够利用所学知识,去一步步的将代码写出来。
1.2.1 一组整数求最大值
如果给出一组整数,如何去求最大值?
1.2.2 背包问题
如何在一个空间有限的背包下,使背包里的物品总价值最大?
1.2.3 求最短路径
通过给出的地图,如何找出从一个城市到另一个城市的最短路线?
1.2.4 运算
- 求出两个数的最大公因数
- 计算两个超大整数的和(正常方式会溢出)
- 用Java求矩阵运算
- 极限、级数和圆周率
- 如何解多元一次线性方程组
- 解一元n次方程
1.2.5 排序
排序链接:点击此处,深入了解排序
1.2.6 刷题!大量刷题
将所有知识系统学习后,就大量刷题。
刷题网站:
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 \color{#0024ff}1 1个
- 第二行打印 2 \color{#0024ff}2 2个
- 第三行打印 3 \color{#0024ff}3 3个
- …等
- 第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的大小有关。