前言
博客仅记录个人学习进度和一些查缺补漏。
学习内容:BV17F411T7Ao
一、查找算法
基本查找/顺序查找
核心思路:遍历所有索引,依次对比直到查到找该元素或者遍历结束
时间复杂度: o(n)
空间复杂度: o(1)
代码如下:
package my.algorithm.search;
import java.util.ArrayList;
public final class BasicSearchDemo {
// 整型数组中查找数字,返回所有索引。
public static ArrayList<Integer> basicSearch(int[] arr, int num){
ArrayList<Integer> res = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
if(arr[i] == num) {
res.add(i);
}
}
return res;
}
}
二分查找/折半查找
特点:数据有序
核心思路:在有序数组中,每次排除一半的查找范围,在剩下的范围重复该算法逻辑。
时间复杂度:o(log(n))
空间复杂度:o(1)、o(log(n))
代码如下:
// 整型增序二分查找,输入为增序序列,返回第一个查到的索引。
public static ArrayList<Integer> binarySearch_Increasing(int[] arr, int num) {
ArrayList<Integer> res = new ArrayList<>();
int start = 0;
int end = arr.length - 1;
int mid = (start + end) / 2;
while (start < end) {
if(arr[mid] == num) {
res.add(mid);
break;
}else if (arr[mid] > num) {
end = mid - 1;
mid = (start + end) / 2;
}else if (arr[mid] < num) {
start = mid + 1;
mid = (start + end) / 2;
}
}
return res;
}
// 递归增查找
public static int binarySearch_Increasing_Recursion(int[] arr, int start, int end, int num) {
if(start > end) {
return -1;
}
int mid = (start + end) / 2;
if (arr[mid] == num) {
return mid;
}else if (arr[mid] > num) {
return binarySearch_Increasing_Recursion(arr, start, mid - 1, num);
}else if (arr[mid] < num) {
return binarySearch_Increasing_Recursion(arr, mid + 1, end, num);
}
return -1;
}
分块查找
特点:数据分块有序,块内无序,块间有序。
分块原则(以递增为例):
1、前一块中最大的数据,小于后一块中所有的数据
2、块数量一般等于数字的个数开根号
核心思路:先确定要查找的元素在哪一块,再挨个顺序查找
代码如下:
package my.algorithm.search;
public class BlockSearchDemo {
public static int blockSearch_Increasing(Block[] brr, int[] arr, int num) {
int block_num = findBlock_Increasing(brr, num);
if(block_num == -1) {
return -1;
}else {
return getIndex(brr, arr, num, block_num);
}
}
public static int getIndex(Block[] brr, int[] arr, int num, int block_num) {
//顺序查找
for (int i = arr[brr[block_num].getStartIndex()]; i < arr[brr[block_num].getEndIndex()]; i++) {
if(arr[i] == num) {
return i;
}
}
return -1;
}
//顺序查找,其实也可以二分查找,此处不赘述
public static int findBlock_Increasing(Block[] brr, int num) {
for (int i = 0; i <brr.length; i++) {
if(num <= brr[i].getMax()) {
return i;
}
}
return -1;
}
}
package my.algorithm.search;
public class Block{
private int max;
private int startIndex;
private int endIndex;
public Block(int max, int startIndex, int endIndex) {
this.max = max;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public int getStartIndex() {
return startIndex;
}
public void setStartIndex(int startIndex) {
this.startIndex = startIndex;
}
public int getEndIndex() {
return endIndex;
}
public void setEndIndex(int endIndex) {
this.endIndex = endIndex;
}
}
解决方式:
1.哈希查找,在某一块上进行堆积,类似数据结构中的十字链表
2.排序树查找
一边增加树高一边进行查找(找不到就插进对应的位置),其实就是二叉排序树。
3.堆查找
大根堆小根堆的使用。
二、排序算法
冒泡排序
将相邻的数据两两比较,判断顺序后将大的(或小的)放到后面
核心思想:根据大小交换最近的两个数据,重复至所有的数据都进行一次对比。
时间复杂度:o(n^2)
空间复杂度:o(1)
代码如下:
// 整型冒泡增序
public static void bubbleSort_Increasing (int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
int temp = arr[j];
if (arr[j] > arr[j + 1]) {
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
选择排序
核心思想:第i轮都确定第i个元素的索引正确。
时间复杂度:o(n^2)
空间复杂度:o(1)
代码如下:
// 选排增序
public static void selectSort_Increasing(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i; j < arr.length; j++) {
if (arr[i] > arr[j]){
swap(arr, i, j);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
插入排序
将38向前面有序的数组进行位置确定,从后往前去遍历,大于则交换,最终找到自己的位置。
时间复杂度:o(n^2)
空间按复杂的:o(1)
细节:升序的时候从大向小比较,降序的时候从小到大比较,否则就要遍历该轮所有数据。
代码如下:
package my.algorithm.sort;
public final class InsertSortDemo {
public static void insertSort_Increasing(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int j = i - 1;
while(j >= 0 && arr[j] > arr[j + 1]){
swap(arr, j, j + 1);
j--;
}
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
快速排序
经典的考研算法之一,看到大题是数组数组基本先手撸一遍快排稳定2分。
核心思想:选定一个数据为轴,先确定轴心位置,将数组分为左右两部分,再分别找轴心,直到所有数据有序。
时间复杂度:o(n*log(n))
其中一共进行logn轮,每轮遍历n个索引。
空间复杂度:o(log(n))
递归栈为logn层。
代码如下:
package my.algorithm.sort;
public final class QuickSortDemo {
public static void quickSort_Increasing(int[] arr, int start, int end){
if(start < end) {
// 确定这一轮的基轴
int pivotIndex = partition(arr, start, end);
// 找到左边的基轴
quickSort_Increasing(arr, start, pivotIndex - 1);
// 找到右边的基轴
quickSort_Increasing(arr, pivotIndex + 1, end);
}
}
public static int partition(int[] arr, int start, int end) {
// 先保存作为基轴的值
int pivot = arr[start];
while(start < end) {
// 确保右边的所有数字都比基轴要大
while (pivot < arr[end] && start < end) {
end --;
}
// 遇到第一个比基轴小的数,丢到左边去
arr[start] = arr[end];
// 确保左边所有的数字都比基轴要小
while (pivot > arr[start] && start < end) {
start ++;
}
// 遇到第一个比基轴大的数,丢到右边去
arr[end] = arr[start];
}
// 至此,所有左边的数字都比基轴小,所有右边的数字都比基轴大,则基轴位置一定在start = end 的此处。
arr[start] = pivot;
return start;
}
}
三、Arrays
在算法题中经常会遇到需要将数组拼接成字符串进行粘连或输出,或是需要快速排序、查找,临时写一个快排二分都不如直接调用Arrays工具类。节省大量时间可以思考更多的代码逻辑。
在二分查找的时候,是一边查找一边查看可以插入的位置,所以会有插入点的概念。
因为java中数组本身就是引用参数,类似指针,一旦更改数组中内容就很难还原,拷贝数组的需求日益见长。
底层原理:
利用插入排序+二分查找的方式进行排序。
默认把0索引的数据当成是有序序列,以后的数据都是无序序列。
遍历无序序列得到里面的每一个元素,假设当前遍历得到的元素是A元素。
把A元素王有序序列中插入,在插入的时候,利用二分查找确定A元素的插入点,确定插入点的比较方法就是compare函数体中所定义的方法。
其中两个整型o1 o2分别就是A和对应的元素进行相应的规则比较。
如果返回值是负数,就把A拿到左边去继续二分,如果返回值是正数,就把A拿到右边的数组去继续二分
直到最终确定A的位置。
compare详解:
o1,来自无序元素中的每一个元素,也就是A。
o2,来自有序元素的每一个元素,也就是有序序列。
返回值为正数:表示当前插入的位置对于正确位置来说靠左,继续往右边找。
返回值为负数:表示当前插入的位置对于正确位置来说靠右,继续往左边找。
例如:
return o1 - o2;
即A - 插入点,正数就说明大数往右插,升序。
return o2 - o1;
相反说明大数往左插,降序。
四、Lambda
在上一节,我们使用匿名内部类来填充compare类型的数据。
使用lamdba表达式简化如下:
函数式编程可以忽略面向对象的复杂语法,强调要做什么而不是谁去做
总结
排序查找的基本算法都是考研期间就猛猛练过了,特别是快排那真是信手拈来。
拉姆达表达式确实不是很熟悉,还需要慢慢学着去用,确实能够很节省代码时间和长度。
arrays的各种方法对算法题的提升很大,以前还要慢慢手写,现在就得学起来加快解题速度。