算法的笔记,直接上代码,思路和问题这些,都在代码注释上面
1、工具类
为了生成测试代码和比较器,专门写了一个数组工具类,代码如下:
/*** 数组工具类*/
public class ArrUtil {/*** 生成随机数组* 长度是[0,maxSize]* 每个值是[-maxValue,maxValue]** @param maxSize 数组最大长度* @param maxValue 数组最大值* @return 随机数组*/public static int[] generateRandomArr(int maxSize, int maxValue) {// Math.random() -> [0,1) 所有的小数,等概率返回一个// Math.random() * N -> [0,N) 所有小数,等概率返回一个// (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个// (int)(Math.random() * (maxSize + 1)) -> [0,maxSize] 所有的整数,等概率返回一个int size = (int) (Math.random() * (maxSize + 1));int[] arr = new int[size];for (int i = 0; i < arr.length; i++) {arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * maxValue);}return arr;}/*** 复制数组** @param arr 原数组* @return 复制数组*/public static int[] copyArr(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;}/*** 比较数组是否相等** @param arr1 数组1* @param arr2 数组2* @return 是否相等*/public static boolean isEqual(int[] arr1, int[] arr2) {if (arr1 == null && arr2 == null) {return true;}if (arr1 == null || arr2 == null) {return false;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}public static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}/*** 获取的系统排序的对数器,用来验证自己的排序是不是正确的参照** @param arr 数组*/public static void sortComparator(int[] arr) {Arrays.sort(arr);}/*** 交换数组中的两个元素** @param arr 数组* @param i 索引1* @param j 索引2*/public static void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}
2、选择排序
/*** 选择排序*/
public class SelectionSort {/*** 选择排序的思路是很接近人类思想的一种排序方式,就是在一堆数中,每次循环都挑出那个最小的,然后在剩下的里面继续这个操作* <p>* 过程:* arr[0~N-1]范围上,找到最小值所在的位置,然后把最小值交换到0位置。* arr[1~N-1]范围上,找到最小值所在的位置,然后把最小值交换到1位置。* arr[2~N-1]范围上,找到最小值所在的位置,然后把最小值交换到2位置。* …* arr[N-1~N-1]范围上,找到最小值位置,然后把最小值交换到N-1位置。* <p>* 算法复杂度: O(n^2)*/public static void selectionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = 0; i < arr.length - 1; i++) {int minIndex = i;for (int j = i + 1; j < arr.length; j++) {if (arr[j] < arr[minIndex]) {minIndex = j;}}if (minIndex != i) {// 交换ArrUtil.swap(arr, i, minIndex);}}}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 数组最大值int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {// 先生成一个随机的数组int[] arr = ArrUtil.generateRandomArr(maxSize, maxValue);// 复制一个数组,用来做对照int[] arr1 = ArrUtil.copyArr(arr);int[] arr2 = ArrUtil.copyArr(arr);// 调用测试方法,排序一个数组selectionSort(arr1);ArrUtil.sortComparator(arr2);if (!ArrUtil.isEqual(arr1, arr2)) {succeed = false;System.out.println("原数组:");ArrUtil.printArray(arr);System.out.println("自己排序后:");ArrUtil.printArray(arr1);System.out.println("系统排序后:");ArrUtil.printArray(arr2);break;}}System.out.println(succeed ? "successful!" : "error!");}}
3、冒泡排序
/*** 冒泡排序*/
public class BubbleSort {/*** 冒泡排序的本质思想是通过对比,每次将一个最大的放到数组的最后,在选择的过程中,是将相邻两个做对比,大的依次往后交换* <p>* 过程:* 在arr[0~N-1]范围上:* arr[0]和arr[1],谁大谁来到1位置;arr[1]和arr[2],谁大谁来到2位置 … arr[N-2]和arr[N-1],谁大谁来到N-1位置* 在arr[0~N-2]范围上,重复上面的过程,但最后一步是arr[N-3]和arr[N-2],谁大谁来到N-2位置* 在arr[0~N-3]范围上,重复上面的过程,但最后一步是arr[N-4]和arr[N-3],谁大谁来到N-3位置* …* 最后在arr[0~1]范围上,重复上面的过程,但最后一步是arr[0]和arr[1],谁大谁来到1位置* <p>* 算法复杂度: O(n^2)*/public static void bubbleSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = arr.length - 1; i > 0; i--) {for (int j = 0; j < i; j++) {if (arr[j] > arr[j + 1]) {ArrUtil.swap(arr, j, j + 1);}}}}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 数组最大值int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {// 先生成一个随机的数组int[] arr = ArrUtil.generateRandomArr(maxSize, maxValue);// 复制一个数组,用来做对照int[] arr1 = ArrUtil.copyArr(arr);int[] arr2 = ArrUtil.copyArr(arr);// 调用测试方法,排序一个数组bubbleSort(arr1);ArrUtil.sortComparator(arr2);if (!ArrUtil.isEqual(arr1, arr2)) {succeed = false;System.out.println("原数组:");ArrUtil.printArray(arr);System.out.println("自己排序后:");ArrUtil.printArray(arr1);System.out.println("系统排序后:");ArrUtil.printArray(arr2);break;}}System.out.println(succeed ? "successful!" : "error!");}
}
4、插入排序
/*** 插入排序*/
public class InsertSort {/*** 插入排序的思想和我们打扑克牌时给拿到的扑克牌排序一样,先将第一个数看作是有序的,* 然后将第二个数插入到有序的数组中,再将第三个数插入到有序的数组中,以此类推,* 直到将所有的数都插入到有序的数组中。* <p>* 过程:* 想让arr[0~0]上有序,这个范围只有一个数,当然是有序的。* 想让arr[0~1]上有序,所以从arr[1]开始往前看,如果arr[1]<arr[0],就交换。否则什么也不做。* …* 想让arr[0~i]上有序,所以从arr[i]开始往前看,arr[i]这个数不停向左移动,一直移动到左边的数字不再比自己大,停止移动。* 最后一步,想让arr[0~N-1]上有序, arr[N-1]这个数不停向左移动,一直移动到左边的数字不再比自己大,停止移动。* <p>* 算法复杂度: O(n^2)*/public static void insertSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = 1; i < arr.length; i++) {int index = i;while (index > 0 && arr[index] < arr[index - 1]) {// 数组不越界,并且当前位置数值比前一个的小,将当前位置的数往前交换移动ArrUtil.swap(arr, index, index - 1);index--;}}}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 数组最大值int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {// 先生成一个随机的数组int[] arr = ArrUtil.generateRandomArr(maxSize, maxValue);// 复制一个数组,用来做对照int[] arr1 = ArrUtil.copyArr(arr);int[] arr2 = ArrUtil.copyArr(arr);// 调用测试方法,排序一个数组insertSort(arr1);ArrUtil.sortComparator(arr2);if (!ArrUtil.isEqual(arr1, arr2)) {succeed = false;System.out.println("原数组:");ArrUtil.printArray(arr);System.out.println("自己排序后:");ArrUtil.printArray(arr1);System.out.println("系统排序后:");ArrUtil.printArray(arr2);break;}}System.out.println(succeed ? "successful!" : "error!");}
}
4、二分查找
- 二分查找:在有序数组中查找是否存在某个数
- 认识二分查找:每次将剩下的数组一分为二,看看需要的数据是否存在,不存在,只在一半中继续二分,另一半丢弃
- 二分查找的复杂度为:O(logN)
- 大部分二分查找的数组要求是有序的,但不是绝对,在特殊的场景和限制中,可以是无序的
4.1、在有序数组中查找是否存在某个数
public class BinarySearchExist {/*** 二分查找:在有序数组中查找是否存在某个数* 存在返回true,不存在返回false* <p>* 二分查找的过程:* 1. 先找到数组的中间位置,判断中间位置的数是否等于要查找的数* 2. 如果等于,直接返回中间位置的下标* 3. 如果中间位置的数大于要查找的数,说明要查找的数在中间位置的左边,所以在左边继续查找* 4. 如果中间位置的数小于要查找的数,说明要查找的数在中间位置的右边,所以在右边继续查找* 5. 重复以上过程,直到找到要查找的数,或者查找范围为空*/public static boolean binarySearchExist(int[] arr, int num) {if (arr == null || arr.length == 0) {return false;}// 左下标int L = 0;// 右下标int R = arr.length - 1;// 中间位置下标int mid = 0;while (L < R) {// 和mid = (L + R ) / 2 是一个意思,都是求中间位置,预防了L+R溢出的问题mid = L + ((R - L) >> 2);if (arr[mid] == num) {return true;}if (arr[mid] < num) {// 中间位置比查询的数字小,说明要查询的在右侧,改表L下标,去右侧查找L = mid + 1;}if (arr[mid] > num) {// 中间位置比查询的数字大,说明要查询的在左侧,修改R下标,去右侧查找R = mid - 1;}}// 到这个位置,说明L == R,看看L位置是不是和要查询的值一样,如果一样就返回L,不一样就是没有,返回-1return arr[L] == num;}/*** 暴力方法写的比较器,用来判断算法的结果是否正确*/public static boolean existComparator(int[] arr, int num) {if (arr == null || arr.length == 0) {return false;}for (int i = 0; i < arr.length; i++) {if (arr[i] == num) {return true;}}return false;}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 数组最大值int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {// 先生成一个随机的数组int[] arr = ArrUtil.generateRandomArr(maxSize, maxValue);// 先排好序Arrays.sort(arr);// 随机生成一个需要查询的数字int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());boolean result = binarySearchExist(arr, value);boolean comparatorResult = existComparator(arr, value);if (result != comparatorResult) {succeed = false;ArrUtil.printArray(arr);System.out.printf("查找的值:%d,比较器结果:%b,算法结果:%b\n", value, comparatorResult, result);break;}}System.out.println(succeed ? "successful!" : "error!");}
}
4.2、在一个有序数组中,找>=value的最左位置
import java.util.Arrays;// 在一个有序数组中,找>=value的最左位置
public class BinarySearchNearLeft {/*** 在一个有序数组中,找>=value的最左位置,如果存在,返回满足的最左侧下标,不存在返回-1* 思路:还是用二分法查找,查询到满足条件的,就把那个结果存起来,继续查找,如果还有则更新保存的结果。* 因为我们要查找的是大于等于value的最左侧的值,当满足条件的时候,还需要往左来看是不是有更满足条件的,所以在满足条件的时候,要往左查找* 步骤:* 1、先找到数组的中间位置,判断中间位置的数是不是大于等于要查找的值* 2、如果中间位置满足条件,保存当前的中间位置的下标,再往左边查找(修改R下标)* 3、如果不满足,则往右侧查找(修改L下标)* 4、重复上面的过程*/public static int binarySearchNearLeft(int[] arr, int value) {if (arr == null || arr.length == 0) {return -1;}int L = 0;int R = arr.length - 1;// 保存结果的变量int resultIndex = -1;while (L <= R) {// 和mid = (L + R ) / 2 是一个意思,都是求中间位置,预防了L+R溢出的问题int mid = L + ((R - L) >> 2);if (arr[mid] >= value) {// 满足条件,保存下标,继续往左尝试resultIndex = mid;R = mid - 1;} else {// 不满足条件,往右尝试L = mid + 1;}}return resultIndex;}/*** 暴力方法写的比较器,用来判断算法的结果是否正确*/public static int nearLeftComparator(int[] arr, int num) {if (arr == null || arr.length == 0) {return -1;}for (int i = 0; i < arr.length; i++) {if (arr[i] >= num) {return i;}}return -1;}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 数组最大值int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {// 先生成一个随机的数组int[] arr = ArrUtil.generateRandomArr(maxSize, maxValue);// 先排好序Arrays.sort(arr);// 随机生成一个需要查询的数字int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());int result = binarySearchNearLeft(arr, value);int comparatorResult = nearLeftComparator(arr, value);if (result != comparatorResult) {succeed = false;ArrUtil.printArray(arr);System.out.printf("查找的值:%d,比较器结果:%d,算法结果:%d\n", value, comparatorResult, result);break;}}System.out.println(succeed ? "successful!" : "error!");}
}
4.3、在一个有序数组中,找<=value的最右位置
import java.util.Arrays;// 在一个有序数组中,找<=value的最右位置
public class BinarySearchNearRight {/*** 在一个有序数组中,找<=value的最右位置,如果存在,返回满足的最左侧下标,不存在返回-1* 思路:还是用二分法查找,查询到满足条件的,就把那个结果存起来,继续查找,如果还有则更新保存的结果* 因为我们要查找小于等于value的最右侧的值,所以在满足条件的时候,要继续往右查找,看有没有更加满足条件的值* 步骤:* 1、先找到数组的中间位置,判断中间位置的数是不是小于等于要查找的值* 2、如果中间位置满足条件,保存当前的中间位置的下标,再往右查找(修改L的下标)* 3、如果不满足,则往右侧查找(修改R下标)* 4、重复上面的过程*/public static int binarySearchNearRight(int[] arr, int value) {if (arr == null || arr.length == 0) {return -1;}int L = 0;int R = arr.length - 1;int resultIndex = -1;while (L <= R) {int mid = L + ((R - L) >> 1);if (arr[mid] <= value) {// 满足条件,保存结果,继续往右(修改L的下标)resultIndex = mid;L = mid + 1;} else {// 不满足条件,说明当前位置比要查找的值大,要往左查找(修改R的值)R = mid - 1;}}return resultIndex;}/*** 暴力方法写的比较器,用来判断算法的结果是否正确*/public static int nearRightComparator(int[] arr, int value) {if (arr == null || arr.length == 0) {return -1;}for (int i = arr.length - 1; i >= 0; i--) {if (arr[i] <= value) {return i;}}return -1;}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 数组最大值int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {// 先生成一个随机的数组int[] arr = ArrUtil.generateRandomArr(maxSize, maxValue);// 先排好序Arrays.sort(arr);// 随机生成一个需要查询的数字int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());int result = binarySearchNearRight(arr, value);int comparatorResult = nearRightComparator(arr, value);if (result != comparatorResult) {succeed = false;ArrUtil.printArray(arr);System.out.printf("查找的值:%d,比较器结果:%d,算法结果:%d\n", value, comparatorResult, result);break;}}System.out.println(succeed ? "successful!" : "error!");}
}
4.4、局部最小值问题
- 局部最小值问题:在一个无序数组中,任意两个相邻的数不相等,[0]<[1]表示0位置局部最小,[n-1]<[n-2]表示n-1局部最小,返回中间局部最小的位置下标
- 在局部最小值问题中,因为强制规定了任意两个相邻的数不相等,[0]<[1]表示0位置局部最小,[n-1]<[n-2]表示n-1局部最小,表示一列数中,前面下降,后面上升,则中间一定存在一个位置,该位置的值比它前后都小,即为局部最小位置
代码和测试程序:
/*** 局部最小值问题:在一个无序数组中,任意两个相邻的数不相等,[0]<[1]表示0位置局部最小,[n-1]<[n-2]表示n-1局部最小,返回中间局部最小的位置下标* 在局部最小值问题中,因为强制规定了任意两个相邻的数不相等,[0]<[1]表示0位置局部最小,[n-1]<[n-2]表示n-1局部最小,* 表示一列数中,前面下降,后面上升,则中间一定存在一个位置,该位置的值比它前后都小,即为局部最小位置*/
public class BinarySearchLocalMinimum {/*** 二分查找局部最小值,如果存在,返回一个局部最小的左侧下标,不存在则返回-1* 思路:* 先判断整个数组的最小和最大的两个边界是不是满足局部最小的定义,如果不满足,则返回-1,满足则一定存在局部最小* 用二分查找的方法,每次先判断中间位置是不是大于右侧和左侧,如果大于左侧,则代表左侧上升趋势,在左侧存在局部最小,* 如果大于右侧,则代表右侧下降趋势,右侧存在局部最小* 如果两个都不满足,则代表这个位置小于左侧和右侧,该位置就是局部最小* 步骤:* 1、先判断整个数组的最小和最大的两个边界是不是满足局部最小的定义,如果不满足,则返回-1,满足则一定存在局部最小* 2、判断中间位置如果相对于前一个是上升趋势,则代表左侧必然存在局部最小,从左侧查找(修改R的值)* 3、如果不满足,判断当前位置相对于下一个的趋势,如果是下降趋势,则右侧必然存在局部最小,从右侧查找(修改L的值)* 4、如果不满足,则代表该位置小于左侧和右侧,就是局部最小位置* 5、重复上面的步骤,直到找到为止*/public static int binarySearchLocalMinimum(int[] arr) {if (arr == null || arr.length == 0) {return -1;}// 判断0位置是否是局部最小if (arr.length == 1 || arr[0] < arr[1]) {return 0;}// 判断n-1位置是不是局部最小if (arr[arr.length - 1] < arr[arr.length - 2]) {return arr.length - 1;}// 如果到这里,代表0位置是下降趋势,n-2位置为上升趋势,一定在1~n-2上存在局部最小的位置int L = 1;int R = arr.length - 2;while (L < R) {int mid = L + ((R - L) >> 1);if (arr[mid] > arr[mid - 1]) {// mid-1到mid是上升趋势,表示在左侧存在局部最小的位置R = mid - 1;} else if (arr[mid] > arr[mid + 1]) {// mid到mid+1是下降趋势,表示在右侧存在局部最小的位置L = mid + 1;} else {// mid位置比mid-1小,而且比mid+1也小,表示mid就是局部最小位置return mid;}}return L;}/*** 判断查找的index是否正确的方法,因为每次返回的结果不固定,所以只需要判断找到的位置是不是正确就行*/public static boolean isRight(int[] arr, int index) {if (index == -1) {return arr == null || arr.length == 0;}if (index == 0) {return arr.length == 1 || arr[0] < arr[1];}if (index == arr.length - 1) {return arr[arr.length - 1] < arr[arr.length - 2];}return arr[index] < arr[index - 1] && arr[index] < arr[index + 1];}/*** 生成相邻不相等的数组*/public static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) (Math.random() * maxSize) + 1];arr[0] = (int) (Math.random() * maxValue) - (int) (Math.random() * maxValue);for (int i = 1; i < arr.length; i++) {do {// 循环生成不想等的值arr[i] = (int) (Math.random() * maxValue) - (int) (Math.random() * maxValue);} while (arr[i] == arr[i - 1]);}return arr;}/*** 用对数器的方法测试*/public static void main(String[] args) {// 测试次数int testTime = 500000;// 数组最大长度int maxSize = 100;// 数组最大值int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {// 生成一个相邻位置不相等的数组int[] arr = generateRandomArray(maxSize, maxValue);// 得到局部最小位置int index = binarySearchLocalMinimum(arr);System.out.println(index);// 判断是否正确if (!isRight(arr, index)) {succeed = false;ArrUtil.printArray(arr);System.out.printf("返回的位置:%d\n", index);break;}}System.out.println(succeed ? "successful!" : "error!");}
}
后记
个人学习总结笔记,不能保证非常详细,轻喷