常见算法
查找
- 基本查找
- 二分查找/折半查找
- 分块查找
- 插值查找
- 斐波那契查找
- 树表查找
- 哈希查找
基本查找
核心:从0索引开始逐个往后查找
1 | public static void main(String[] args) { |
二分查找
前提条件:数组中的数据必须是有序的
核心逻辑:每次排除一般的查找范围
优势:提高查找效率
假设有一个数组为:[7, 23, 79, 81, 103, 127, 131, 147]
那么,可以得到两个值,min=0,max=7,这两个值表示最大索引值和最小索引值,
接着 (max + min) / 2,得出来的值就是mid值
- 如果查找的元素在mid的左边,缩小范围时,min不变,max等于mid减1
- 如果查找的元素在mid的右边,缩小范围时,max不变,min等于mid加1
- 如果 min 大于 max 时,说明数据不存在
如果数据是乱的,先排序再用二分查找得到的索引没有实际意义,只能确定当前数字在数组中是否存在,因为排序之后数字的位置就可能发生变化了(排序浪费的性能比查找高得多)
插值查找(二分查找改进)
假设,数组的数据都是均匀分布的,如[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],那么可以套用以下公式
1 | mid = min + ((key-arr[min]) / (arr[max]-arr[min])) * (max-min) |
通过以上公式,可以获得目标数据在索引中的大概位置
缺点也很明显:需要数据均匀分布,若是不均匀的则会导致资源浪费或查找不到
斐波那契查找(二分查找改进)
斐波那契查找就是在二分查找的基础上,根据斐波那契数列进行分割的
斐波那契数列:
1 | [F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10] |
1 | [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144] |
首先,将要查找的数组长度看为 F(k)
,mid的左侧看作 F(k-1)-1
,mid的右侧看作 F(k-2)
可以得出:F(k)=F(k-1)-1+F(k-2)
1 | mid = arr[min] + F(k-1) - 1 |
解释:
在斐波那契数列找一个等于略大于查找表中元素个数的数f(n)
,将原查找表扩展为长度F(n)
(如果要补充元素,则补充重复最后一个元素,直到满足F(n)
),完成后进行斐波那契分割,即F(n)
个元素分割为前半部分F(n-1)
个元素,后半部分F(n-2)
个元素,找出要查找的元素在哪一部分并递归,直到找到
相等,则mid位置的元素即为所求
>,则low=mid+1,k-=2
解释:low=mid+1说明待查找的元素在
[mid+1,high]
范围内,k-=2说明范围[mid+1,high]
内的元素个数为n-(F(k-1)) = Fk-1-F(k-1) = Fk-F(k-1)-1=F(k-2)-1
<,则high=mid-1,k-=1
分块查找
假设有这样一个无序的数组:
[7, 10, 13, 19, 16, 20, 27, 22, 30, 40, 36, 43, 50, 48]
把这个数组分成几个不同的块,变成:
[7,10][13,19,16,20][27,22,30,40,36][43,50,48]
分块原则1:前一块中的最大数据,小于后一块中每一个数据(块内无序,块间有序)
分块原则2:块数数量一般等于数字的个数开根号。比如:16个数字一般分为4块左右
排序
冒泡排序
冒泡排序:
- 相邻的数据两两比较,小的放前面,大的放后面
- 第一轮循环结束,最大值已经找到,在数组的最右边
- 第二轮循环,只需要找剩余数组的最大值
- 以此类推,如果数组中有n个数据,总共只要执行n-1轮的代码就可以
1 | int[] arr = {2,4,5,3,1}; |
选择排序
选择排序:从0索引开始,拿着每一个索引上的元素跟后面的元素依次比较
,小的放前面,大的放后面,以此类推
1 | int[] arr = {2,4,5,3,1}; |
插入排序
插入排序:将0索引的元素到N索引的元素看作是有序的,把N+1索引的元素到最后一个当成是无序的。遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面
类似与打牌时整理牌的过程
1 | int[] arr = {3,44,38,5,47,15,36,26,27,2,46,4,19,50,48}; |
目前的排序的方法,速度对比:选择排序 > 插入排序 > 冒泡排序
如果数组本身不是很乱的,如 [2,5,12,43,23,11,65] 这种,插入排序是最快的
递归算法
递归指的是方法中调用方法本身的现象
递归的注意点:递归一定要有出口,否则就会出现内存溢出
递归算法的作用
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算
书写递归的两个核心
- 找出口:什么时候不再调用方法
- 找规则:如何把大问题变成规模较小的问题
快速排序
第一轮:把0索引的数字作为基准数,确定基准数在数组中正确的位置。比基准数小的全部在左边,比基准数大的全部在右边
1 | public static void quickSort(int[] arr,int i,int j) { |
总结
冒泡排序:
相邻的元素两两比较,小的放前面,大的放后面
选择排序:
从0索引开始,拿着每一个索引上的元素跟后面的元素依次比较
小的放前面,大的放后面,以此类推
插入排序:
将数组分为有序和无序两组,遍历无序数据,将元素插入有序序列中即可
快速排序:
- 将排序范围中的第一个数字作为基准数,再定义两个变量start,end
- start从前往后找比基准数大的,end从后往前找比基准数小的(end先开始找,然后start再找)
- 找到之后交换start和end指向的元素,并循环这一过程,直到start和end处于同一个位置,该位置是基准数在数组中应存入的位置,再让基准数归位
Arrays
操作数组的工具类
方法名 | 说明 |
---|---|
public static String toString(数组) | 把数组拼接成一个字符串 |
public static int binarySearch(数组, 查找元素) | 二分查找法找元素 |
public static int[] copyOf(原数组, 新数组长度) | 拷贝数组 |
public static int[] copyOfRange(原数组, 起始索引, 结束索引) | 拷贝数组(指定范围) |
public static void fill(数组, 元素) | 填充数组 |
public static void sort(数组) | 按照默认方式进行数组排序 |
public static void sort(数组, 排序规则) | 按照指定的规则排序 |
1 | // toString 把数组变为字符串 |
1 | Integer[] arr={2,3,1,5,6,7,8,4,9}; |
Lambda表达式
对 Arrary.sort()
进行简化:
1 | Integer[] arr={2,3,1,5,6,7,8,4,9}; |
函数式编程
函数式编程(Functional programming)是一种思想特点
面向对象:先找对象,让对象做事情
函数式编程思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做
而Lambda表达式就是函数式思想的体现
Lambda表达式的标准格式
Lambda表达式是JDK8开始后的一种新语法形式
1 | () -> {} |
()
对应着方法的形参->
固定格式{}
对应着方法的方法体
注意点:
Lambda表达式可以用来简化匿名内部类的书写
Lambda表达式只能简化函数式接口的匿名内部类的写法
函数式接口:
有且仅有一个抽象方法的接口叫做函数式接口,接口上方可以加@FunctionalInterface注解
Lambda表达式的省略写法
省略核心:可推导,可省略
- 参数类型可以省略不写
- 如果只有一个参数,参数类型可以省略,同时
()
也可以省略 - 如果lambda表达式的方法体只有一行,大括号,分号,return可以省略不写,需要同时省略
1 | Arrarys.sort(arr,(o1,o2) -> o1 - o2) |