数据结构与算法
引言
发现不写笔记,不造轮子,学习效率太低,数据结构和算法又很重要,每次看都看不完,这次下定决心总结一下,主要的方法是,看书了解概念和原理,每看完一部分做一些练习题,参考书籍有:《算法》,《数据结构与算法分析 C语言描述》,还有本王道的单科数据结构。
本笔记中的代码均是由Java实现,项目源码地址如下:
GitHub:https://github.com/cyhe/algorithm.git
一、数据结构
数据结构是什么?是存在特定关系的数据元素的集合(比如线性表,相同数据类型的有限序列,除第一个元素外,每个元素有且仅有一个直接前驱,除最后一个元素外,每个元素有且仅有一个直接后继)。
数据结构包括三个方面:逻辑结构,存储结构,数据的运算。一个算法的设计取决于选定的逻辑结构,而实现则依赖于采用的存储结构。
1. 逻辑结构:描述元素之间的逻辑关系,分为线性和非线性结构。
数据逻辑结构分类图:
2. 存储结构:也称物理结构,主要有一下几种类型:顺序存储,链式存储,散列(Hash)存储和索引存储。
(1)顺序存储:底层使用数组实现,逻辑上相邻的,物理位置上也相邻。优点,随机存取;缺点,使用一块相邻的存储单元,可能产生碎片。
(2)链式存储:使用元素的存储地址表示元素之间的逻辑关系。优点,不产生碎片,充分利用存储单元;缺点,指针多占用一个存储单元,只能顺序存取。
(3)散列存储:根据元素的关键字直接计算出该元素的存储地址,优点,检索,增加,删除节点的操作都很快;缺点,hash函数不好,会产生冲突。
(4)索引存储:存储元素时,维护一个索引表(关键字,地址)。优点:检索速度快;缺点:较多的存储空间,增删元素需修改索引表。
3. 数据运算:运算的定义和实现
运算的定义是针对逻辑结构的,指出功能;运算的实现针对存储结构,指出具体的步骤。
二、 算法(algorithm)
算法对特定问题求解步骤的描述,与编程语言无关,是一种适合计算机程序解决问题的方法。
算法5个重要的特性:
1)有穷性:一个算法必须总是(对任何合法的输入值)在执行有穷步后结束,且每一步都可在有穷时间内完成;
2)确定性:算法中每一条指令在理解时不会产生二义性,即对于相同输入只能得到相同的输出;
3)可行性:算法的描述的操作可以通过已实现的基本运算执行有限次实现;
4)输入:一个算法有零个或多个输入;
5)输出: 一个算法有零个或多个输出,同输入有特定的关系。
一个好的算法应该有以下几点:可读的,正确的,健壮的(一些边界和非法值的检查),高效率低存储(运行时间快,所需最大存储空间小,与问题的规模有关)。
1. 算法的时间复杂度
算法中所有语句的频度(重复执行的次数)之和记作T(n),它是算法问题规模n的函数,时间复杂度就是分析T(n)的数据量级。算法中基本运算(最深层循环的语句)的频度和T(n)同数量级,所有通常采用算法中基本运算的频度f(n)来分析算法的时间复杂度,即: T(n) = O(f(n))
注:时间复杂度不仅依赖问题的规模,也取决于输入数据的性质。
常见的渐近时间复杂度:
O(1) < O(log2n) < O(n) < O(nlog2n) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
计算方法:
(1)循环主体中的变量参与循环条件的判断
找出主体句与T(n)成正比的循环变量,代入条件计算即可。
比如,
int i = 1;
while(i < n){
i = i*2;
}
其中i乘以2的次数即是T(n)(即i=2T(n)),故2T(n)<=n => T(n)<= log2n;
int y = 5;
while((y+1)*(y+1) < n){
y=y+1;
}
其中y加1的次数是T(n),又y初值为5,所以,y=T(n)+5,代入条件得出T(n)<(根号n)-6,即T(n)=O(根号n)
(2)循环主体中的变量与循环条件无关
主要计算方法有:直接累计循环次数;嵌套循环从内到外分析,忽略单步和条件判断语句,只关注主体语句的执行次数。此类问题又可分为递归(用公式进行递推)和非递归(直接累计次数)。
2. 算法的空间复杂度
定义该算法耗费的存储空间,记为S(n)。
算法原地工作所需辅助空间是常量,即O(1)