九大排序算法
最近总结了一下各大常见的算法,并用Java代码实现了一遍。
-
(平均)时间复杂度O(N^2)
-
冒泡排序
时间复杂度最好的情况是O(N)、最坏情况是O(N^2) 空间复杂度(1) 稳定(稳定的意思就是一个数组中相同数据在排序后位置不变)
思想: 比较相邻两个数据的大小。
public static int[] sort(int[] data) { if(data == null) return null; for(int i =0;i<data.length;i++) { for(int j=0;j<data.length - 1-i;j++) { if(data[j] > data[j+1] ) { int temp = data[j]; data[j]= data[j+1]; data[j+1] = temp; } } } return data; } public static int[] optimizeSort(int[] data) { if(data ==null) return null; for(int i = 0;i< data.length;i++) { boolean isSorted = false; for(int j = 0;j<data.length - 1 -i;j++) { if(data[j]> data[j+1] ) { int temp = data[j]; data[j]= data[j+1] ; data[j+1] = temp; isSorted = true; } } if(!isSorted) { //如果排序发现并没有数据进行交换,说明此为有序数据了 //要考虑有序数据 break; } } return data; }
-
插入排序
时间复杂度:最好O(N) 最坏O(N^2) 空间复杂度 O(1) 稳定
思想:假设待插入数据前的数据都是有序的,不断往其中插入数据,一般都是以第一个数据为有序数组,然后插入。
public static int[] sort(int[] data) { if (data == null) return null; // 向有序数组内插入数据 for (int i = 1; i < data.length; i++) { int value = data[i];// 要插入的值 int j = i - 1; for (; j >= 0; --j) { if (value < data[j]) { data[j + 1] = data[j]; } else { break; } } data[j + 1] = value; } return data; }
-
选择排序
时间复杂度最好最坏情况都是O(N^2) 空间复杂度 O(1) 不稳定(每次选择后面的最小的元素进行交换)
思想:选择一个数作为最小值(最大值),然后找出后面的最小(最大)的元素和其交换。
public static int[] sort(int[] data) { if(data == null) return null; for(int i = 0;i<data.length;i++) { int mim = i; for(int j= mim +1;j<data.length;j++) { if(data[mim] >data[j] ) { mim = j; } } if(mim != i) { int temp = data[i]; data[i]= data[mim]; data[mim]= temp; } } return data; }
-
-
(平均)时间复杂度O(N * logN)
-
希尔排序
冒泡排序的升华版 时间复杂度:最好O(N) 最坏O(N^2) 空间复杂度 O(1) 不稳定(这里画个排序示意图就明白了)
思想: 确定步长,即当步长为h,第一次先比较0、h-1、2h-1 .... 位置上的数据,然后步长逐步缩小
public static int[] heerSort(int[] data) { if (data == null) return null; int h = 1;// 步长 int n = data.length; while (h < n / 3) { h = 3 * h + 1; //为什么是3?这是由数学家经过大量实验得来的 } // 0...h-1 // h ....2h-1... while (h >= 1) { for (int i = h; i < n; i++) { int index; int e = data[i]; for (index = i; index >= h && e < data[index - h]; index -= h) { // 交换 data[index] = data[index - h]; } data[index] = e; } h /= 3; } return data; }
-
快速排序
时间复杂度:最好O(N * logN) 最坏O(N^2) 空间复杂度O(logN) ~O(N) 不稳定(如果和基点值一样大的有多个就会出现)。这里最好和最坏肯定是指整个数组是否有序,基点选择的是哪一个
思想:选择一个基点pivot,使得左边的数比它小,右边的数比它大。
/**这里包括普通快排 二路 三路优化版快排**/ public static int[] sort(int[] data) { if (data == null) return null; //quickSort(data, 0, data.length - 1); parition3(data,0,data.length -1); return data; } private static void quickSort(int[] data, int left, int right) { if (left >= right) return; int pos = parition2(data, left, right); quickSort(data, left, pos - 1); quickSort(data, pos + 1, right); } private static int parition(int[] data, int left, int right) { int index = new Random().nextInt((right - left + 1)); int value = data[index + left];// 随机选一个作为基准数 data[index + left] = data[left]; data[left] = value; // int value = data[left]; int j = left; for (int i = left + 1; i <= right; i++) { if (data[i] < value) { data[j] = data[i]; ++j; data[i] = data[j]; // data[j] = value; } } data[j] = value; return j; } private static int parition2(int[] data, int left, int right) { int number = right - left; int index = new Random().nextInt(number); int value = data[left + index];// 随机选一个作为基准数 data[index + left] = data[left]; data[left] = value; int i = left + 1; int j = right; while (i < j) { while (i <= j && data[i] <= value) { i++; } // data[j] = data[i]; while (i <= j && data[j] >= value) { j--; } // data[i] = data[j]; if (i > j) break; int temp = data[i]; data[i] = data[j]; data[j] = temp; i++; j--; } data[left] = data[j]; data[j] = value; // data[i] = value; return j; } private static void parition3(int[] data, int left, int right) { if(left>= right) return; // if(right - left <= 15) { // 插入排序 // } int number = right - left; int index = new Random().nextInt(number); int value = data[left + index];// 随机选一个作为基准数 data[index + left] = data[left]; data[left] = value; // int value = data[left]; int lt = left; // data[ left + 1 ... lt] < v int gt = right + 1;// data[gt ... right] > v int i = left + 1; // data[lt+ 1 ... i] == v while (i < gt) { if (data[i] < value) { int temp = data[lt + 1]; data[lt + 1] = data[i]; data[i] = temp; i++; lt++; } else if (data[i] > value) { int temp = data[gt - 1]; data[gt - 1] = data[i]; data[i] = temp; gt--; } else { i++; } } data[left] = data[lt]; data[lt] = value; parition3(data, left, lt - 1); parition3(data, gt, right); }
-
归并排序
时间复杂度 最好O(N * logN) 最好(N * logN) 稳定 空间复杂度O(N) 至于为什么是O(N)呢?因为新建辅助空间最大也就是N长,而归并排序合并的时候是自底向上的,方法存在于栈帧上,执行完后,栈上空间自动会释放,所以最后是O(N),而快排是自顶向下的,相当于树的深度为logN,而最坏情况是将基点选在最后一个元素上,此时深度是N,所以快排空间复杂度为O(logN) ~ O(N)
思想:采用分而治之思想,将数组分成两半,然后再分,再分,到最后不能分了,就开始合并。
/** * 自顶向下 先分称一个一组 O(NlogN) * * @param data * @return */ public static int[] sort(int[] data) { if (data == null) return null; int left = 0; int right = data.length - 1; mergeSort(data, left, right); return data; } /** * 自底向上 * @param data * @return */ public static int[] sortFormBootm(int[] data) { if (data == null) return null; //这里可以先用插入排序 for(int i=0;i<data.length -1;i+=20) { insertSort(data, i, Math.min(i+19, data.length -1)); } int n = data.length-1; for (int sz = 1; sz <= n; sz += sz) {//步长 逐一合并 for(int i =0;i<n-sz;i+=sz+sz) { if(data[i+sz-1] > data[i+sz]) { merge(data, i, i+sz-1, Math.min(i+2*sz-1,n-1)); } } } return data; } private static void mergeSort(int[] data, int left, int right) { int mid = (right - left+1) / 2; // 将两边进行拆分 mergeSort(data, left, mid); mergeSort(data, mid + 1, right); // 合并 if (data[mid] > data[mid + 1]) {// 当合并时,最后一个数大于第一个数时才需要排序 merge(data, left, mid, right); } } private static void merge(int[] data, int left, int mid, int right) { // 总共有 right - left + 1 个元素 int[] aux = new int[right - left + 1]; for (int i = left; i <= right; i++) { aux[i - left] = data[i]; } int i = left, j = mid + 1; for (int k = left; k <= right; k++) { if (i > mid) { // left 这边没有数据了 data[k] = aux[j - left]; ++j; } else if (j > right) { // right 这边没有数据了 data[k] = aux[i - left]; ++i; } else if (aux[i - left] < aux[j - left]) { data[k] = aux[i - left]; ++i; } else { data[k] = aux[j - left]; ++j; } } } private static void insertSort(int[] data,int left,int right) { if(right< left) return; for(int i = left;i <= right;i++) { int value = data[i + 1]; int j = i+1; for(;j>=0 && value < data[j-1];--j) { data[j] = data[j-1]; } data[j] = value; } }
-
堆排序
时间复杂度最好、最坏情况都是O(logN) 不稳定 空间复杂度O(1)
思想:构建一个最大堆,每次取出堆顶元素,就得将堆底元素放到堆顶,然后排序。(最大堆:父节点比左右孩子节点值大)
private int[] data;//数组从 1 下标开始存数据。k 子节点 为 2k 2k+1 ,如果从0开始,子节点为 2k+1,2k+2 private int count; private int capacity; public HeapSort(int capacity) { data = new int[capacity]; count = 0; this.capacity = capacity; } public boolean isEmpty() { return count == 0; } public void insert(int a) { if (count > capacity) return; data[count + 1] = a; shiftUp(count + 1); count++; } public int extractMax() { int result = data[1]; data[1] = data[count]; data[count] = 0; count--; shiftDown(1); return result; } /** * 插入第k个节点 logN * * @param k */ private void shiftUp(int k) { while (k > 1 && data[k / 2] < data[k]) {// 父节点 值 小于 子节点 就需要交换 int temp = data[k]; data[k] = data[k / 2]; data[k / 2] = temp; k = k / 2; } } private void shiftDown(int k) { int value = data[k]; while (2 * k <= count) {// 父节点为k,左子孩子为 2*k int j = 2 * k; if (j + 1 <= count && data[j] < data[j + 1]) { j++; } if (value >= data[j]) { break; } //int temp = data[j]; //data[j] = data[k]; data[k] = data[j]; k = j;// 继续深入树内 } data[k] = value; }
-
-
时间复杂度为O(M+N)
-
桶排序 不稳定
思想: 将数据分到有限量的桶子里,对每个桶子数据进行排序,然后取出来就是有序的了。
做法:每个桶存放的数据范围先定下来,从最小值到最大值,新建一个二位数组(或List),二维存放的是数据,一维是桶的编号。首先通过 element * N /(max + 1) 得到 需要放到哪个桶中,然后存放数据就可以了。
-
计数排序
思想:基于桶排序,例如 0~10的数据,数据分别为 2 3 8 5 5,则需要建一个长度为11数组桶,编号从0到10,0号桶存放0出现的次数,以此类推,遍历完后,打印桶号,打印次数为桶号对应数据的大小。
/** * 如果已知这些数的范围 例如为0 到 100 分 * O(2*(m+n)) -> O(M) * @param data */ public static void sort(int... data) { int[] a = new int[101]; //初始值都为0,代表出现过0次该分 for(int i =0;i<data.length;i++) {// n次 a[data[i]] ++; } for(int i=0;i<a.length;i++) {// m 次 if(a[i]!= 0 ) { for(int j = 0;j<a[i];j++) { // m- 不等于0 次 System.out.print(i+"\t"); // n次 } // System.out.println(); } } } /** * 计数排序 基于桶排序思想,由于数过多,可以使用数出现的频率作为桶编号 * @param data */ public static void sortCount(int min,int max,int... data) { int length = max - min + 1; int[] a = new int[length]; for(int i = 0;i<data.length;i++) {//最小值对应数组下标 0 a[data[i]- min] ++;//出现了多少次 } for(int i =0;i<a.length;i++) { if(a[i]!= 0 ) { for(int j = 0;j<a[i];j++) { System.out.print((i+min)+"\t"); } } } }
-
基数排序
思想:基于桶排序,利用计数排序的思想,先对个位上的数字建立桶,然后入桶,然后再对十位上的数字,以此类推,最后打印出数据就是拍好序了的。
/** * 基数排序 * 空间O(N) * 时间O(N*digist) * @param max * @param data */ public static void sortBase(int max,int... data) { int base = 10; int length= 1; while(max>base) { length ++; base *= 10; } int[] temp = new int[data.length];//0.....9 int dividend = 1; while(length>0) { int[] count = new int[10]; //统计某一位出现相同数字的个数 for(int i = 0;i<data.length;i++) { int index = data[i]/dividend %10; count[index]++; } //统计个位相同的数在数组中出现的位置 for(int i = 1;i<10;i++) { //0 出现0次 1出现2次 2出现3次 那么2的下标为 1 2 3 count[i]+= count[i-1]; //i出现的位置为 i +(i-1) } for(int i =data.length-1;i>=0;i--) { int index = data[i]/dividend %10; temp[--count[index]] = data[i];//先使用再增加,记录的是个数某个数字出现的起始位置 } dividend *= 10; length --; } for(int n:temp) { System.out.print(n+"\t"); } }
-