算法基础入门——归并排序、堆排序、快速排序、小和问题、有序数组的排序、荷兰国旗问题
package com.zuoshen.jichurumen.class02; import java.util.PriorityQueue; /** * @author ShiZhe * @create 2022-02-24 9:06 */ public class code01 { /** * 归并排序 * 思想是分而治之 * 时间复杂度O(N*logN),额外空间复杂度O(N) * @param arr */ public static void mergeSort(int[] arr, int left, int right) { if (arr == null || arr.length < 2) { return; } if (left == right) { return; } int mid = left + ((right - left) >> 1); mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); merge(arr, left, mid, right); } /** * 归并排序算法步骤: * 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列; * 设定两个指针,最初位置分别为两个已经排序序列的起始位置; * 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置; * 重复步骤 3 直到某一指针达到序列尾; * 将另一序列剩下的所有元素直接复制到合并序列尾。 * @param arr * @param left * @param mid * @param right */ public static void merge(int[] arr, int left, int mid, int right) { // 辅助空间,将2个有序序列比较后合成一个有序序列 int[] help = new int[right - left +1]; // 辅助空间下标 int i = 0; // 第一个有序序列的指针 int one = left; // 第二个有序序列的指针 int two = mid + 1; // 利用2个指针比较大小 while (one <= mid && two <= right) { help[i++] = arr[one] < arr[two] ? arr[one++] :arr[two++]; } while (one <= mid) { help[i++] = arr[one++]; } while (two <= right) { help[i++] = arr[two++]; } for (i = 0; i < help.length; i++) { arr[left++] = help[i]; } } /** * 小和问题 * 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。 * 例子:[1,3,4,2,5] 1左边比1小的数,没有; 3左边比3小的数,1; 4左边比4小的数,1、3; 2左边比2小的数,1; 5左边比5小的数,1、3、4、2; 所以小和为1+1+3+1+1+3+4+2=16 * 转换思路:求每一个数左边比当前小的数,也就等同于求每个数右边比当前大的数的个数 */ public static int smallSum(int[] arr, int left, int right) { if (arr == null || arr.length < 2){ return 0; } if (left == right) { return 0; } int mid = left + ((right - left) >> 1); return smallSum(arr, left ,mid) + smallSum(arr,mid + 1, right) + smallSumMergeSort(arr, left, mid ,right); } public static int smallSumMergeSort(int[] arr, int left, int mid, int right) { int[] help = new int[right - left + 1]; int i = 0; int one = left; int two = mid + 1; // 和 int res = 0; while (one <= mid && two <= right) { // 这一步最重要,左边有序序列的某值比右边有序序列的某值小,那右边有序序列某值之后的所有值都比左边某值大 res = arr[one] < arr[two] ? res+arr[one] * (right - two + 1) :res; help[i++] = arr[one] < arr[two] ? arr[one++] :arr[two++]; } while (one <= mid){ help[i++] = arr[one++]; } while (two <= right){ help[i++] = arr[two++]; } for (i = 0; i < help.length; i++) { arr[left++] = help[i]; } return res; } /** * 堆排序 * 第一步:生成大根堆 * 第二步:把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始 * @param arr */ public static void heapSort(int[] arr) { if (arr == null || arr.length < 2) { return; } // 生成大根堆 for (int i = 0; i < arr.length; i++) { heapInsert(arr, i); } int size = arr.length; swap(arr, 0, --size); // 调整,注意循环条件,使用异或调整,最后一个不换 while (size > 1) { heapify(arr, 0, size); swap(arr, 0, --size); } } /** * 将一个数组生成为大根堆 * 时间复杂度为O(N) * @param arr * @param index */ public static void heapInsert(int[] arr, int index) { // 当一个孩子的值大于父亲节点的值得时候交换,注意数组的下标从0开始 // 左孩子是2 * i + 1,右孩子是2 * i + 2 // 父亲节点是(i - 1) / 2 // 注意(0 - 1) >> 1 = -1 // while (arr[index] > arr[(index -1) >> 1]) { // swap(arr, index, (index -1) >> 1); // index = (index -1) >> 1; // } // (0 - 1) / 2 = 0 while (arr[index] > arr[(index - 1) / 2]) { swap(arr, index, (index - 1) /2); index = (index - 1)/2 ; } } /** * 调整 * 时间复杂度为O(N*logN) * @param arr * @param index * @param size */ public static void heapify(int[] arr, int index, int size) { // index的左孩子 int left = index * 2 + 1; while (left < size) { // 左孩子和右孩子中比较大小,得到大的下标 int bigIndex = (left + 1) < size && arr[left] < arr[left + 1] ? left + 1 : left; // 孩子中大的与index相比较大小,得到最大的下标 bigIndex = arr[bigIndex] > arr[index] ? bigIndex : index; // index为最大值,不需要调整,跳出循环 if (bigIndex == index) { break; } swap(arr, index, bigIndex); index = bigIndex; left = index * 2 + 1; } } /** * 数组异或交换 * 当输入的i和j相同时,交换结果均为0 * @param arr * @param i * @param j */ public static void swap(int[] arr, int i, int j) { arr[i] = arr[i] ^ arr[j]; arr[j] = arr[i] ^ arr[j]; arr[i] = arr[i] ^ arr[j]; } /** * 数组辅助交换 * @param arr * @param i * @param j */ public static void swap2(int[] arr,int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } /** * 对近乎有序的数组进行排序,近乎有序是,如果排好序的话,指每个元素移动的距离不超过k * @param arr * @param k */ public static void sortedArrDistanceLessK(int[] arr, int k) { // PriorityQueue类提供堆数据结构的功能。默认是升序排列 PriorityQueue<Integer> heap = new PriorityQueue<>(); int index = 0; // 将前k个放入堆中,排列有序 for (; index < Math.min(arr.length, k); index++) { heap.add(arr[index]); } int i = 0; // 依次添加数组元素和将队首输出,保持堆中个数为k for (; index < arr.length; i++, index++) { heap.add(arr[index]); arr[i] = heap.poll(); } // 将队列中最后k个依次输出 while (!heap.isEmpty()) { arr[i++] = heap.poll(); } } /** * 荷兰国旗问题 * 给定一个数组arr,和一个数num, * 请把小于num的数放在数组的左边, * 等于num的数放在数组的中间, * 大于num的数放在数组的右边。 * 要求额外空间复杂度O(1),时间复杂度O(N) * @param arr * @param left * @param right * @param num * @return */ public static int[] netherlandsFlag(int[] arr, int left, int right, int num) { // less指向小于num的下标 int less = left - 1; // more指向大于num的下标 int more = right + 1; while (left < more) { if (arr[left] < num) { swap2(arr, ++less, left++); } else if (arr[left] > num) { swap2(arr, --more, left); } else { left++; } } // 返回的是等于num值得数组下标区间 return new int[] {less + 1, more -1}; } /** * 快速排序:对荷兰国旗问题的再应用,将数组最后一个值作为划分值 * 划分值越靠近两侧,复杂度越高;划分值越靠近中间,复杂度越低,不改进的快速排序时间复杂度为O(N^2) * 改进的快排就是等概率随机选取一个值作为划分值 * @param arr * @param left * @param right */ public static void quickSort(int[] arr, int left, int right) { if (arr == null || arr.length < 2) { return; } if (left < right) { // 将某个值与最后的值做交换,等概率随机选取一个值作为划分值 swap2(arr, left + (int) (Math.random() * (right - left + 1)), right); // 划分为3部分 int[] p = partition(arr, left, right); // 递归调用 quickSort(arr, left, p[0] - 1); quickSort(arr, p[1] + 1, right); } } public static int[] partition(int[] arr, int left, int right) { int less = left - 1; int more = right; while (left < more) { if (arr[left] < arr[right]) { swap2(arr, ++less, left++); } else if (arr[left] > arr[right]) { swap2(arr, -- more, left); } else { left++; } } swap2(arr, more, right); // 注意这里将最后的值作为划分值,进行交换后,返回的p[1]应该为more,而不是more-1 return new int[] {less + 1, more}; } public static void main(String[] args) { // 归并排序 int[] arr1 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534}; mergeSort(arr1, 0, arr1.length - 1); for (int i = 0; i < arr1.length; i++) { System.out.println(arr1[i]); } // 小和问题 int[] arr2 = {1, 3, 4, 2, 5}; int small = smallSum(arr2, 0, arr2.length - 1); System.out.println(small); // 堆排序 int[] arr3 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534}; heapSort(arr3); for (int i = 0; i < arr3.length; i++) { System.out.println(arr3[i]); } // 注意:(0 - 1) / 2 = 0 System.out.println((0-1)/2); // 注意:(0 - 1) >> 1 = -1 System.out.println((0-1)>> 1); // 堆排序扩展 int[] arr4 = {1, 3, 2, 4, 5, 6, 8, 7, 10, 9}; sortedArrDistanceLessK(arr4, 2); for (int i = 0; i < arr4.length; i++) { System.out.println(arr4[i]); } // 荷兰国旗问题 int[] arr5 = {0, 2, 0, 0, 2, 2, 1, 2, 1, 0}; int[] partition = netherlandsFlag(arr5, 0, arr5.length - 1, 1); for (int i = 0; i < arr5.length; i++) { System.out.println(arr5[i]); } for (int i = 0; i < partition.length; i++) { System.out.println(partition[i]); } // 快排 int[] arr6 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534}; quickSort(arr6, 0, arr6.length -1); for (int i = 0; i < arr6.length; i++) { System.out.println(arr6[i]); } } }