参考视频: 【黑马程序员】2020最新数据结构与算法教程(求职面试必备)
参考leetcode学习资料: 图解算法数据结构
文章目录
一. 数据结构和算法概述
1.1 数据结构简介 :
-
数据结构含义 : 一门研究 非数值计算的 程序设计问题中的 操作对象,以及他们之间的 关系和 操作等相关问题的学科
-
数据结构分类 :把数据结构分为逻辑结构和物理结构两大类
-
逻辑结构 :
- 从具体问题中抽象出来的模型, 是抽象意义上的结构,按照对象中 数据元素之间的 相关关系分类,分为
-
集合结构 : 集合结构中数据元素除了属于同一集合外,他们之间没有任何其他的关系
-
线性结构 : 线性结构中的数据元素之间存在一对一的关系
-
树形结构 : 树形结构中的元素之间存在一对多的层次关系
-
图形结构 : 图形结构的数据结构元素是多对多的关系
-
物理结构分类 :
逻辑结构在计算机中真正的表示方式(又称为映像)称为物理结构, 也可以叫做存储结构。
常见的物理结构 : 顺序存储结构,链式存储结构
-
顺序存储结构 : 把数据元素放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的,比如我们常用的数组就是顺序存储结构
-
链式存储结构 :
是把数据元素存放在任意的存储单元里, 这组存储单元可以是连续的也可以是不连续的。此时。
- 数据结构之间并不能反映元素间的逻辑关系。因此,在链式存储结构中引入一个指针存放数据元素的地址,这样通过地址可以找到相 关联 数据元素的位置
-
1-2 算法
-
算法是指解题方案的准确而正确的描述, 是一系列解决问题的清晰指令,算法代表着用系统的方法解决问题的策略机制,也就是说,能够对一定规范的输入,在有限时间内获得多要求的输出
- 即 : 根据一定的条件,对一些数据进行计算, 得到需要的结果
-
在程序中,可以用不同的算法解决相同的问题,而不同的算法的成本是不相同的。一个优秀的算法追求以下两个目标:
- 花最少的时间完成需求
- 占用最少的内存空间完成需求
1-3 算法的时间复杂度分析
-
事前分析估算法
在计算机程序编写前,依据统计方法对算法进行估算。经过总结,我们发现一个高级语言编写的程序在计算机上运行所消耗的时间取决于下列因素
- 算法采用的策略和方案
- 编译产生的代码质量 (XXXXXXXXXX)
- 问题的输入规模(所谓的问题输入规模就是输入量的多少)
- 机器执行指令的速度(XXXXXXXXXXX)
-
事后分析估算法
通过设计好的测试程序和测试数据,利用计算机计时器对不同的算法编制的程序的运行时间进行比较,从而确定算法效率的高低
- 缺陷 : 必须依据算法实现好的测试程序,通常要花费大量的时间和经历;并且不同的测试环境(硬件环境)的差别导致测试的结果差异也很大
-
package test; // 计算100个1 +100个2 + 100个100的结果 public class test1_3 { public static void main(String[] args) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { sum += i; } } System.out.println("sum" + sum); } }
- 在研究算法的效率时,只考虑核心代码的执行次数
- 研究算法复杂度,侧重的是当输入规模增大时,算法的增长量的一个抽象规律,而不是精确定位需要执行多少次,如果这样,又得重新考虑编译期优化的问题,容易主次颠倒
-
不关心编写程序的语言是什么,不关心这些程序将跑在什么计算机上,只关心它所实现的算法.
- 不不计循环索引的递增和循环终止的条件,变量声明,打印结果等操作,最终在分析程序的运行时间时,最重要的是把程序看作独立于程序设计语言的算法或一系列步骤
- 我们分析一个算法的运行时间,最重要的是把核心操作的次数和输入规模关联起来
1-4 算法时间复杂度 – 函数渐进增长
- 给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N , f(n)总是比g(n)大,那么我们说f(n)的增长渐进快于g(n).
- 随着输入规模的增大,算法的常熟操作 可以忽略不记
- 随着输入规模的增大,与最高次项相乘的相乘可以忽略
- 最高次项的指数大的,随着n的增长,结果也会增长特别快
- 算法函数中n最高次幂越小, 算法效率 越高
- 总结四个测试 :
- 算法函数中的常数可以忽略
- 算法函数中最高次幂的常数因子可以忽略
- 算法函数中最高次幂越小,算法效率越高
1-5 算法时间复杂度 – 大O记法
-
- 在进行算法分析时,语句总的执行次数T(n)时关于问题规模n的函数,进而分析T(n)随着n的变化情况并确定T(n)的量级.
- 算法的时间复杂度,就是算法的时间量度,记作 :T(n) = O(f(n)).
- 算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称时间复杂度,其中f(n)是问题规模n的某个函数
-
执行时间 = 执行次数
-
用算法O()来体现算法时间复杂度的记法, 我们称之为大O记法.随着输入规模n的增大,T(n)增长最慢的算法为最优算法
-
推导大O阶段的表示法有 以下规则 :
-
用常数1取代运行时间中的所有加法常数;
-
在修改后的运行次数中,只保留高阶项;
-
如果最高阶项存在,且常数因子不为 1, 则去除与这个项相乘的常数
规则的例子 :
算法一 : 3次----------> O(1) 算法二 : n+3次 ----------> O(n) 算法三 : n^2+2次 ----------> O(n^2)
-
1-6 算法时间复杂度 – 常见的大O阶
-
线性阶 : 一般含有非嵌套循环涉及线性阶,线性阶就是随着输入规模的扩大,对应计算次数呈直线增长
- 下面的代码 : 它的循环的时间复杂度为O(n),因为循环体中的代码需要执行n次
public class test1_1 { // psym public static void main(String[] args) { int sum = 0; //执行一次 int n = 100;// 执行1次 for (int i = 1; i <= n; i++) { // 执行n+1次 sum += i;//执行n次 } System.out.println("sum=" + sum); } }
-
平方阶 : 一般嵌套循环属于这种时间复杂度
- 下面这段代码,n=100,也就是说,外层循环每执行一次,内层循环就执行100次
- 总共程序需要n的平方次循环,所以这段代码的时间复杂度就是O(n^2)
package test; // 计算100个1 +100个2 + 100个100的结果 public class test1_3 { public static void main(String[] args) { int n = 1000; int sum = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { sum += i; } } System.out.println("sum" + sum); } }
-
立方阶 : 三层循环属于这种时间复杂度
-
对数阶 :
- 假设有x个2相乘后大于n,则会退出循环,由于是2*x = n,得到x=log(2)n,所以这个循环的时间复杂度为O(logn)
- 对于对数阶 ,由于随着输入规模n的增大,不管底数为多少,他们呢的增长趋势是一样的,所以我们会忽略底数
int i = 1,n=100; while(i<n){ i = i*2; }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZnlduxJE-1637979872190)(https://i.loli.net/2021/08/21/LR2IBHkOK45u3Vl.png)]
-
常数阶 :一般不涉及循环操作的都是常数阶,因为它不会随着n的增长而增加操作次数
- 时间复杂度: O(1)
int n = 100; int i = n+2;
-
总结: 对常见时间复杂度的一个总结 :
描述 增长的数量级 说明 举例 常数级别 1 普通语句 将两个数相加 对数级别 logN 二分策略 二分查找 线性级别 N 循环 找出最大元素 线性对数级别 NlogN 分治思想 归并排序 平方级别 N^2 双层循环 检查所有元素对 立方级别 N^3 三层循环 检查所有三元组 指数级别 2^N 穷举查找 检查所有子集 -
复杂程度从低到高依次为 :
O(1) < O(logn) <O(n) <O(nlogn) <O(n^2) <O(n^3)
-
1-7 算法时间复杂度 – 函数调用的时间复杂度分析
之前我们分析的都是单个函数内,算法代码的时间复杂度,接下来我们分析函数调用过程中时间复杂度
-
案例一 :
public static void main(String[] args){ int n = 100; for(int i = 0; i<n; i++){ show(i); } private static void show(int i){ sout(i); } }
- 在main方法中,有一个for循环,循环体调用了show方法,由于show方法内部只执行了一行代码, 所以show方法的时间复杂度 O(1),那main方法的时间复杂度就是O(n)
-
案例二:
-
show方法的时间复杂度O(n),最终main方法的时间复杂度为O(n^2)
public static void main(String[] args) { int n =100; show(n); for( int i = 0; i < n; i++){ show(i); } } private static void show(int i){ for( int i = 0; i < n; i++){ sout(i); } }
-
-
最坏情况
-
有一个存储了n个随机数字的数组,请从中查找出指定的数字
public int search(int num){ int[] arr = {11,10,9,34,24,34,0} for(int i = 0; i < arr.length; i++){ if(num == arr[i]){ return i; } } return -1; }
-
最好情况 : 查找的第一个数字就是期望的数字,那么算法的时间复杂度 O(1)
-
最坏情况 : 查找的最后一个数字,才是期望的数字,那么算法的时间复杂度为O(n)
-
平均情况 :任何数字查找的平均成本是O(n/2)
-
1-8 算法的空间复杂度分析 – Java中常见内存占用
-
基本数据类型内存占用情况 :
数据类型 内存占用字节数 byte 1 short 2 int 4 long 8 float 4 double 8 boolean 1 char 2 -
计算机访问的方式都是一次一个字节[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHPUPh8w-1637979872191)(https://i.loli.net/2021/08/21/W68UB9gmEyjvxfP.png)]
-
一个引用(机器地址)需要8个字节表示 :
例如: Date date = new Date(), 则date这个变量需要占用8个字节来表示
-
创建一个对象,比如new Date(), 除了Date对象内部存储的数据占用的内存,该对象本身也有内存开销,每个对象的自身开销是16个字节, 用来保存对象的头信息
-
一般内存的使用,如果不够8个字节,都会被自动填充为8字节 :
public class A{ public int a = 1; } 通过new A()创建一个对象的内存占用如下: 1. 整形成员变量a占用4个字节 2. 对象本身占用16个字节 那么创建该对象总共需要20个字节,但由于不是以8位单位,会自动填充为24个字节
-
java中数组被限定为对象,他们一般都为记录长度而需要额外的内存,一个原始数据类型的数组一般需要24字节的头信息(16个个自己的对象开销,4字节用于保存长度以及4个填充字节)再加上保存值所需的内存
1-9 算法的空间复杂度
–
每日小知识:为啥子我直接粘贴笔记,不会显示“图片外链无法获取”,因为我的typora搭有图床:
- 参考博客:Gitee搭建免费博客
- 老方便了,随后搭个博客平台或者把文件发给别人,都不用发愁自己的图片发不出去了
笔记在博主本人的博客上也有奥!全套!!!
- 翼遥bingo【持续完善中,biu~~】