排序算法
1. 插入排序
/** * 升序排列:每个数都一步一步往前找到自己的位置,前面的数是排序好的。 *算法复杂度:O(n*n) */ public static void getInsertSort(int[] a) { if(a == null || a.length == 0) { System.out.println("该数组为空!"); eturn; } int n = a.length; int temp; int j; for(int i = 1; i < n;i++){ temp = a[i]; j = i-1; for(; j>=0 && a[j]>temp; --j) { a[j+1] = a[j]; } a[j+1]= temp; } }
2. 快排
/** * 递归方式的快排(分治法): * 1. 分解(divide):数组A[p..r]被划分为两个(可能为空)子数组A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每个元素都小于等于A[q],而A[q]也小于等于A[q+1..r]中的每个元素。其中,计算下标q也是划分过程的一部分。 * 2. 解决(Conquer):通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]进行排序。 * 3. 合并(Combine):因为子数组都是原址排序,所以不需要合并操作:数组A[p..r]已经有序。 */ public void QuickSort1(int[] a, int p, int r) { if (p < r) { int q = Partition(a, p, r); QuickSort1(a, p, q-1); QuickSort1(a,q+1, r); } } private int Partition(int[] A, int p, int r) { int x = A[r]; //取比较值 int i = p-1; for (int j = p; j < r; j++) { if (A[j] <= x) { i++; int tmp = A[j]; A[j] = A[i]; A[i] = tmp; } } int tmp = A[r]; A[r] = A[i+1]; A[i+1] = tmp; return i+1; }
// 随机快排:随机选取主元,没有特定输入引起最差的运行效率 public void Sort(int[] a, int p, int r) { if (p < r) { int q = Partition(a, p, r); Sort(a, p, q-1); Sort(a,q+1, r); } } private int Partition(int[] A, int p, int r) { /*随机选取主元元素*/ Random random = new Random(); int random_index = random.nextInt(r-p+1)+p; int temp = A[random_index]; A[random_index] = A[r]; A[r] = temp; int x = A[r]; //pivot = A[p] int i = p-1; for (int j = p; j < r; j++) { if (A[j] <= x) { //与pivot作比较 i++; int tmp = A[j]; A[j] = A[i]; A[i] = tmp; } } int tmp = A[r]; A[r] = A[i+1]; A[i+1] = tmp; return i+1; }
// 递归+双指针 public static void sort(int[] arr, int start, int end) { int left = start; int right = end; int temp = 0; if(left <= right) { temp = arr[left]; //以左边为基准 while(left != right) { //左右两边交替扫描,直到left == right while(left < right && arr[right] >= temp) { right--; //从右往左扫描,直到找到第一个小于基准的位置 } if(left < right) { arr[left] = arr[right]; } while(left < right && arr[left] <= temp) { left++; //从左往右扫描,直到找到第一个大于基准的位置 } if(left < right) { arr[right] = arr[left]; } } arr[right] = temp; sort(arr, start, left - 1); //此时left==right sort(arr, right + 1, end); } }
3. 归并排序
// 归并排序:分治法 // PS: 当排序规模较小时,插入排序的代价要小于递归。递归到小规模时,就不要递归了,换用插入排序。 public static int[] sort(int[] a,int low,int high){ int mid = (low+high)/2; if(low<high){ sort(a,low,mid); sort(a,mid+1,high); //左右归并 merge(a,low,mid,high); } return a; } public static void merge(int[] a, int low, int mid, int high) { int[] temp = new int[high-low+1]; int i= low; int j = mid+1; int k=0; // 把较小的数先移到新数组中 while(i<=mid && j<=high){ if(a[i]<a[j]){ temp[k++] = a[i++]; }else{ temp[k++] = a[j++]; } } // 把左边剩余的数移入数组 while(i<=mid){ temp[k++] = a[i++]; } // 把右边边剩余的数移入数组 while(j<=high){ temp[k++] = a[j++]; } // 把新数组中的数覆盖nums数组 for(int x=0;x<temp.length;x++){ a[x+low] = temp[x]; } }
4. 堆排序
public static void heapSort(int[] arr) { //构建大顶堆(一棵完全树的非叶节点的index是len/2-1) for(int i = arr.length/2 - 1; i >= 0; i--) { sink(arr, i, arr.length); } //排序,将最大值(根节点)放在最后一位,其它数再推选一个根节点 for(int i = arr.length - 1; i >= 0; i--) { int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; sink(arr, 0, i); } } private static void sink(int[] arr, int index, int len) { int temp = arr[index];//主角,上升或下降 for(int i = 2*index + 1; i < len; i = 2*index + 1) { if(i < len - 1 && arr[i] < arr[i+1]) { i++; //儿子中最大的和老爸比 } if(temp >= arr[i]) break; arr[index] = arr[i]; index = i; } arr[index] = temp; }
5. 计数排序
原理:因为数组的下标是升序的,我们用数据对应辅助数组的下标,便可以得到有序的数据缺点:空间换时间,当 k 很大时,效果不如基于比较的排序复杂度:O(n+k),辅助数组为大小为k
public int[] countingSort(int[] A, int n) { //找数组中的最大值和最小值,确定桶的个数 int max=A[0]; int min=A[0]; for(int i=0;i<n;i++){ if(A[i]>max) max=A[i]; if(A[i]<min) min=A[i]; } //定义桶数组B int[] B= new int[max-min+1]; //把数组A的元素装到对应桶里 for(int i=0; i<n; i++){ B[A[i]-min]++; } //把所有桶倒出来 for(int i=0, j=0; j<max-min+1; j++){ //倒桶j for(int k=B[j]; k>0; k--){ A[i++]=j+min; } } return A; }
6. 桶排序
思想:类似于ConcurrentHashMap的分段处理,将元素放进N个桶中,对每个桶排序(桶与桶在分割的时候是有序的)优点:降低了内存占用,不用像计数排序那样声明那么大的数组缺点:如果数据分布不均匀,可能导致元素大量集中在某几个桶中,简化排序的意义不在
public static void bucketSort(int[] arr){ int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; for(int i = 0; i < arr.length; i++){ max = Math.max(max, arr[i]); min = Math.min(min, arr[i]); } //桶数 int bucketNum = (max - min) / arr.length + 1; ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum); for(int i = 0; i < bucketNum; i++){ bucketArr.add(new ArrayList<Integer>()); } //将每个元素放入桶 for(int i = 0; i < arr.length; i++){ int num = (arr[i] - min) / (arr.length); bucketArr.get(num).add(arr[i]); } //对每个桶进行排序 for(int i = 0; i < bucketArr.size(); i++){ Collections.sort(bucketArr.get(i)); } System.out.println(bucketArr.toString()); }
7. 基数排序
基本思想:将整数按位数切割成不同的数字,然后按每个位数分别比较。具体做法:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。步骤:1. 取余10,比个位,排序2. 除以10 ,取余10,比10位,排序(升位或降位)......
public class RadixSort { // 各位装通法, n为最大元素的length, length为数组大小 public int[] radixSort(int[] A, int n, int length) { int divisor = 1;// 定义每一轮的除数,1,10,100... int[][] bucket = new int[10][length];// 定义了10个桶,以防每一位都一样全部放入一个桶中,length为了防止所有数都放在一个桶里 int[] count = new int[10];// 统计每个桶中实际存放的元素个数 int digit;// 获取元素中对应位上的数字,即装入那个桶 for (int i = 1; i <= n; i++) { // 经过n次装通操作,排序完成 for (int temp : A) {// 计算入桶 digit = (temp / divisor) % 10; bucket[digit][count[digit]++] = temp; } int k = 0;// 被排序数组的下标 for (int b = 0; b < 10; b++) {// 从0到9号桶按照顺序取出 if (count[b] == 0)// 如果这个桶中没有元素放入,那么跳过 continue; for (int w = 0; w < count[b]; w++) { A[k++] = bucket[b][w]; } count[b] = 0;// 桶中的元素已经全部取出,计数器归零 } divisor *= 10; } return A; } }