troysps 2020-06-05
https://www.cnblogs.com/bjwu/articles/10006419.html
/**冒泡排序 * 第一次比较0~N-1位 将最大值沉底 * 第二次比较0~N-2位 将最大值沉底 * ...... * 第N-1次比较0~1位 将最大值沉底(在0~1中沉底) * 时间复杂度:O(n^2) */ public static void sort(int[] nums){ if(nums == null || nums.length < 2){ return; } for(int end = nums.length-1; end > 0; end--){ for(int j = 0; j < end; j++){ if(nums[j] > nums[j+1]){ swap(nums,j,j+1); } } } } public static void swap(int[] nums, int i, int j){ int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
/** 插入排序 * 0~end-1 位置的数都有序 * 每次考察end位置的数应该插在哪里 * 如果前面的数比end位置数大则交换 * * 插入选择时间复杂度和数据样本无关 * 插入排序 有序 O(n) 逆序O(n^2) * 时间复杂度按最差的算 * 所以插入排序的时间复杂度是O(n^2) */ public static void sort(int[] nums){ for(int end = 1; end < nums.length; end++){ for(int i = end-1; i >= 0; i--){//与前面有序部分比较是否能够交换 if(nums[i] > nums[i+1]){ swap(nums,i,i+1); } } } } public static void swap(int[] nums, int i, int j){ int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
/**选择排序 * 第一次比较 0~N-1 找到最小值的位置和0交换 * 第二次比较 1~N-2 找到最小值的位置和1交换 * ...... * 第N-1次比较 N-2~N 找到最小值的位置和N-2交换 * 时间复杂度:O(N^2) */ public static void sort(int[] nums){ if(nums == null || nums.length < 2){ return; } for(int start = 0; start < nums.length; start++){ int minIndex = start; for(int i = start; i < nums.length; i++){ minIndex = nums[i] < nums[minIndex]? i:minIndex; } swap(nums, minIndex, start); } } public static void swap(int[] nums, int i, int j){ int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
public static void quickSort(int [] nums, int left, int right){ if (nums == null || nums.length < 2) { return; } if(left < right){ //随机选一个位置和最后一个数字交换 swap(nums, left+(int)Math.random()*(right-left+1), right); int[] p = partition(nums, left, right);//等于区域的左边界和右边界 quickSort(nums, left, p[0]-1); // 小于区域递归 quickSort(nums, p[1]+1, right); // 大于区域递归 } } public static int[] partition(int[] nums, int left, int right){ int smaller = left - 1; // 小于区域的右边界 int bigger = right; // 大于区域的左边界 while(left < bigger){ int target = nums[right]; if(nums[left] > target){ swap(nums,left,bigger-1); bigger--; }else if(nums[left] < target){ swap(nums,left,smaller+1); smaller++; left++; }else { left++; } } swap(nums, bigger, right); return new int[]{smaller+1,bigger}; } public static void swap(int[] nums, int i, int j){ int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; }
/**归并排序 * 左边排序 右边排序 * 外排整体排序 辅助数组 * 左边有序数组和右边有序数组谁小谁加入辅助数组 * T(N) = 2T(N/2) + O(N) * log2(2) = 1 = d --> * 时间复杂度为 O(N*logN) */ public static void mergeSort(int[] nums){ if(nums ==null || nums.length < 2){ return; } sortProcess(nums, 0, nums.length-1); } public static void sortProcess(int[] nums, int left, int right){ if(left == right){//结束递归条件 这个范围只有一个数 return; } int mid = left + (right - left)/2; sortProcess(nums,left,mid);//左边有序 T(N/2) sortProcess(nums,mid+1,right);//右边有序 T(N/2) merge(nums, left, mid, right);//外排序 O(N) } public static void merge(int[] nums, int left, int mid, int right){ int[] help = new int[right-left+1];//left到right有多少个数 int i = 0;//指向help int p1 = left;//指向左侧数组第一个数 int p2 = mid+1;//指向右侧数组第一个数 while(p1 <= mid && p2 <= right){//有一个数组读完了就退出循环 //哪边小哪边的p指向的元素加入help 加入之后移动p指针 help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++]; } //只有一个越界 把剩余的数组一次性添加进help while(p2 <= right){ help[i++] = nums[p2++]; } while(p1 <= mid){ help[i++] = nums[p1++]; } //将help中的依次覆盖nums for(int j = 0; j < help.length; j++){ nums[left+j] = help[j]; } }
逆序和 & 最小和都可以用归并排序的思路来解
/** * 逆序对和最小和求解思路的区别 * * 【最小和】是优先找小值 * [1,1,2] [2,3,4] * 优先在左侧数组找到小值后 右边剩下的就都比该值要大了 从左向右遍历 * 例如:对于左边第一个元素1 右边第一个元素2及2之后的所有元素都对1有一个小和 * smallsum += 1 * (right-p2+1) * 即 如果左小于右 累加 * * 【逆序对】要求是左侧元素大于右侧 * 优先在左侧找大值 对于升序排序 大值在最右边 应该从右向左遍历 * [1,1,5] [1,3,4] * 例如:对于左边最后一个元素5 大于右边最后一个元素4 则5可以和4及4之前的所有元素形成一个逆序对 * [5,1][5,3][5,4] * count += right-(mid+1)+1 5 - 3 + 1 = 3 个 * 即 如果左大于右 累加 */ public class ReversePair { private static int count; private static List<int[]> list = new ArrayList<>(); public static void mergeSort(int[] nums){ if(nums ==null || nums.length < 2){ return; } sortProcess(nums, 0, nums.length-1); } public static void sortProcess(int[] nums, int left, int right){ if(left == right){//结束递归条件 这个范围只有一个数 return; } int mid = left + (right - left)/2; sortProcess(nums,left,mid);//左边有序 T(N/2) sortProcess(nums,mid+1,right);//右边有序 T(N/2) merge(nums, left, mid, right);//外排序 O(N) } public static void merge(int[] nums, int left, int mid, int right){ int[] helper = new int[right -left + 1]; int p = right - left; int rightBegin = mid + 1; while(left <= mid && rightBegin <= right){ if(nums[mid] > nums[right]){ count += (right - rightBegin)+1; for(int k = right; k >= rightBegin; k--){ list.add(new int[] {nums[mid],nums[k]}); } helper[p--] = nums[mid--]; }else{ helper[p--] = nums[right--]; } } while(left <= mid){ helper[p--] = nums[mid--]; } while(right >= rightBegin){ helper[p--] = nums[right--]; } for(int help:helper){ nums[left++] = help; } } public static void main(String[] args) { int[] nums = {3,5,4,2,1}; mergeSort(nums); System.out.println(Arrays.toString(nums)); System.out.println(count); for(int[] pair: list){ System.out.println(Arrays.toString(pair)); } } }
public class SmallSum { public static int smallSum(int[] arr){ if(arr == null|| arr.length < 2){ return 0; } return mergeSort(arr,0,arr.length-1); } /** * 计算left ~ right范围内有多少小和 */ public static int mergeSort(int[] arr, int left, int right){ if(left == right){ return 0; } int mid = left + ((right - left) >> 1); //右移1位 等于 除以2 //mid = left + (right - left)/2; 防止溢出 return mergeSort(arr, left, mid)+//左边排序产生的小和 mergeSort(arr, mid+1, right)+//右边排序产生的小和 merge(arr, left, mid, right);//最后合并产生的小和 } public static int merge(int[] arr, int left, int mid, int right){ int[] help = new int[right-left+1];//准备一个临时数组 // 长度和传进来的arr一样 是原arr的一部分 用left和right划分出来的一部分 int i = 0; int p1= left; int p2 = mid+1; int res = 0;//计算小和 while(p1 <= mid && p2 <= right){ //关键在这里: // 每一次比较可以知道比当前值更大的值有几个,这个较小的当前值就需要累加多少次。 //如果左边小于右边,那就有(r - p2 + 1)个arr[p1]元素的和是最小和,进行累加。 //p2后面的个数 * p1指向的数 res += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0; help[i++] = arr[p1] > arr[p2] ? arr[p2++] : arr[p1++]; } while(p1 <= mid){ help[i++] = arr[p1++]; } while (p2 <= right){ help[i++] = arr[p2++]; } for(i = 0; i < help.length; i++){ arr[left+i] = help[i]; } return res; } public static void main(String[] args) { int[] nums = {1,3,4,2,5}; int i = smallSum(nums); System.out.println(i); } }
/** * HeapSort展示了heapinsert和heapify的两个过程 * 时间复杂度是O(NlogN) * 实际上可以只用heapify * 用heapify代替heapinsert从后向前heapify * 原来的heapinsert是O(NlogN) heapify时间复杂度是O(N) 可以稍微快一点 * 但最后的时间复杂度还是O(NlogN) 因为后面的操作还是O(NlogN) */ public class HeapSort { //堆化【调整堆】 某个数在index值 能否往下移动 public static void heapify(int[] nums, int index, int heapSize){ //index是初始的父节点 int left = index * 2 + 1; //左孩子 while(left < heapSize){//下方还有孩子时 没有越界 int largest = left; if(left + 1 < heapSize) {//右孩子存在 没有越界 //左右两孩子PK largest = nums[left]>nums[left+1]?left:left+1; } //较大孩子和父亲PK largest = nums[index]>nums[largest]?index:largest; //父亲最大不交换 if(largest == index){ break; } //父亲和较大孩子交换 swap(nums, largest, index); //下一个比较的数 新父亲 index = largest; //交换后的新的左孩子 left = index * 2 + 1; } } /** * 无序数组 相当于依次插入大根堆 heapinsert * 得到大根堆后 * 每次让数组第一个元素(最大值)和最后一个元素交换 * 交换后heapsize--,调整堆heapify为大根堆 * 最后heapsize == 0 数组有序 */ public static void heapSort(int[] nums){ if(nums == null || nums.length < 2){ return; } for (int i = nums.length-1; i >= 0; i--) { heapify(nums,i,nums.length); } int heapSize = nums.length; swap(nums,0,--heapSize);//先交换 while(heapSize > 0){ heapify(nums,0,heapSize);//再堆化 swap(nums,0,--heapSize);//继续交换 } } public static void swap(int[] nums, int i, int j){ int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } public static void main(String[] args) { int[] nums = {2,3,1,4,4,3,3,4,5,1}; heapSort(nums); System.out.println(Arrays.toString(nums)); } }
* 已知一个几乎有序的数组,几乎有序是指, * 如果把数组排好顺序的话,每个元素移动的距离可以不超过K * 可以用堆排序来解决这个问题 * * 思路: * 1.建立有K个元素的小顶堆,然后取出顶上元素 * 2.堆顶用没有建堆的下一元素替代,重新建堆 * 3.反复调用,完成排序,此算法因为每个元素移动都在k以内,所以时间复杂度为O(NlogK) */ public class KHeapSort { public static void kHeapSort(int[] nums, int k) { PriorityQueue<Integer> heap = new PriorityQueue<>(); int index= 0; //k个值加入堆 for(; index <= Math.min(k, nums.length); index++){ heap.add(nums[index]); } int i = 0; //从index的下一个位置开始遍历 也就是k+1 for(; index < nums.length; i++,index++){ heap.add(nums[index]);//加一个 heapsize+1 nums[i] = heap.poll();//弹一个 heapsize-1 } while (!heap.isEmpty()){ nums[i++] = heap.poll(); } } public static void main(String[] args) { int [] nums ={3,2,7,1,8,6,4,5,12,10,15,9,13,14,11,20,16,17,18,19}; kHeapSort(nums,7); System.out.println(Arrays.toString(nums)); } }
public class RadixSort { /** * 统计最大值有多少位 */ public static int maxbits(int[] nums){ int max = nums[0]; for(int i = 0; i < nums.length; i++){ max = Math.max(nums[i], max); } int count = 0; while(max > 0){ max /= 10; count++; } return count; } /** * 获取倒数第i位的数字 i=1 取出个位 */ public static int getDigit(int num,int i){ while(i > 1){ num /= 10; i--; } return num%10; } /** * 基数排序 */ public static void radixSort(int[] arr){ int digit = maxbits(arr); //基数 10 final int radix = 10; //给每个数准备一个桶 int[] buckets = new int[arr.length]; //有多少位 就要出桶进桶几次 for(int d = 1; d <= digit; d++){ //计数器 等于count的有几个 int[] count = new int[radix]; for(int i = 0; i < arr.length; i++){ int j = getDigit(arr[i],d); count[j]++; } //将count变为前缀和 小于等于count的有几个 for(int i = 1; i < radix; i++){ count[i] += count[i-1]; } System.out.println(Arrays.toString(count)); //数组从右向左遍历 入桶 for(int i = arr.length-1; i >=0; i--){ int j = getDigit(arr[i],d); buckets[count[j]-1] = arr[i]; count[j]--; } //当前位数出桶 for(int i = 0; i < arr.length; i++) { arr[i] = buckets[i]; } } } public static void main(String[] args) { int[] nums = {123,423,19,32,321,1200}; radixSort(nums); System.out.println(Arrays.toString(nums)); } }