- 时间复杂度
- 对数器
- 分析递归思想
- 递归时间复杂度
- 堆相关知识点
- 排序算法的穩定性概念、意义
- 比较器 意义和概念
- 布隆过滤器
1.时间复杂度
常数时间操作:
如果一个操作跟数据量大小没有关系,每次都是固定时间完成的操作,称之为常数时间操作。
如:数组寻址、判断值、比较大小、数组赋值…
时间复杂度为:
在一个算法中,在最坏情况下,用 常数操作数量 来作为评判指标。在常书操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分表达式记为 f(N),则时间复杂度为O(f(N))
例子;
一个有序数组A长度为N,一个无序数组B长度为M,打印B中的所有不在A中的数
算法1:对于B中的每一个数都在A中通过遍历方式找一下
算法2:读于B中的每一个数,都通过二分的方式找一下
算法3:先把数组B排序,然后用外排的方式打印所有A中没出现的数
算法1:
B中的每一个数都要在A中比对一次,为O(M*N)
算法2:
B中的每一个数,先找到A中中间数大小进行比较,则每次可以排除一半的数,为O(M*logN) 【A数组中,最多比对logN次,2^x = N -> logN = x】
算法3:
先对B排序,为O(M*logM);外排为O(M+N)
总时间复杂度为:O(M*logM) + O(M+N)
这时候需要根据数据量大小M,N来判断
2.对数器
作用:
用来测试自己的算法是否正确;若不正确,可以找到代码错在哪里
使用:
1.有一个需要测试的方法
2.实现一个绝对正确,但是时间复杂度不好的方法
3.实现一个随机样本产生器
4.实现比对方法
5.把方法A和方法B比对很多次来验证方法A是否正确
6.如果有一个样本使得比对出错,打印样本分析是哪个方法出错
7.当样本数量很多时比对依然正确,则可以确定方法A正确
3.分析递归思想
递归即为:自己调用自己
底层逻辑:压栈
一个函数在调用子过程之前,会将当前所有信息放到栈里,信息完全保存;子过程返回之后,会利用这些信息还原现场
**由于
1.递归时,系统压栈是将所有信息(有用的没用的)全部压栈,当样本量大时,消耗内存很多,且有栈满的风险
2.所有递归函数都可以转换为迭代函数
所以,最好将递归函数改为迭代函数**
例子:
//测试
int arr[] = {9,8,3,0,2,1,5,7};
int max = getMax(arr,0,arr.length-1);
Log.d("jiang", String.valueOf(max));
//递归得到最大值
public static int getMax(int[] arr,int L,int R){
if (L == R){
return arr[L];
}else{
int mid = L + (R-L)/2;
int maxLeft = getMax(arr,L,mid);
int maxRight = getMax(arr,mid+1,R);
return Math.max(maxLeft,maxRight);
}
}
时间复杂度:
O(N^d * logN)
4.递归时间复杂度:
master公式:T(N) = aT(N/b) + O(N^d)
N:样本量
a:子过程发生的次数
b:样本被分为几次研究
d:剩下的常数操作数量大小
5. 堆相关知识点
(1)堆为完全二叉树(满二叉树/从左向右依次补齐的二叉树)
(2)堆可以用数组的思想实现
堆的一个结点的左孩子为2i+1
堆的一个结点的右孩子为2i+2
堆的一个结点的父结点为(i-1)/2
其中对于根结点 他的父结点为(0-1)/2 = 0
(3)堆可以分为大根堆和小根堆
大根堆:对于任意一个子树,他的根结点都是最大值
小根堆:对于任意一个子树,他的根结点都是最小值
6. 排序算法的穩定性 以及 意义
概念:
在一系列数据中,有多个重复值,排序后,原本在前面的元素还是在前面,并没有因为排序的原因,使得原本在前面的元素排到了后边。
意义:
在业务上,比如excel表格既想按照各个学生按班级排序,又想按照学生成绩排序。那么,先点击班级排序,所有学生会按照班级排好序,但是学生成绩还是乱序。再点击成绩排序,期望的成果是,学生按照班级排序的同时,又能按照成绩排序。这时候就需要算法具有稳定性,可以将上一次排序的信息保留下来。
汇总:
- 冒泡排序:可以稳定
- 选择排序:可以稳定
- 插入排序:可以稳定
- 归并排序:可以稳定
- 改进经典排序:做不到稳定
- 随机排序:做不到稳定
- 堆排序:做不到稳定
7.比较器
(1)意义:
如果在面试中,排序不是重点,排完序之后的操作才是重点,那么可以调用系统的函数快速进行排序,就可以不用浪费大量的时间在排序上
(2)概念:
调用系统的Arrays.sort()函数
如果是基础类型 -> 会根据值得大小排序
如果是自己定义的数组类型 -> 会根对象的内存地址排序(没有意义)
所以想要排序自己定义的数组类型,要给Arrays.sort()函数传两个参数,一个是数组,一个是排序的方法
对象
public static class Student{
public int id;
public String name;
public int age;
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
排序方法
public static class IdAscendingComparator implements Comparator<Student> {
//告诉怎么比大小
//如果返回负数 -> 第一个参数应该排在前面
//如果返回整数 -> 第一个参数应该排在后面
//入股返回0 -> 认为一样大
@Override
public int compare(Student student1, Student student2) {
//按照id从小到大排序
//小的数应该排在前面
// if (student1.id < student2.id){
// return -1;
// }else if (student2.id < student1.id){
// return 1;
// }else {
// return 0;
// }
//优化
return student1.id - student2.id;
}
}
运行
public static void main(String[] args) {
Student student3 = new Student(3,"C",3);
Student student2 = new Student(2,"B",20);
Student student1 = new Student(1,"A",18);
Student[] students = new Student[]{student3,student2,student1};
for (Student student : students){
System.out.println(student.toString());
}
Arrays.sort(students,new IdAscendingComparator());
for (Student student : students){
System.out.println(student.toString());
}
}
优先级队列就是 堆
!!!系统提供的非常方便的有序结构,都需要提供一个比较器让系统排序
// PriorityQueue<Student> heap = new PriorityQueue<>(); 不给比较器就会按照内存地址排序 没有意义
PriorityQueue<Student> heap = new PriorityQueue<>(new IdAscendingComparator());
heap.add(student3);
heap.add(student2);
heap.add(student1);
while (!heap.isEmpty()){
Student student = heap.poll();
System.out.println(student.toString());
}
8.布隆过滤器
解决的问题:有100亿个URL黑名单,每个URL是64字节的,用户搜索时需要在黑名单
中进行排查
可以用hash表实现,但是空间复杂度很高
布隆过滤器是一种集合(bit类型的map,每个位置是0或者1)
布隆过滤器有很小的失误率
如果一个URL在黑名单中,一定不会出错
如果一个URL不在黑名单中,有可能被认为在黑名单中
int[] arr = new int[1000]; //实际可以表示 1000 * 4字节 * 8比特 = 32000bit
int index = 30000; //假设通过hashcode算出来的值
int intIndex = index / 32; //定位来自哪个整数(桶)
int bitIndex = index % 32; //定位这个整数(桶)哪个比特位
// 来自哪个桶 1左移到bitIndex位置
arr[intIndex] = (arr[intIndex] | (1 << bitIndex)); //