文章参考:
开发者头条@zhutoulwz
http://student.zjzk.cn/course_ware/data_structure/web/paixu/paixu8.1.1.1.htm
不同的排序算法在不同的场景或应用中会有不同的表现,我们需要对各种排序算法熟练才能将它们应用到实际当中,才能更好地发挥它们的优势,这也是面试经常问到的,以下是常见的几种算法的复杂度和稳定性以及代码示例:
private int[] resultArr; /** * 从小到大 * 冒泡排序--它是基于比较的排序算法,时间复杂度为O(n^2),其优点是实现简单,n较小时性能较好。 * <h3>算法原理</h3> 相邻的数据进行两两比较,小数放在前面,大数放在后面,这样一趟下来,最小的数就被排在了第一位,第二趟也是如此,如此类推,直到所有的数据排序完成 * @param int[] arr * @return int[] resultArr * */ public int[] bubbleSort(int[] arr) { int len = arr.length; int MAX = 0; resultArr = arr; for (int i = 0; i < len-1; i++) { for (int j = len-1; j > 0; j--) { if (resultArr[j] < resultArr[j-1]) { MAX = resultArr[j-1]; resultArr[j-1] = resultArr[j]; resultArr[j] = MAX; } } } return resultArr; }
private int[] resultArr; /** * 从小到大 * 选择排序--是一种简单直观的排序算法, 选择排序是不稳定的排序方法(比如序列[5, 5, 3]第一次就将第一个[5]与[3]交换,导致第一个5挪动到第二个5后面)。 * <h3>算法原理</h3> 先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 * @param int[] arr * @return int[] resultArr * */ public int[] selectSort(int[] arr) { int len = arr.length; resultArr = arr; for (int i = 0; i < len; i++) { int index = i; for (int j = i+1; j < len; j++) { if (resultArr[j] < resultArr[index]) { index = j; } if (index != i) { int temp = resultArr[i]; resultArr[i] = resultArr[index]; resultArr[index] = temp; } } } return resultArr; }
private int[] resultArr; /** * 从小到大 * 插入排序(直接插入排序)--每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。 时间复杂度为O(n^2),是一种稳定的排序算法。 * <h3>算法原理</h3> 将数据分为两部分,有序部分与无序部分,一开始有序部分包含第1个元素,依次将无序的元素插入到有序部分,直到所有元素有序. * @param int[] arr * @return int[] resultArr * */ public int[] insertSort(int[] arr) { int len = arr.length; resultArr = arr; for (int i = 1; i < len; i++) { int j = i-1; int k = arr[i]; while (j > -1 && k < arr[j]) { arr[j+1] = arr[j]; j--; } arr[j+1] = k; } return resultArr; }
private int[] resultArr; /** * 从小到大 * 插入排序(二分插入排序)--二分插入排序是稳定的与二分查找的复杂度相同; * 最好的情况是当插入的位置刚好是二分位置 所用时间为O(n); * 最坏的情况是当插入的位置不在二分位置 所需比较次数为 n^2/2;时间复杂度为:O(n^2) * <h3>算法原理</h3> 其实也属于插入法类型,分已排序和未排序部分.然后将未排序部分元素逐个排序插入, 但是插入的过程不同,需要每次求一个中间位置,和中间位置元素比较大小,然后根据大小情况, 将高位左移或者将低位右移,再求中间元素比较,直到找到合适位置后,将其后已排序元素全部后移一位,再插入该匀速即可。 * @param int[] arr * @return int[] resultArr * */ public int[] binarySort(int[] arr) { int len = arr.length; resultArr = arr; for (int i = 0; i < len; i++) { int temp = resultArr[i]; int left = 0; int right = i - 1; int mid = 0; while (left <= right) { mid = (left + right) >> 1; if (temp < resultArr[mid]) { right = mid - 1; } else { left = mid + 1; } } for (int j = i - 1; j >= left; j--) { resultArr[j + 1] = resultArr[j]; } if (left != i) { resultArr[left] = temp; } } return resultArr; }
private int[] resultArr; /** * 从小到大 * 插入排序(希尔排序)--是一种是插入排序的一种类型,也可以用一个形象的叫法缩小增量法。降低了直接插入排序的移动次数,是一种不稳定的排序,时间复杂度O(nlogn). * 基本思想就是把一个数组分为好几个数组,有点像分治法,不过这里的划分是用一个常量increment(增量)来控制。 * <h3>算法原理</h3> 算法先将要排序的一组数按某个增量increment(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(increment/2)对它进行分组, 在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。 * @param int[] arr * @return int[] resultArr * */ public int[] shellSort(int[] arr) { int len = arr.length; resultArr = arr; int j = 0; int temp = 0; for (int increment = len / 2; increment > 0; increment /= 2) { for (int i = increment; i < len; i++) { temp = resultArr[i]; for (j = i; j >= increment; j -= increment) { if(temp > resultArr[j - increment]){ resultArr[j] = resultArr[j - increment]; }else{ break; } } resultArr[j] = temp; } } return resultArr; } /** * 希尔排序方法2 * */ public int[] shellSort2(int[] arr){ int len = arr.length; resultArr = arr; int j, gap; for (gap = len / 2; gap > 0; gap /= 2) for (j = gap; j < len; j++)//从数组第gap个元素开始 if (resultArr[j] < resultArr[j - gap]){//每个元素与自己组内的数据进行直接插入排序 int temp = resultArr[j]; int k = j - gap; while (k >= 0 && resultArr[k] > temp){ resultArr[k+gap] = resultArr[k]; k -= gap; } resultArr[k + gap] = temp; } return resultArr; } /** * 希尔排序方法3 * */ public int[] shellSort3(int[] arr){ int len = arr.length; resultArr = arr; int i, j, gap; for (gap = len/2; gap>0; gap/=2) for (i = gap; i < len; i++) for (j = i-gap; j>=0 && resultArr[j] > resultArr[j+gap]; j -= gap){ int temp = resultArr[j]; resultArr[j] = resultArr[j+gap]; resultArr[j+gap] = temp; } return resultArr; }
private int[] resultArr; /** * 从小到大 * 快速排序 --是目前在实践中非常高效的一种排序算法,它不是稳定的排序算法,平均时间复杂度为O(nlogn),最差情况下复杂度为O(n^2)。 * <h3>算法原理</h3> 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序, 整个排序过程可以递归进行,以此达到整个数据变成有序序列。 * @param int[] arr * @return int[] resultArr * */ public int[] quickSort(int[] arr) { _quickSort(arr, 0 , arr.length - 1); return resultArr; } public void _quickSort(int[] arr, int left, int right) { resultArr = arr; if (left < right) { int i = left, j = right, target = resultArr[left]; while (i < j) { while (i < j && resultArr[j] > target) j--; if (i < j) resultArr[i++] = arr[j]; while (i < j && resultArr[i] < target) i++; if (i < j) resultArr[j] = resultArr[i]; } resultArr[i] = target; _quickSort(resultArr, left, i - 1); _quickSort(resultArr, i + 1, right); } }
private int[] resultArr; /** * 从小到大 * 归并排序 --是稳定的排序算法,其时间复杂度为O(nlogn),如果是使用链表的实现的话,空间复杂度可以达到O(1), * 但如果是使用数组来存储数据的话,在归并的过程中,需要临时空间来存储归并好的数据,所以空间复杂度为O(n)。 * <h3>算法原理</h3> 归并排序具体工作原理如下(假设序列共有n个元素): 1.将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素 2.将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素 3.重复步骤2,直到所有元素排序完毕 * @param int[] arr * @return int[] resultArr * */ public int[] mergeSort(int[] arr) { _mergeSort(arr, 0, arr.length - 1); return resultArr; } private void _mergeSort(int[] arr, int left, int right) { if (left >= right) return; // 找出中间索引 int center = (left + right) / 2;// 对左边数组进行递归 _mergeSort(arr, left, center);// 对右边数组进行递归 _mergeSort(arr, center + 1, right); // 合并 merge(arr, left, center, right); resultArr = arr; } /** * 将两个数组进行归并,归并前面2个数组已有序,归并后依然有序 * * @param data * 数组对象 * @param left * 左数组的第一个元素的索引 * @param center * 左数组的最后一个元素的索引,center+1是右数组第一个元素的索引 * @param right * 右数组最后一个元素的索引 */ private void merge(int[] arr, int left, int center, int right) { int[] tmpArr = new int[arr.length];// 临时数组 int mid = center + 1;// 右数组第一个元素索引 int third = left;// third 记录临时数组的索引 int tmp = left;// 缓存左数组第一个元素的索引 while (left <= center && mid <= right) { // 从两个数组中取出最小的放入临时数组 if (arr[left] <= arr[mid]) { tmpArr[third++] = arr[left++]; } else { tmpArr[third++] = arr[mid++]; } } // 剩余部分依次放入临时数组(实际上两个while只会执行其中一个) while (mid <= right) { tmpArr[third++] = arr[mid++]; } while (left <= center) { tmpArr[third++] = arr[left++]; } // 将临时数组中的内容拷贝回原数组中 // (原left-right范围的内容被复制回原数组) while (tmp <= right) { arr[tmp] = tmpArr[tmp++]; } }
private int[] resultArr; /** * 从小到大 * 堆排序 --堆排序的时间复杂度为O(nlogn)。是一树形选择排序。 * <h3>算法原理</h3> 1.先将初始数据R[1..n]建成一个最大堆,此堆为初始的无序区。 2.再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key。 3.由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。 4.重复2、3步骤,直到无序区只有一个元素为止。 * @param int[] arr * @return int[] resultArr * */ public int[] heapSort(int[] arr) { for (int i = 0; i < arr.length; i++) { createMaxdHeap(arr, arr.length - 1 - i); swap(arr, 0, arr.length - 1 - i); } resultArr = arr; return resultArr; } private void swap(int[] data, int i, int j) { if (i == j) { return; } data[i] = data[i] + data[j]; data[j] = data[i] - data[j]; data[i] = data[i] - data[j]; } private void createMaxdHeap(int[] data, int lastIndex) { for (int i = (lastIndex - 1) / 2; i >= 0; i--) { // 保存当前正在判断的节点 int k = i; // 若当前节点的子节点存在 while (2 * k + 1 <= lastIndex) { // biggerIndex总是记录较大节点的值,先赋值为当前判断节点的左子节点 int biggerIndex = 2 * k + 1; if (biggerIndex < lastIndex) { // 若右子节点存在,否则此时biggerIndex应该等于 lastIndex if (data[biggerIndex] < data[biggerIndex + 1]) { // 若右子节点值比左子节点值大,则biggerIndex记录的是右子节点的值 biggerIndex++; } } if (data[k] < data[biggerIndex]) { // 若当前节点值比子节点最大值小,则交换2者得值,交换后将biggerIndex值赋值给k swap(data, k, biggerIndex); k = biggerIndex; } else { break; } } } }
基数排序又被称为桶排序,基数排序首先将待排序数据元素依次“分配”到不同的桶里,然后再把各桶中的数据元素“收集”到一起。 通过使用对多关键字进行排序的这种“分配”和“收集”的方法, 基数排序实现了对多关键字进行排序。
private int[] resultArr; /** * 从小到大 * 基数排序 --基数排序又被称为桶排序,基数排序首先将待排序数据元素依次“分配”到不同的桶里, * 然后再把各桶中的数据元素“收集”到一起。 通过使用对多关键字进行排序的这种“分配”和“收集”的方法, * 基数排序实现了对多关键字进行排序。 * <h3>算法原理</h3> * 设待排序的数据元素关键字是m位d进制整数(不足m位的关键字在高位补0) * 设置d个桶,令其编号分别为0,1,2.... d-1 * 首先,按关键字最低位的数值一次把各数据元素放到相应的桶中 * 然后,按照桶号从小到大和进入桶中数据元素的先后次序收集分配在各桶中的数据元素, * 这样就形成了数据元素集合的一个新的排列,此为一次基数排序。重复m次,就得到了排好序 * 的数据元素序列 * 最高位优先(Most Significant Digit first)法,简称MSD法: 先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组, 之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。 最低位优先(Least Significant Digit first)法,简称LSD法: 先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列 * @param int[] arr * @return int[] resultArr * */ public int[] radixSort(int[] arr) { int d = getDistance(arr); _radixSort(arr, 10, d); return resultArr; } private int getDistance(int[] array) { int distance = 0; int num = getMax(array); if (num == 0) { return 0; } while (num/10 != 0) { ++distance; num /= 10; } return distance + 1; } private int getMax(int[] array) { int max = array[0]; for (int i = 1; i < array.length; i++) { if (max < array[i]) { max = array[i]; } } return max; } private void _radixSort(int[] array,int radix, int distance) { //array 为待排序数组 //radix 代表基数 //distance 代表排序元素的位数 int length = array.length; int[] temp = new int[length];//用于暂存元素 int[] count = new int[radix];//用于计数排序,存储0~9 int divide = 1; for (int i = 0; i < distance; i++) { System.arraycopy(array, 0, temp, 0, length); Arrays.fill(count, 0); for (int j = 0; j < length; j++) { int tempKey = (temp[j] / divide) % radix; count[tempKey]++; } for (int j = 1; j < radix; j++) { count[j] = count[j] + count[j - 1]; } for (int j = length - 1; j >= 0; j--) { int tempKey = (temp[j] / divide) % radix; count[tempKey]--; array[count[tempKey]] = temp[j]; } divide *= radix; } resultArr = array; }
计数排序就是计算相同key的元素各有多少个,然后根据出现的次数累加而获得最终的位置信息。 但是计数排序有两个限制条件,那就是存在一个正整数K,使得数组里面的所有元素的key值都不大于N,且key值都是非负整数。
private int[] resultArr; /** * 从小到大 * 计数排序 --计数排序就是计算相同key的元素各有多少个,然后根据出现的次数累加而获得最终的位置信息。 * 但是计数排序有两个限制条件,那就是存在一个正整数K,使得数组里面的所有元素的key值都不大于N,且key值都是非负整数。 * <h3>算法原理</h3> * 计数排序算法步骤: 建一个长度为K+1的的数组C,里面的每一个元素初始都置为0(Java里面默认就是0)。 遍历待排序的数组,计算其中的每一个元素出现的次数,比如一个key为i的元素出现了3次,那么C[i]=3。 累加C数组,获得元素的排位,从0开始遍历C, C[i+1]=C[i]+C[i-1] 建一个临时数组T,长度与待排序数组一样。从数组末尾遍历待排序数组,把元素都安排到T里面,直接从C里面就可以得到元素的具体位置, 不过记得每处理过一个元素之后都要把C里面对应位置的计数减1。 * @param int[] arr * @return int[] resultArr * */ public int[] countSort(int[] arr) { _countSort(arr, getMax(arr)); return resultArr; } private int getMax(int[] array) { int max = array[0]; for (int i = 1; i < array.length; i++) { if (max < array[i]) { max = array[i]; } } return max; } private void _countSort(int[] array, int range) { //range 最大元素 if (array.length <= 1) { return; } int[] countArray = new int[range + 1]; for (int i = 0; i < array.length; i++) { int value = array[i]; if (value < 0 || value > range) { return; } countArray[value] += 1; } for (int i = 1; i < countArray.length; i++) { countArray[i] += countArray[i - 1]; } int[] temp = new int[array.length]; for (int i = array.length - 1; i >= 0; i--) { int value = array[i]; int position = countArray[value] - 1; temp[position] = value; countArray[value] -= 1; } for (int i = 0; i < array.length; i++) { array[i] = temp[i]; } resultArr = array; }