初识数据结构
补充
坚持一定很酷!!
作业:
4.父子类关系catch异常时-- 子类在前父类在后,否则都会执行父类异常,根本不会获得子类异常
一次只能捕获一个异常,一次只能进入一个异常!!!就算两个异常都存在,看顺序以及当时所出现的具体异常!!!
一次catch一个异常!!
6. RuntimeException不是必须被try{} catch 语句捕获,可以不捕获,只不过是交给JVM处理而已。
- 进入数据结构,学会画图和调试,逻辑很严谨。画图!!!
你说你会努力的 对吗
一、初识集合框架
数据结构:描述/组织数据的方式
java里面会把这些数据结构封装成一个特定的类 》集合类 》java里面有很多集合类
集合框架【有多种集合类 》背后是一种数据结构】
1. 什么是集合框架
- Java 集合框架 Java Collection Framework ,又被称为容器 container ,是定义在 java.util 包下的一组接口 interfaces 和其实现类 classes 。
其主要表现为将多个元素 element 置于一个单元中,用于对这些元素进行快速、便捷的存储 store 、检索 retrieve 、管理 manipulate ,即平时我们俗称的增删查改 CRUD。 - 类和接口总览
2. 集合框架的重要性
- 笔试及面试题
- 腾讯-Java后台开发面经
- HashMap 了解不,介绍一下,如果一个对象为 key 时,hashCode 和 equals 方法的用法要注意什么?
- HashSet 和 HashMap 的区别是什么?
- HashMap 是线程安全的么?那需要线程安全需要用到什么?
- 阿里巴巴-Java后台开发面经
- ArrayList 和 LinkedList 的区别是什么?
- 有了解过 HashMap 的具体实现么?
3. HashMap 和 ConcurrentHashMap 哪个效率更高?
- 今日头条-Java后台开发面经
- 编程题:判断一个链表是否是一个回文链表。
- Redis 的 zset 类型对应到 java 语言中大致是什么类型?
- hashCode 主要是用来做什么用的?
3. 背后所涉及的数据结构以及算法
- 什么是数据结构?
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。 - 容器背后对应的数据结构(对应看上面“类和接口”图)
现阶段主要学习以下容器,每个容器其实都是对某种特定数据结构的封装:
- Collection:是一个接口,包含了大部分容器常用的一些方法
- List:是一个接口,规范了ArrayList 和 LinkedList中要实现的方法
ArrayList:实现了List接口,底层为动态类型顺序表
LinkedList:实现了List接口,底层为双向链表- Stack:底层是栈,栈是一种特殊的顺序表
- Queue:底层是队列,队列是一种特殊的顺序表
- Deque:是一个接口
- Set:集合,是一个接口,里面放置的是K模型HashSet:底层为哈希桶,查询的时间复杂度为O(1)
TreeSet:底层为红黑树,查询的时间复杂度为O(log2N ),关于key有序的- Map:映射,里面存储的是K-V模型的键值对
HashMap:底层为哈希桶,查询时间复杂度为O(1)
TreeMap:底层为红黑树,查询的时间复杂度为O( log2N),关于key有序
- 什么是算法
算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。 - 如何学好数据结构以及算法
死磕代码、画图思考、博客总结、刷题!
二、时间和空间复杂度
1.如何衡量一个算法的复杂度
时间和空间上
但是就现代而言,空间上问题较小(如128G内存也只有U盘上大小),所以更加注重时间上的效率
2. 算法效率
算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间效率被称为时间复杂度,而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间,在计算机发展的早期,计算机的存储容量很小,所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。
3. 时间复杂度
1. 时间复杂度的定义
- 在计算机科学中,算法的时间复杂度是一个数学函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。
- 一定要注意:时间复杂度与执行次数有关!!
2. 大O的渐进表示法:
- 实际就是将各个执行次数相加,最后按照规则取最大次幂(去掉系数)
- 并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们
使用大O的渐进表示法 - 大O符号(Big O notation):是用于描述函数渐进行为的数学符号
3. 推导大O阶方法
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶。
(小结:计算时间复杂度要去掉系数!!! 常数是则复杂度记为是O(1) 只保留最高阶) - 大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
- 另外有些算法的时间复杂度存在最好、平均和最坏情况:
最坏情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最好情况:任意输入规模的最小运行次数(下界)
(注意:以后所说的时间复杂度是最坏情况!!)
4. 常见时间复杂度计算举例
- 注意点::log2N 在算法分析中表示是底数为2,对数为N,有些地方会写成lgN。
- 【实例1】
// 计算func3的时间复杂度?
void func3(int N, int M) {
int count = 0;
for (int k = 0; k < M; k++) {
count++;
}
for (int k = 0; k < N ; k++) {
count++;
}
System.out.println(count);
}
【分析】M与N未表明是常数,则时间复杂度与执行次数有关,所以两个for循环分别执行M与N次,ze最后时间复杂度为O(M+N)
- 【实例2】
// 计算func4的时间复杂度?
void func4(int N) {
int count = 0;
for (int k = 0; k < 100; k++) {
count++;
}
System.out.println(count);
}
【分析】执行次数为100(常数),常数的时间复杂度为O(1)
- *【实例3】冒泡排序
// 计算bubbleSort的时间复杂度?
void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {
boolean sorted = true;
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}if (sorted == true) {
break;
}
}
}
【分析】循环N遍,每一次循环经历的次数如下:
N-1
N-2
…
1
每一遍所需要经历的次数都不同!
构成了等差数列!!
计算:(N-1+1)*N/2 =N^2/2,去掉系数,则时间复杂度F(N)=O(N ^2)
【补充】最好情况下:i走完了但是并没有进入满足条件,所以O(N-1),即:O(N)
- 【实例4】二分查找
// 计算binarySearch的时间复杂度?
int binarySearch(int[] array, int value) {
int left = 0;
int right = array.length - 1;
while (begin <= end) {
int mid = left + ((right-left) / 2);
if (array[mid] < value)
left = mid + 1;
else if (array[mid] > value)
right = mid - 1;
else
return mid;
}
return -1;
}
【分析】最坏情况就是找最大值。
二分查找:O(log2N)
注意:如果是直接按照执行次数,该题任意误导成O(N)
所以:注意:计算复杂度必须结合代码思想,不能只看代码
数据个数 查找次数
2 2
4 3
8 4
… …
N 2^(x-1)
即: 2^(x-1)=N -> x-1 = log2N -> x = log2N+1
所以:F(N)= log2N+1 -> 即:log2N
或者(作图)思想:每次查找进行折半,最后剩余1,即:
N/(2^x)=1 > x=log2N
- 【实例5】递归
// 计算阶乘递归factorial的时间复杂度?
long factorial(int N) {
return N < 2 ? N : factorial(N-1) * N;
}
【分析】递归时间复杂度=递归次数*每次递归执行次数(其实每次递归执行次数就是1)
递归次数根据递归作图结果知:N则递归(N-1)次
所以时间复杂度:F(N)= O((N-1)*1) -> O(N)
- 【实例6】斐波那契数列
时间复杂度:O(2^N)
// 计算斐波那契递归fibonacci的时间复杂度?
int fibonacci(int N) {
return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
}
【分析】斐波那契数列其实也是递归思想,所以同样使用递归思想进行问题解决。
- 递归时间复杂度=递归次数*每次递归执行次数(其实而言就是1)
- 斐波那契数列暂时可以认为:0 1 1 2 3 5…
作图:
层数 递归次数
1 2^0
2 2^1
… …
N 2^(N-1)
所以:构成等比数列求:2^N-1
所以时间复杂度:O(2^N)
三、空间复杂度
-
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
-
计算空间复杂度规则类似于时间复杂度!(去掉最高项系数、常数则1、保留最高阶)
-
实例
- 【实例1】冒泡排序
// 计算bubbleSort的空间复杂度?
void bubbleSort(int[] array) {
for (int end = array.length; end > 0; end--) {
boolean sorted = true;
for (int i = 1; i < end; i++) {
if (array[i - 1] > array[i]) {
Swap(array, i - 1, i);
sorted = false;
}
}if (sorted == true) {
break;
}
}
}
【分析】空间复杂度是看变量的个数。
其中进行传参的数组是必要的,不算在该题有效变量中;
变量:end/ sorted/ i 三个变量
所以:空间复杂度:O(1)
- 【实例2】斐波那契
// 计算fibonacci的空间复杂度?
int[] fibonacci(int n) {
long[] fibArray = new long[n + 1];
fibArray[0] = 0;
fibArray[1] = 1;
for (int i = 2; i <= n ; i++) {
fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
}
return fibArray;
}
【分析】数组有(N+1)个元素,则空间复杂度O(N+1) -> 即:O(N)
- 【实例3】递归
// 计算阶乘递归Factorial的空间复杂度?
long factorial(int N) {
return N < 2 ? N : factorial(N-1)*N;
}
【分析】递归的空间复杂度就是递归的次数:
则:空间复杂度O(N) :与时间复杂度一样
- 注:如果每次递归都创建数组,则空间复杂度就要乘以数组长度
注意
- 常见复杂度:(数值从小到大,速度从慢到快)
O(1) O(logN) O(N) O(N*logN) O(N^2) O(2^N)
lgN 代表以2为底的对数! - 注意:数组空间复杂度–如果数组【】中大小已知则O(1),未知具体常量数字大小则O(N)
*实例分析–计算时间以及空间复杂度
- 时间复杂度看执行次数,空间复杂度看变量个数!
- 【实例1】
public int firstUniqChar(String s) {
int[] count = new int[256];
// 统计每个字符出现的次数
for(int i = 0; i < s.length(); ++i){
count[s.charAt(i)]++;
}
// 找第一个只出现一次的字符
for(int i = 0; i < s.length(); ++i){
if(1 == count[s.charAt(i)]){
return i;
}
}
return -1;
}
【分析】时间复杂度:O(2N) -> O(N)
空间复杂度:只有数组变量,且已知具体常量大小,所以O(1)
- 【实例2】
import java.util.Scanner;
public class Main{
public static void main(String[] args){
// 循环输入
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
// 获取一行单词
String s = sc.nextLine();
// 1. 找到最后一个空格
// 2. 获取最后一个单词:从最后一个空格+1位置开始,一直截取到末尾
// 3. 打印最后一个单词长度
int len = s.substring(s.lastIndexOf(' ')+1,s.length()).length();
System.out.println(len);
}
sc.close();
}
}
【分析】时间复杂度: O(N)
空间复杂度:常数个变量,所以O(1)
THINK
注意时间以及空间复杂度的算法!!