排序算法总结
排序算法比较
https://www.cnblogs.com/bjwu/articles/10006419.html
冒泡排序
1 /**冒泡排序 2 * 第一次比较0~N-1位 将最大值沉底 3 * 第二次比较0~N-2位 将最大值沉底 4 * ...... 5 * 第N-1次比较0~1位 将最大值沉底(在0~1中沉底) 6 * 时间复杂度:O(n^2) 7 */ 8 public static void sort(int[] nums){ 9 if(nums == null || nums.length < 2){ 10 return; 11 } 12 for(int end = nums.length-1; end > 0; end--){ 13 for(int j = 0; j < end; j++){ 14 if(nums[j] > nums[j+1]){ 15 swap(nums,j,j+1); 16 } 17 } 18 } 19 20 } 21 public static void swap(int[] nums, int i, int j){ 22 int tmp = nums[i]; 23 nums[i] = nums[j]; 24 nums[j] = tmp; 25 }
插入排序
1 /** 插入排序 2 * 0~end-1 位置的数都有序 3 * 每次考察end位置的数应该插在哪里 4 * 如果前面的数比end位置数大则交换 5 * 6 * 插入选择时间复杂度和数据样本无关 7 * 插入排序 有序 O(n) 逆序O(n^2) 8 * 时间复杂度按最差的算 9 * 所以插入排序的时间复杂度是O(n^2) 10 */ 11 public static void sort(int[] nums){ 12 for(int end = 1; end < nums.length; end++){ 13 for(int i = end-1; i >= 0; i--){//与前面有序部分比较是否能够交换 14 if(nums[i] > nums[i+1]){ 15 swap(nums,i,i+1); 16 } 17 } 18 } 19 } 20 21 public static void swap(int[] nums, int i, int j){ 22 int tmp = nums[i]; 23 nums[i] = nums[j]; 24 nums[j] = tmp; 25 }
选择排序
/**选择排序 * 第一次比较 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; }
快排
1 public static void quickSort(int [] nums, int left, int right){ 2 if (nums == null || nums.length < 2) { 3 return; 4 } 5 if(left < right){ 6 //随机选一个位置和最后一个数字交换 7 swap(nums, left+(int)Math.random()*(right-left+1), right); 8 int[] p = partition(nums, left, right);//等于区域的左边界和右边界 9 quickSort(nums, left, p[0]-1); // 小于区域递归 10 quickSort(nums, p[1]+1, right); // 大于区域递归 11 } 12 } 13 14 public static int[] partition(int[] nums, int left, int right){ 15 int smaller = left - 1; // 小于区域的右边界 16 int bigger = right; // 大于区域的左边界 17 while(left < bigger){ 18 int target = nums[right]; 19 if(nums[left] > target){ 20 swap(nums,left,bigger-1); 21 bigger--; 22 }else if(nums[left] < target){ 23 swap(nums,left,smaller+1); 24 smaller++; 25 left++; 26 }else { 27 left++; 28 } 29 } 30 swap(nums, bigger, right); 31 return new int[]{smaller+1,bigger}; 32 } 33 public static void swap(int[] nums, int i, int j){ 34 int tmp = nums[i]; 35 nums[i] = nums[j]; 36 nums[j] = tmp; 37 }
归并排序
1 /**归并排序 2 * 左边排序 右边排序 3 * 外排整体排序 辅助数组 4 * 左边有序数组和右边有序数组谁小谁加入辅助数组 5 * T(N) = 2T(N/2) + O(N) 6 * log2(2) = 1 = d --> 7 * 时间复杂度为 O(N*logN) 8 */ 9 public static void mergeSort(int[] nums){ 10 if(nums ==null || nums.length < 2){ 11 return; 12 } 13 sortProcess(nums, 0, nums.length-1); 14 } 15 public static void sortProcess(int[] nums, int left, int right){ 16 if(left == right){//结束递归条件 这个范围只有一个数 17 return; 18 } 19 int mid = left + (right - left)/2; 20 sortProcess(nums,left,mid);//左边有序 T(N/2) 21 sortProcess(nums,mid+1,right);//右边有序 T(N/2) 22 merge(nums, left, mid, right);//外排序 O(N) 23 } 24 25 public static void merge(int[] nums, int left, int mid, int right){ 26 int[] help = new int[right-left+1];//left到right有多少个数 27 int i = 0;//指向help 28 int p1 = left;//指向左侧数组第一个数 29 int p2 = mid+1;//指向右侧数组第一个数 30 31 while(p1 <= mid && p2 <= right){//有一个数组读完了就退出循环 32 //哪边小哪边的p指向的元素加入help 加入之后移动p指针 33 help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++]; 34 } 35 //只有一个越界 把剩余的数组一次性添加进help 36 while(p2 <= right){ 37 help[i++] = nums[p2++]; 38 } 39 while(p1 <= mid){ 40 help[i++] = nums[p1++]; 41 } 42 //将help中的依次覆盖nums 43 for(int j = 0; j < help.length; j++){ 44 nums[left+j] = help[j]; 45 } 46 }
逆序和 & 最小和都可以用归并排序的思路来解
逆序对
1 /** 2 * 逆序对和最小和求解思路的区别 3 * 4 * 【最小和】是优先找小值 5 * [1,1,2] [2,3,4] 6 * 优先在左侧数组找到小值后 右边剩下的就都比该值要大了 从左向右遍历 7 * 例如:对于左边第一个元素1 右边第一个元素2及2之后的所有元素都对1有一个小和 8 * smallsum += 1 * (right-p2+1) 9 * 即 如果左小于右 累加 10 * 11 * 【逆序对】要求是左侧元素大于右侧 12 * 优先在左侧找大值 对于升序排序 大值在最右边 应该从右向左遍历 13 * [1,1,5] [1,3,4] 14 * 例如:对于左边最后一个元素5 大于右边最后一个元素4 则5可以和4及4之前的所有元素形成一个逆序对 15 * [5,1][5,3][5,4] 16 * count += right-(mid+1)+1 5 - 3 + 1 = 3 个 17 * 即 如果左大于右 累加 18 */ 19 public class ReversePair { 20 private static int count; 21 private static List<int[]> list = new ArrayList<>(); 22 public static void mergeSort(int[] nums){ 23 if(nums ==null || nums.length < 2){ 24 return; 25 } 26 sortProcess(nums, 0, nums.length-1); 27 } 28 public static void sortProcess(int[] nums, int left, int right){ 29 if(left == right){//结束递归条件 这个范围只有一个数 30 return; 31 } 32 int mid = left + (right - left)/2; 33 sortProcess(nums,left,mid);//左边有序 T(N/2) 34 sortProcess(nums,mid+1,right);//右边有序 T(N/2) 35 merge(nums, left, mid, right);//外排序 O(N) 36 } 37 38 public static void merge(int[] nums, int left, int mid, int right){ 39 int[] helper = new int[right -left + 1]; 40 int p = right - left; 41 int rightBegin = mid + 1; 42 while(left <= mid && rightBegin <= right){ 43 if(nums[mid] > nums[right]){ 44 count += (right - rightBegin)+1; 45 for(int k = right; k >= rightBegin; k--){ 46 list.add(new int[] {nums[mid],nums[k]}); 47 } 48 helper[p--] = nums[mid--]; 49 }else{ 50 helper[p--] = nums[right--]; 51 } 52 } 53 while(left <= mid){ 54 helper[p--] = nums[mid--]; 55 } 56 while(right >= rightBegin){ 57 helper[p--] = nums[right--]; 58 } 59 for(int help:helper){ 60 nums[left++] = help; 61 } 62 } 63 64 public static void main(String[] args) { 65 int[] nums = {3,5,4,2,1}; 66 mergeSort(nums); 67 System.out.println(Arrays.toString(nums)); 68 System.out.println(count); 69 for(int[] pair: list){ 70 System.out.println(Arrays.toString(pair)); 71 } 72 } 73 }
最小和
1 public class SmallSum { 2 public static int smallSum(int[] arr){ 3 if(arr == null|| arr.length < 2){ 4 return 0; 5 } 6 return mergeSort(arr,0,arr.length-1); 7 } 8 9 /** 10 * 计算left ~ right范围内有多少小和 11 */ 12 public static int mergeSort(int[] arr, int left, int right){ 13 if(left == right){ 14 return 0; 15 } 16 int mid = left + ((right - left) >> 1); //右移1位 等于 除以2 17 //mid = left + (right - left)/2; 防止溢出 18 return mergeSort(arr, left, mid)+//左边排序产生的小和 19 mergeSort(arr, mid+1, right)+//右边排序产生的小和 20 merge(arr, left, mid, right);//最后合并产生的小和 21 } 22 23 public static int merge(int[] arr, int left, int mid, int right){ 24 int[] help = new int[right-left+1];//准备一个临时数组 25 // 长度和传进来的arr一样 是原arr的一部分 用left和right划分出来的一部分 26 int i = 0; 27 int p1= left; 28 int p2 = mid+1; 29 int res = 0;//计算小和 30 while(p1 <= mid && p2 <= right){ 31 //关键在这里: 32 // 每一次比较可以知道比当前值更大的值有几个,这个较小的当前值就需要累加多少次。 33 //如果左边小于右边,那就有(r - p2 + 1)个arr[p1]元素的和是最小和,进行累加。 34 //p2后面的个数 * p1指向的数 35 res += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0; 36 help[i++] = arr[p1] > arr[p2] ? arr[p2++] : arr[p1++]; 37 } 38 while(p1 <= mid){ 39 help[i++] = arr[p1++]; 40 } 41 while (p2 <= right){ 42 help[i++] = arr[p2++]; 43 } 44 for(i = 0; i < help.length; i++){ 45 arr[left+i] = help[i]; 46 } 47 return res; 48 } 49 50 public static void main(String[] args) { 51 int[] nums = {1,3,4,2,5}; 52 int i = smallSum(nums); 53 System.out.println(i); 54 } 55 }
堆排序
/** * 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)); } }