【数据结构与算法分析】第一章、第二章总结
昨天晚上7点在长沙出发坐11个小时的火车回家,第一次坐硬卧回家还买到了一张下铺票,到底要比硬座舒服了很多。上午休整了半天下午就这干活,虽然放假但是手中的work哪能说停下就停下呢?毕竟也是自己喜欢的work。2016有很多事情都迫在眉睫,虽说是寒假但已然是1月18号,留给我的时间已经很紧张很紧张啦。回家带了5本书,一本Thinking In Java
、一本Data Structures And Algorithm Analysis In Java
, 三本Android
进阶,看起来任务还蛮重啊。
chapter.1 – 引论
第一章,引论,主要介绍这本书的将要用到的一些基础知识,包括数学知识有级数、模运算、证明方法的归纳演绎等;包括Java的递归特性以及函数对象等。
Java中实现泛型的组件主要有四个,一是所有引用类型数据的存储操作方法接收的形参都用Object
来表示,Object
是所有对象的父类,也就意味着存储方法可以接收任意引用类型的参数,比如我们可以存出一个String
类型的字符串,也可以是一个Student[]
类型的对象数组。而在读取方法中由于我们存储的类型Object
类型,所以取出时也就必然是Object
,那么问题来了,我们怎样还原到我们存储时的原本类型呢?这里就需要类型强制转换!把取出来的Object
数据强制转换为原来的String字符串或者Student[]
对象。具体看代码如下
package com.demo.datastructure;/** 方法类,封装read()、write()方法* read()---->return the stored value* write(Object x)---->x is stored* */public class Method {private Object storedData;public Object read() {return storedData;}public void write(Object x) {storedData = x;}}package com.demo.datastructure;public class StorDataTest {public static void main(String[] args) {Method m = new Method();m.write(45);// 类型强制转换,或者调用toString()方法也可以String result = (String) m.read();System.out.println("result are:" + result);}}
二是基本类型的包装类的自动装箱、自动拆箱,在Java中虽然没一个引用类型都和Object
类型相容,但是8种基本类型却不能与Object
相容,所以就有了8种基本类型各自对应的包装类,比如int
—->Integer
、boolean
—->Boolean
。比如上面的m.write(45)
就是自动装箱,把基本类型的int
型数据45自动装箱为Integer
类型,有自动装箱自然就有自动拆箱,当取出数据时如果int i = m.read()
那么此时就把Integer
类型自动拆箱为int
类型。
三是使用接口类型表示泛型。我们考虑,通过比较对象数组找出最大项的问题,我们不能直接找出对象数组的最大元素。最简单的思路就是让我们的对象数组类实现Comparable
接口,通过调用compareTo()
方法来确定数组元素的顺序,因为compareTo()
对所有的comparable
都是现成可用的。看如下代码
package com.demo.findmax;class FindMaxDemo {/** return max item in arr* precondition : arr.length > 0* */public static Comparable findMax(Comparable[] arr) {int maxIndex = 0;for (int i = 1; i < arr.length; i++) {if (arr[i].compareTo(arr[maxIndex]) > 0) {maxIndex = i;}}return arr[maxIndex];}/** Test findMax on shape and String objects* */public static void main(String[] args) {String[] st1 = {"Dell", "Jobs", "Bill", "Frank"};System.out.println(findMax(st1));}}
这是我们要注意,第一,只有实现Comparable
接口的那些对象才能作为Comparable
数组的元素被传递,仅有comparaTo()
方法但并未实现Comparable
接口的对象不是Comparable
的,它不具备必须的IS-A关系;第二,如果Comparable
数组有两个不相容的对象,比如一个String
,一个Integer
,那么compareTo()
方法将抛出ClassCastException
异常;第三,一定要注意,基本类型不能作为Comparable
传递,但是他们的包装类可以,因为包装类实现了Comparable
接口;第四,接口究竟是不是标准的库接口倒不是必须的;最后,这个方案不是总能行的通,因为有时宣称一个类实现所需的借口是不可能的,例如一个类可能是标准库中的类,而接口是自定义的接口,这就行不通,再例如一个类是final
类,那么我们就不可能再扩展它以创建一个新类。
四是数组类型的兼容性,在Java中数组是类型兼容的,称作covariant araay type
(协变数组类型),每个数组都会指明他所兼容的数据类型,如果把一个不兼容的类型插入到数组中,虚拟机将会抛出ArrayStoredException
异常。不过有时为了避免类型混乱的问题发生,就需要特别这些数组不是类型兼容的。
Java5开始提供泛型支持,这些泛型类很容易使用。但是泛型类的api
实现却不是那么容易的一件事情,内部编码非常复杂,在这里我们就不涉及。我们主要来看看下面几个方面。
首先是泛型类和接口的概念。泛型类在声明时需要为其指定一个或多个类型参数,这些类型参数放在类名后面的尖括号内,例如public class GenericDemo<String>
。也可以声明接口是泛型的,例如,在Java5以前Comparable
接口不是泛型的,而他的compareTo()
方法接收一个Object
作为参数,于是传递到compareTo()
方法的任何引用变量即使不是一个合理的类型也会编译通过,而只是在运行时抛出ClassCastException
错误。在Java5中Comparable
接口是支持泛型的。例如现在String
类实现Comparable<String>
接口并有一个compareTo()
方法,这个方法以一个String
作为其参数。通过是类编程泛型类,以前只有在运行时才能抛出的错误现在编译时就能抛出。大大提高了开发效率。
泛型通配符有两种形式,<? super ZiClass>
?表示占位符,是ZiClass的父类;<? extends FuClass>
?是FuClas
s的子类。泛型(以及泛型集合)不是协变的(但是有意义),而数组是协变的。 泛型类可以由编译器通过类型擦除过程转变成非泛型类。
chapter.2 – 算法分析
算法:是为了求解一个问题所要遵循的、被清除指定的简单指令的集合。
我们考虑这样两种情况:一个算法给定并认为是正确的,当求解一个问题的时候需要长达一年的时间,而且运行起来需要几个GB
的内存空间,这显然是不合理的。
那么上面算法运行的时间长短就是指算法的时间复杂度,算法运行所需的空间资源就是算法的空间空间复杂度。
这一章将讨论:
- 如何估算一个程序所需的时间;
- 如何将一个程序所需的时间从年、天降低到秒甚至更少;
- 粗心使用递归的后果;
- 将一个数自乘得到其幂,以及计算两个数的最大公因数的非常有效的算法。
基础,四个数学定义:
T(N) = O( f(N) )
表示T(N)
的增长率小于或等于f(N)
的增长率;T(N) = ∩( g(N) )
表示T(N)
的增长率大于或等于g(N)
的增长率;T(N) = ⊙( h(N) )
当且仅当T(N) = O( f(N) )
和T(N)= ∩( g(N) )
表示表示T ( N )
的等于h (N)
的增长率;T(N) = o( h(N) )
表示T(N)
的增长率小于p(N)
的增长率;
(1)式意味着f(N)
是T(N)
的上界,相反T(N)
是f(N)
的下界;(2)式意味着T(N)
是g(N)
的上界,相反g(N)
是T(N)
的下界;(3)式是(1)、(2)式的交集情况;(4)式意味着对于任意的N都有p(N)>T(N)
。要注意的是当两个函数的增长率相同的时候,既可以是 T(N) = O( f(N) )
,也可以是T(N)= ⊙( h(N) )
。
两个重要的法则:
1。 T1( N ) = O ( f (N) ) 且T2( N ) =∩ ( g (N) )
,那么有
(a) T1( N ) + T2( N ) = O ( f (N) + g (N) )
(或者可以写成max( O ( f (N) ) , O ( g (N) )
,虽然这种写法不正式但很直观 ) );
(b) T1( N ) * T2( N ) = O ( f (N) * g (N) )
。
2。 如果T ( N )
是一个k次多项式,则T ( N ) = ⊙ ( h (N^k) ) .
模型假设:一台标准的计算机,在机器中指令被顺序的执行,模型机做任何一件简单的工作都恰好花费一个时间单位,比如依次加法操作、依次减法操作都会花费一个时间单位;还假设模型机有无限大小的内存(当然在现实中计算机不可能在任何一个简单操作上都用相同的时间,也不可能有无限大小的内存,但是这样假设就可以避免现实中的一些严重问题,比如缺页中段,使得模型机可以处理一些高效的算法)。实际上算法分析的结果为程序在一定的时间范围内能够中终止运行提供了保障,程序可以提前结束运行,但绝不可能错后。
要分析的问题:运行时间。主要考虑平均情形和最坏情形,因为平均情形的性能反映典型行为,最坏情形的性能则代表对任何可能的情况的一种保证。偶尔我们也会考虑下最好情形,但是没有多大的代表性。还要注意,虽然我们是以Java程序为例来讨论,但是所得到的界实际上是算法的界而不是程序的界,程序是算法以一种特殊编程语言的实现,程序设计语言的细节几乎总是不影响算法分析的答案,即可以这样理解,对求解某个问题同一种算法无论是用Java语言实现,还是用C++来实现,对其算法分析的结果都是影响不大的。
案例分析:计算1^3 + 2^3 + 3^3 + 4^3 + ...... + N^3 = ?
.程序如下所示
1 public static int sum(int n) {
2 int sum = 0;
3 for (int i = 0; i <= n; i ++) {
4 sum += i*i*i;
5 }
6 return sum;
7 }
对于这个程序段的分析是简单的,所有的声明均不计时间,第2、第6行各占一个时间单元,第4行每执行一次占4个时间单元(两次乘法、一次加法、一次赋值),共执行N
次占用4N
个时间单元,第3行的初始化i、判断i<=N以及i的自增运算隐含着开销,所有这些的开销是初始化1个时间单元,所有的判断为N+1
个时间单元,而所有的自增运算为N
个时间单元,我们忽略调用方法和返回值的开销,得到总量是6N+4
个时间单元。而当N
趋近于无限大的时候,计算第2、6行的时间开销显然相对于循环是毫无意义的愚蠢的,因此总结出以下这些规则:
法则1——for循环:一个for循环的运行时间至多是该for
循环内部那些语句(包括判断)的运行时间乘以迭代的次数,即kN
;
法则2——多层嵌套for循环:如果为两层嵌套,即为(kN)^2
;
法则3——顺序语句:将各个语句的运行时间求和即可;
法则4——if/else语句:
if (condition) {S1;
} else {S2;
}
一个if/else
语句的运行时间从不超过判断的运行时间在加上S1
、S2
中运行时间较长者的运行时间的总和。虽然在某某些特殊情况下可能会过多的估计了运行时间,但是绝不会估计过低。
一般分析的策略是由内向外分析,从内部或最深层部分想歪展开分析,如果有调用则要首先分析这些方法调用,如果有递归过程那么存在几种选择,若递归实际上只是被遮上面纱的for
循环,则分析通常是简单的。
现在我们讨论求最大子序列和的案例。输入任意长度为N
的整数数组(包括负数),求出最大的子序列之和。书中提供了4个算法,如下
public static int maxSum1(int[] arr) {int maxSum = 0;for (int i = 0; i < arr.length; i++) {for (int j = i; j < arr.length; j++) {int thisSum = 0;for (int k = i; k <= j; k++) {thisSum += arr[k];if (thisSum > maxSum) {maxSum = thisSum;}}}}return maxSum;
}
这种算法的运行时间为O(N^3)
。下面看第二种算法如下,这种算法涉及到“分治策略”
public class maxSubSum2(int[] arr) {int maxSum = 0;for (int i= 0; i < arr.length; i++) {int thisSum = 0;for (int j = i; j < arr.length; j++) {thisSum += arr[j];if (thisSum > maSum) {maxSum = thisSum;}}}return maxSum;}
这种算法的运行时间为O(N^2)
。下面看第三种算法,这种算法运用了“分治策略”的思想和递归调用的实现,代码如下
private static int maxSubSum3(int[] arr) {if (left == right) { // base cseif (arr[left] > 0) {return arr[left];} else {return 0;}}int center = ( left + right ) / 2;int maxLeftSum = maxSumRec(arr, left, center);int maxRightSum = maxSumRec(arr, center + 1; right);int maxLeftBorderSum = 0, leftBorderSum = 0;for (int i = center; i >= left; i--) {leftBorderSum += arr[i];if (leftBorderSum > maxLeftBorderSum) {maxLeftBorderSum = leftBorderSum;}}int maxRightBorderSum = 0, rightBorderSum = 0;for (int i = center + 1; i <= right; i++) {rightBorderSum += arr[i];if (rightBorderSum > maxRightBorderSum) {maxRightBorderSum = rightBorderSum;}}// max3()方法返回三个参数中的最大值return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
}public static int maxSubSum3 (int[] arr) {return maxSunRec(arr, 0, arr.lenght-1);
}
不难看出上面这种算法时间复杂度是O(N logN)
,是三种算法里最简单的一种算法,但是程序看起来却更长,也就需要付出更多的编程努力。然而程序长并公布意味着程序就好,相反程序成长也并不意味着程序就不好。下面看第四种算法,代码如下
public static int maxSum4(int[] arr) {int maxSum = 0,thisSum = 0;for (int j = 0; j < arr.length; j++) {thisSum += ar[j];if (thisSum > maxSum) {maxSum = thisSum;} else {thisSum = 0;}}return maxSum;
}
这种算法的时间复杂度为O(N)
,是许多聪明的算法中的典型算法,虽然正确但是正确性却不是那么容易看出来的。这个算法的优点是只对数据进行一次扫描,一旦数组arr[i]
被读入并被处理,它就不需要被记忆。如果通过磁盘读入或者通过互联网传送读入,它就可以按顺序读入而不需要在主内存中存储数组的任何元素。不仅如此,在任意时刻算法都能给出读入序列的正确答案。
其他的算法不具有这个特性,具有这种特性的算法叫做联机算法,仅需要常量空间并以线性时间运行的联机算法几乎是完美的算法。
算法分析最混乱的方面主要集中在对数上面,某些分治算法将以O(N logN)
时间运行,对数最常出现的规律可以进行一下概括:如果一个算法用常数时间O(1)
将问题的大小消减为其一部分,通常是其1/2,那么该算法就是O(log N)
。另一方面,如果使用常数时间只是把问题减少一个常数的数量,比如减少1或者2,那么这种算法就是O(N)
的。下面给出三个具有对数特点的例子:
第一个案例:这半查找(binary search)
算法
给定一个整数X和一个随机整数数组arr[n]
, 数组已排序。找出数组中和X相等的元素arr[i]
,如果数组元素中不存在X ,则返回i = -1
。
public static <AnyType extends Comparable<? super AnyType>> int binarySearch(AnyType[] arr, AnyType x) {int low = 0, high = arr.lenght-1;while (low <= high){int mid = (low + high) / 2;if (arr[mid].compareTo(x) < 0){low = mid + 1;} else if (arr[mid].compareTo(x) > 0) {high = mid -1;} else {return mid; // found}}return NOT_FOUND; // not fonud is defined as -1
}
显然每次迭代在循环内用时为O(1)
,因此需要分析循环的次数才能确定时间复杂度。循环是从high-low=N-1
开始并在high-low<=-1
结束,每次循环后high-low的值至少将该次循环前的值折半,于是循环的次数至多是[log(N-1)]+2
。当数据不允许插入操作、删除操作的时候,或者数据已经是排好序的时候,这种操作可能是非常有用的。
第二个案例:欧几里得算法
欧几里得算法即计算最大公因数的算法,两个整数的最大公因数是同时整除二者的最大整数。代码如下
public static long gcd(long m, long n) {while (n != 0) {long rem = m % n;m = n;n = rem;}return m;
}
这是一个快速算法,迭代次数至多是2logN = O (logN)
。算法连续计算余数知道余数为0为止,最后的非零余数就是最大公因数。
第三个案例:幂运算
计算一个数的幂,通常我们得到的结果一般都是相当大的。我们用乘法的次数作为运行时间的度量。计算X^N
的明显算法就是使用N-1
次的自乘运算,但有一种递归算法的效果更好。N<=1
是这种递归的基准情形。否则,若N
是偶数,X^N = X^N/2 * X^N/2
;若N
是奇数,X^N = X^N/2 * X^N/2 * N
。显然所需的乘法次数最多是2logN
。代码如下
public static long pow(BigInteger x, int n) {if (n == 0)return 1;if (n == 1)return x;if (isEven(n))return pow(x*x, n/2);elsereturn pow(x*x, n/2) * x;
}
【数据结构与算法分析】第一章、第二章总结相关推荐
- 《数据结构与算法分析》习题-----第二章(3)(关于list的题目)
PS:这两条习题是添加到STL的List代码实现 http://www.cnblogs.com/alan-forever/archive/2012/09/12/2682437.html 3.15 给L ...
- 《算法帝国》第一章第二章读书笔记
heeeeeeeeeeeeeeeeeeeeello! 好像有半个月都没好好写笔记了,经历了两次面试,一次败在单面,一次败在群面,哈啊-说明还完全有待努力! 稍微研究了一下,这本书好像并不需要什么代码记 ...
- 【Git】版本控制管理(第二版) 前言 第一章 第二章
版本控制管理 前言 第一章 第二章 资源 前言 本书结构 第一章 介绍 总结在开头 1.1 背景 1.2 Git的诞生 1.3 先例 1.4 时间线 第二章 安装Git 2.1 使用Linux上的二进 ...
- Day1ps设计基础作业第一章第二章
Day1 ps设计基础作业第一章第二章 1.1工作区和工作流程 3种调整人像照片亮度的方式:1图像-调整-亮度/对比度,2图像-调整-色阶,3获取图像亮度+混合模式,通道(右下)按ctrl RGB的缩 ...
- Java 北大青鸟 第一学期 第二章 上机练习
Java 北大青鸟 第一学期 第二章 上机练习 手中牌互换 华氏度摄氏度 银行定期储蓄业务 数据类型 源文件下载 手中牌互换 public static void main(String[] args ...
- 【吃瓜笔记】第一章第二章
[吃瓜笔记]第一章&第二章 一.基本术语 二.模型评估与选择 1.评估方法 (1).留出法 (2).交叉验证法 (3).自助法 2.选择依据 (1).性能度量 1).错误率与精度 2).查准率 ...
- 第一篇第二章火灾的基础知识
沿外墙面蔓延的情况 需要注意:层高要足够高 要不下层着火会直接蔓延到上层 喷头系统必须在轰然之前进行灭火 否则灭火失败 2019/1/3 [录播]2018一消精华班-实务-一级消防工程师-环球网校 h ...
- 谈谈在计算机系统中引入操作系统,初中信息技术第一册第二章第1节《操作系统简介》教学设计...
广州市初中信息技术第一册第二章第1节<操作系统简介>教学设计 一.学习者分析 学生通过第一章的学习,对计算机的软.硬件知识有了初步的了解,同时对操作系统的作用也有了简单的认识.但由于学生普 ...
- 《算法导论》学习总结 — 2.第一章 第二章 第三章
上一篇:http://www.cnblogs.com/tanky_woo/archive/2011/04/09/2010263.html 前三章基本没什么内容,所以合在一起总结. 第一章: 讲了算法( ...
- Practical Vim 第一章 第二章
第一章:Vim 解决问题的方式 前言 本质上讲,我们的工作是重复性的.凡是可以简化重复性操作的方式,都会成倍地节省我们的时间. Vim 对重复性操作进行了优化.它之所以能高效地重复,是因为它会记录我们 ...
最新文章
- Android应用中如何保护JAVA代码
- 重庆三峡学院计算机应用技术,重庆三峡学院 数学与计算机学院 刘福明老师简介 联系方式 手机电话 邮箱...
- 吴恩达《构建机器学习项目》精炼笔记(1)-- 机器学习策略(上)
- ipv4到ipv6的过渡
- 私有CA的创建和证书的申请
- 2D空间中求线段与圆的交点
- mysql修改视图字段长度_SQL Server 数据库创建视图时修改字段长度
- 唐宇迪 python 的免费课程 分享
- 归并排序时间复杂度为什么是NlgN
- numpy下载失败解决方法
- 三分钟破解奇迹热门外挂
- C语言递归函数 计算学生年龄
- ***能篡改WiFi密码,源于存在漏洞
- Django实现邮箱激活
- 正圆锥体空间方程_数值模拟偏微分方程的三种方法:FDM、FEM及FVM
- youtube批量采集-低成本解决方案-2
- AutoCAD Civil 3D里材质资源管理器手动重安装
- 用Xftp和Xshell本地链接华为云主机
- 谷歌浏览器csdn图片无法显示
- Lightgbm如何处理类别特征?