JavaDS预备知识
集合框架
Java 集合框架 Java Collection Framework ,又被称为容器 container ,是定义在 java.util 包下的一组接口 interfaces和其实现类 classes 。
其主要表现为将多个元素 element 置于一个单元中,对数据进行创建(Create)、读取(Retrieve)、更新(Update)和删除(Delete)的操作。即平时我们俗称的增删查改 CRUD 。
数据结构与算法的概念
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的
集合。
算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。
时间复杂度和空间复杂度
算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间,
由于计算机的飞速发展,计算机的存储容量得到大幅度提高,因此我们现在主要考虑时间复杂度。
时间复杂度
时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个数学函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。
因此我们使用计算机执行语句的次数来表示时间复杂度,但是在实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。
大O渐进法使用规则:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
一个算法面对不同的问题时,执行语句的次数也会由此不一样,例如我们在一个数组里查找一个数据,如果这个数据就是第一个,那执行次数就是1次,如果是最后一个,那执行次数就是整个数组的长度,也就是算法的执行情况,有三种,最好的情况,平均情况,最坏情况。时间复杂度取算法的最坏情况,因为最坏情况下都能接受,那这个算法就是一个好的算法。
例题:
//求func2的时间复杂度 void func2(int N){ int count = 0; for (int i = 0; i 0){ count++; } }
首先for循环语句一共执行2N次,while语句执行10次,一共执行2N+10 次,使用大O渐进法表示,保留最高阶,剩下2*N,去除2,剩下N,所以时间复杂度为O(N)
void func3(int N,int M){ int count = 0; for (int i = 0; i一共执行次数为N+M,即时间复杂度为O(N+M)
void func4(){ int count = 0; for (int i = 0; i语句执行次数为100,是常数化为1,则时间复杂度为O(1)
void bubbleSort(int[] array){ boolean flag = true; for (int i = 0; flag && i array[j+1]){ int tmp = array[j]; array[j] = array[j+1]; array[j+1] = tmp; flag = true; } } } }上面这个是冒泡排序算法,时间复杂度取最坏的情况,假设数组的长度为N,第一轮冒泡排序的执行次数为N-1,第二困轮冒泡排序的执行次数为N-2,以此类推,这个是一个等差数列,使用等差数列的求和公式为N(N-1)/2,使用大O渐进法表示O(N^2)
int binarySearch(int[] array, int value) { int begin = 0; int end = array.length - 1; while(begin value){ end = mid - 1; }else if(array[mid]二分查找,每次查找,待查询数据都会减少一半,直到数据只剩下一个,假设查找的次数为x,数据总数为N,
(1/2)^ x * N = 1 ; x = logN (以2为底数的对数)
long factorial(int N) { return N上面是一个求阶乘的递归算法,递归算法的执行次数等于 递归调用次数*每次递归执行的语句次数,所以这个算法的执行总次数 N-2(N>2时)或者 1 (N == 1 || N == 2),时间复杂度为O(N)
int fibonacci(int N) { return N我们画个图:假设求第八个斐波那契数
一般情况下,每求一个数都会进行两次递归才能求出,以此类推,时间复杂度为O(N^2)
空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
例题:
void bubbleSort(int[] array){ boolean flag = true; for (int i = 0; flag && i array[j+1]){ int tmp = array[j]; array[j] = array[j+1]; array[j+1] = tmp; flag = true; } } } }冒泡排序使用了一个数组的空间和几个变量的空间,这是常数,所以空间复杂度为O(1)
// 计算fibonacci的空间复杂度? int[] fibonacci(int n) { long[] fibArray = new long[n + 1]; fibArray[0] = 0; fibArray[1] = 1; for (int i = 2; i fibArray[i] = fibArray[i - 1] + fibArray [i - 2]; } return fibArray; } return N3.泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换
擦除机制
泛型的引入是JDK5开始引入的,在JDK5之前是没有泛型的,所以裸类型是为了兼容JDK5之前的版本,从JDK5版本后泛型使用的是擦除机制。
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
由于Java会进行静态类型检查,所以如果你使用Integer的泛型,就不能使用String,double等数据进行赋值。
泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
class 泛型类名称 { //... }举个例子:
public class MyArray { //... }这时候我们传入的类型形参就必须是Number或者是Number的子类
public static void main(String[] args) { Array arr1 = new Array(); Array arr2 = new Array(); Array arr3 = new Array();//编译错误 err }Integer和Double 都是Number 的子类,String 不是Number 的子类,所以使用String 会发生编译报错。
public class MyArray { ... }这时候E就必须实现Comparable接口
泛型方法
方法限定符 返回值类型 方法名称(形参列表) { ... }举个例子:
public E find(E[] arr){ //... } public static void find(){ //... }方法的泛型类型形参写在 public 等访问权限修饰符 和 static 后面,并且类型形参要保持一致,就是如果使用E就必须统一使用E,不能混用别的类型形参(T等等)
使用:
调用泛型方法时,我们可以在方法前面加上类型参数或者不使用类型参数,和上面一样也有类型推导的机制。
public static void main(String[] args) { Integer[] a = {1,2,3,4,5,6}; MyArray.find(a); MyArray.find(a); }