数据结构和算法
1、数据结构
传统上,把数据结构分为逻辑结构和物理结构两大类
1.1、逻辑结构分类
-
集合结构:集合结构中数据元素除了属于同一个集合外,他们之间没有任何其他的关系
-
线性结构:线性结构中的数据元素之间存在一对一的关系
-
树形结构:树形结构中的数据元素之间存在一对多的层次关系
-
图形结构:图形结构的数据元素是多对多的关系
1.2、物理结构分类
逻辑结构在计算机中真正的表示方式(又称为映像)称为物理结构,也可以叫做存储结构。常见的物理结构有顺序存储结构、链式存储结构。
顺序存储结构:
把数据元素放到地址连续的存储单元里面,其数据间的逻辑关系和物理关系是一致的,比如我们常用的数组就是顺序存储结构。顺序存储结构存在一定的弊端,有个数据有个索引,当插入新元素之时,需要每个元素向后移动一位。
链式存储结构:
是把数据元素存放在任意的存储单元里面,这组存储单元可以是连续的也可以是不连续的。此时,数据元素之间并不能反映元素间的逻辑关系,因此在链式存储结构中引进了一个指针存放数据元素的地址,这样通过地址就可以找到相关联数据元素的位置。插入新元素只需要更新插入点两端元素的指针索引。
2、算法
2.1、算法初体验
根据一定的条件,对一些数据进行计算,得到需要的结果。一个优秀的算法追求以下两个目标:
- 花最少的时间完成需求;时间复杂度
- 占用最少的内存空间完成需求; 空间复杂度
2.1、算法的时间复杂度分析
事后分析估算法
- 把程序跑一遍,计算程序的运行时间
事前分析估算法
在计算机程序编写前,依据统计方法对算法进行估算,经过总结,我们发现一个高级语言编写的程序程序在计算机上运行所消耗的时间取决于下列因素:
- 算法采用的策略和方案;
- 编译产生的代码质量;(编译器)
- 问题的输入规模(所谓的问题输入规模就是输入量的多少);
- 机器执行指令的速度;(JVM)
我们研究算法复杂度,侧重的是当输入规模不断增大时,算法的增长量的一个抽象(规律),而不是精确地定位需要执行多少次,因为如果是这样的话,我们又得考虑回编译期优化等问题,容易主次跌倒。
不计那些循环索引的递增和循环终止的条件、变量声明、打印结果等操作,最终在分析程序的运行时间时,最重要的是把程序看做是独立于程序设计语言的算法或一系列步骤。我们分析一个算法的运行时间,最重要的就是把核心操作的次数和输入规模关联起来
2.1.1、函数渐进增长
概念:给定两个函数,如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么我们说f(n)的增长渐近快于g(n)。
下列算法,哪—个更快—些呢?
当输入规模n>2时,算法A1的渐近增长小于算法B1的渐近增长
随着输入规模的增大,算法的常数操作可以忽略不计
随着输入规模的增大,与最高次项相乘的常数可以忽略
最高次项的指数大的,随着n的增长,结果也会变得增长特剧快
算法函数中n最高次幂越小,算法效率越高
我们比较算法随着输入规模的增长量时,可以有以下规则∶
- 算法函数中的常数可以忽略;
- 算法函数中最高次幂的常数因子可以忽略;
- 算法函数中最高次幂越小,算法效率越高。
2.1.2、算法时间复杂度
1、大O记法
在进行算法分析时,语句总的执行次数是关于问题规模n的函数,进而分析T(n)随着n的变化情况并确定T(n)的量级。算法的时间复杂度,就是算法的时间量度,记作:T(n)=O(fn))。它表示随着问题规模n的增大,算法执行时间的增长率和fn)的增长率相同,称作算法的渐近时间复杂度,简称时间复杂度,其中f(n)是问题规模n的某个函数。需要明确,执行次数=执行时间
用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。下面我们使用大o表示法来表示—些求和算法的时间复杂度。下面算法的时间复杂度分别为:O(1)、O(n)、O(n^2)。
2、常见的大O阶
-
线性阶O(n) ,线性阶就是随着输入规模的扩大,对应计算次数呈直线增长。例子—>1到n的累加
- 平方阶O(n^2),线性阶就是随着输入规模的扩大,对应计算次数呈2次函数增长。例子—>两层的嵌套循环
- 平方阶O(n^3),线性阶就是随着输入规模的扩大,对应计算次数呈3次函数增长。例子—>三层的嵌套循环
- 对数阶O(logN),线性阶就是随着输入规模的扩大,对应计算次数呈对数函数增长。**例子—>**二分法遍历数组
- 常数阶O(1),线性阶就是随着输入规模的扩大,对应计算次数不变。*
复杂程度从低到高依次为:
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)
3、函数调用的时间复杂度分析
执行次数为n+n2+n2=2n2+n。根据大O推导规则,去掉n保留最高阶项,并去掉最高阶项的常数因子2,所以最终main方法的时间复杂度为**O(n2)**
4、最坏结果
算法分析也是类似,假如有一个需求:
有一个存储了n个随机数字的数组{14,6,46,4,6,55,87,9,521,6,2,31,6,4,15,2,6,85},请从中查找出指定的数字。
最好情况∶查找的第一个数字就是期望的数字,那么算法的时间复杂度为O(1)
最坏情况︰查找的最后一个数字,才是期望的数字,那么算法的时间复杂度为O(n)
平均情况∶任何数字查找的平均成本是O(n/2)
2.3、算法的空间复杂度分析
2.3.1、java中常见内存占用
1、基本数据类型内存占用情况:
2、计算机访问内存的方式都是一次一个字节
3、一个引用地址需要8个字节表示
例如: Date date = new Date(),则date这个变量需要占用8个字节来表示
4、创建一个对象,比如new Date(),除了Date对象内部存储的数据(例如年月日等信息)占用的内存,该对象本身也有内存开销,每个对象的自身开销是16个字节,用来保存对象的头信息。
5.—般内存的使用,如果不够8个字节,都会被自动填充为8字节:
6、java中数组被被限定为对象,他们一般都会因为记录长度而需要额外的内存,一个原始数据类型的数组一般需要24字节的头信息(16个自己的对象开销,4字节用于保存长度以及4个填充字节)再加上保存值所需的内存。
2.3.2、算法的空间复杂度
算法的空间复杂度计算公式记作:S(n)=O(f(n)),其中n为输入规模,f(n)为语句关于n所占存储空间的函数。
案例:
对指定的数组元素进行反转,并返回反转的内容,下面两个的空间复杂度分别为O(1),O(N)