算法网课视频
左程云算法视频:https://player.bilibili.com/player.html?bvid=BV13g41157hK
视频p2,讲了几个排序算法和异或,题目比较经典。
石墨和语雀都可以上传 java文件,语雀可以在线预览,导出的 PDF 中代码段效果很好;石墨无法预览,导出的PDF中代码段没有高亮,效果还差。
而且语雀导出PDF中,还可以下载附件中的 java代码,石墨就不能下载,语雀YYDS!
排序算法
选择排序
源码:SelectionSort.java
完整版代码在附件中,包含主函数和其他测试方法。篇幅很长会导致页面显示效果不好,所以这里只截取关键步骤代码。
public static void selectionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}// 0 ~ N-1 找到最小值,在哪,放到0 位置上// 1 ~ n-1 找到最小值,在哪,放到1 位置上// 2 ~ n-1 找到最小值,在哪,放到2 位置上/*** 选择排序* 每次内循环,找到当前元素中最小的值,调整到最前面,排序结果为升序* 可以设置一个 k 值,实现 k 次循环,找到当前元素中 k个最小的元素*/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; // 内循环,每次找到最小值的索引,赋值给minIndex}}swap(arr, i, minIndex); // 一次循环结束,把最小值交换到最前面}}
冒泡排序
源码:BubbleSort.java
外层从后往前,内层循环,每次比较相邻元素,把较大值放后面,每次循环就能找到当前元素里最大的那个值了。
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]) { // 每次比较相邻元素swap(arr, j, j + 1); // 大的元素往后挪}}}}
插入排序
public static void insertionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}/*** 插入排序* 和打扑克牌的动作很像*/for (int i = 1; i < arr.length; i++) { // 0~i 上做到有序for (int j = i - 1; j >= 0; j--) { // j 从i相邻的位置开始往前遍历if (arr[j] > arr[j + 1]) {swap(arr, j, j + 1); // 往前遍历,把当前所有元素中较小的值往前挪}}}}
归并排序
public static void mergeSort(int[] arr) {if (arr == null || arr.length < 2) {return;}process(arr, 0, arr.length - 1);}public static void process(int[] arr, int left, int right) {if (left == right) {return;}int mid = left + ((right - left) >> 1); // 不会溢出,且快速process(arr, left, mid); // 递归调用process(arr, mid + 1, right);merge(arr, left, mid, right);}// 递归版本public static void merge(int[] arr, int left, int mid, int right) {int[] help = new int[right - left + 1]; // 帮助排序的数组int i = 0; // 主数组的游标int p1 = left; // 左边的游标int p2 = mid + 1; // 右边的游标while (p1 <= mid && p2 <= right) {// 可以简化成下面这一句代码// help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];if (arr[p1] <= arr[p2]) {help[i++] = arr[p1++];} else {help[i++] = arr[p2++];}}// 要么p1越界了,要么p2越界了while (p1 <= mid) {help[i++] = arr[p1++]; // 把未比较的元素复制到help数组}while (p2 <= right) {help[i++] = arr[p2++];}for (i = 0; i < help.length; i++) {arr[left + i] = help[i]; // 把排好序的元素复制到原数组对应位置上}}
非递归版本:不容易理解,多看几遍。
public static void mergeSort2(int[] arr) {if (arr == null || arr.length < 2) {return;}int N = arr.length;int mergeSize = 1; // 步长while (mergeSize < N) {int left = 0; // 当前左组的第一个位置while (left < N) {if (mergeSize >= N - left) {break;}int mid = left + mergeSize - 1;int right = mid + Math.min(mergeSize, N - mid - 1);merge(arr, left, mid, right);left = right + 1; // 跳到下一个merge的左组位置}// 防止溢出if (mergeSize > N / 2) {break;}mergeSize <<= 1; // 步长扩大一倍}}
快速排序
源码:QuickSort.java
算法第四版,三切分快速排序。
/*** 快速排序,算法第四版* 三切分,把数组分组三部分,小于,等于,大于*/public static void quickSort(int[] arr) {if (arr == null || arr.length < 2) {return;}process(arr, 0, arr.length - 1);}public static void process(int[] arr, int left, int right) {if (left >= right) {return;}int[] equalArea = partion(arr, left, right); // 等于部分的边界数组process(arr, left, equalArea[0] - 1); // 递归处理左侧process(arr, equalArea[1] + 1, right); // 递归处理右侧}// 数组切分方法public static int[] partion(int[] arr, int left, int right) {if (left >= right) {return new int[]{-1, -1};}int lt = left; // 小于切分元素的边界int i = left + 1; // 游标,用来遍历数组int gt = right; // 大于切分元素的边界int pivot = arr[left]; // 这里是选择第一个元素为切分元素while (i <= gt) {if (arr[i] < pivot) { // 5, 1, 9, 2, 8swap(arr, lt++, i++);} else if (arr[i] > pivot) {swap(arr, i, gt--); // 大于区域边界往左侧扩展,当前i位置的数是交换过来的,大小未知,所以i不变} else {i++; // 等于的话,i增加1,比较下一个}}return new int[]{lt, gt}; // lt~gt上的都等于切分元素}
堆排序
源码:HeapSort.java
堆的结构很巧妙,值得多研究。
public static void heapSort(int[] arr) {if (arr == null || arr.length < 2) {return;}// 方法1:从无到有,建立大根堆// 复杂度: O(N * logN)// for (int i = 0; i < arr.length; i++) {// heapInsert(arr, i);// }// 方法2:把数组看成建好的堆,再调整成大根堆// 复杂度: O(N)for (int i = arr.length - 1; i >= 0; i--) {heapify(arr, i, arr.length);}int heapSize = arr.length; // 用来控制堆的逻辑范围// 最大值交换到末尾,缩小堆的逻辑范围,末尾元素还存在,但逻辑上不属于堆了swap(arr, 0, --heapSize);while (heapSize > 0) {// 根结点是刚从末尾交换的元素,不一定符合大根堆要求,重新调整大根堆heapify(arr, 0, heapSize);swap(arr, 0, --heapSize); // 最大值交换到末尾,堆范围逻辑缩小}}// 建堆方法// 与父结点比较,直到调整到合适的位置public static void heapInsert(int[] arr, int index) {// 大根堆// 当前元素一直与它的父结点比较while (arr[index] > arr[(index - 1) / 2]) {swap(arr, index, (index - 1) / 2);index = (index - 1) / 2;}}// arr[index] 位置的数,能否向下移动public static void heapify(int[] arr, int index, int heapSize) {int left = index * 2 + 1;while (left < heapSize) {int larger = left;int right = left + 1;if (right < heapSize && arr[left] < arr[right]) {larger = right;}if (arr[index] > arr[larger]) {break; // 如果index大于两个子结点的较大者,就退出}swap(arr, index, larger);index = larger;left = index * 2 + 1;}}
