> 文章列表 > 十大排序总结

十大排序总结

十大排序总结

十大排序

稳定性

十大排序总结

有一个数组,3,2,3,4,1

我们把第一个3标记为黑色

第二个3标记为红色

如果排序之后,黑色的3仍在红色3前边,我们就认为这个排序是稳定的

如果红色3在黑色3前面,我们就认为这个排序是不稳定的


插入排序

直接插入排序

想象斗地主时咋摸牌的,保证有序

import java.util.Arrays;public class InlineSorting {public static void main(String[] args) {int[] arr = {1,5,8,10,6};System.out.println("排序前的数组:");System.out.println(Arrays.toString(arr));InlineSorting(arr);System.out.println("排序后的数组:");System.out.println(Arrays.toString(arr));}static void InlineSorting(int[] arr){sort(arr);}static void sort(int[] arr){int len = arr.length;for(int i=1; i<len; i++){int tmp = arr[i];int j=i-1;for(; j>=0; j--){if (arr[j] > tmp){arr[j+1] = arr[j];}else {break;}}arr[j+1] = tmp;}}
}
排序前的数组:
[1, 5, 8, 10, 6]
排序后的数组:
[1, 5, 6, 8, 10]

时间复杂度:

最好: O(N)

最坏: O(N^2)

空间复杂度

O(1)

稳定性:

稳定排序

适用场景:

当原始数据有序的时候,直接就continue了,所以越有序的数据越快

希尔排序

也叫缩小增量算法,核心就是分组和组内直接插入排序

希尔排序为啥要跳着分组,不是相邻分组?

可以把较大元素,尽量放到后面去

package InsertSorting;import java.util.Arrays;public class ShellSort {public static void main(String[] args) {int[] arr = {1,5,8,10,6,9};System.out.println("排序前的数组:");System.out.println(Arrays.toString(arr));ShellSort(arr);System.out.println("排序后的数组:");System.out.println(Arrays.toString(arr));}static void ShellSort(int[] arr){int[] drr = {5,2,1};for (int i = 0; i < drr.length; i++) {sortByShell(arr,drr[i]);}}static void sortByShell(int[] arr,int gap){int len = arr.length;for(int i=gap; i<len; i++){int tmp = arr[i];int j=i-gap;if (tmp > arr[j]){//说明有序的,直接continuecontinue;}for(; j>=0; j-=gap){if (arr[j] > tmp){arr[j+gap] = arr[j];}else {break;}}arr[j+gap] = tmp;}}
}
排序前的数组:
[1, 5, 8, 10, 6, 9]
排序后的数组:
[1, 5, 6, 8, 9, 10]

希尔排序的代码,就是直接插入排序的代码-1,+1的部分,替换为-gap,+gap

时间复杂度:

O(N^1.3 ~ N^1.5)

空间复杂度

O(1)

稳定性:

不稳定

适用场景:

希尔排序是一种基于插入排序的排序算法,在对大规模数据进行排序时具有比较高的效率。它适合于以下场景:

  1. 数据规模较大:由于希尔排序采用分组的方式进行排序,可以减少元素之间的比较次数,因此在数据规模较大时能够有效地提高排序效率。
  2. 数据集分布较均匀:希尔排序的效率与数据集的分布情况有关。当数据集的分布较为均匀时,希尔排序的效率会更高。
  3. 内存限制较小:由于希尔排序是一种原地排序算法,不需要额外的内存空间来存储待排序的数据,因此可以节省内存资源。

总体而言,希尔排序适合处理大规模且分布较为均匀的数据集,且内存资源有限的场景。

选择排序

直接选择排序

package SelectSort;import java.util.Arrays;public class SelectSort {public static void main(String[] args) {int[] arr = {2,3,10,8,7};System.out.println("排序前的数组:");System.out.println(Arrays.toString(arr));SelectSort(arr);System.out.println("排序后的数组:");System.out.println(Arrays.toString(arr));}static void SelectSort(int[] arr){sort(arr);}static void sort(int[] arr){int length = arr.length;for (int i = 0; i < length-1; i++) {int minIndex = i;for (int j = i+1; j < length ; j++) {if (arr[j] < arr[minIndex]){minIndex = j;}}swap(arr,i,minIndex);}}static void swap(int[] arr,int i,int j){int tmp = arr[j];arr[j] = arr[i];arr[i] = tmp;}
}
排序前的数组:
[2, 3, 10, 8, 7]
排序后的数组:
[2, 3, 7, 8, 10]

时间复杂度:

O(N^2)

空间复杂度:

O(1)

稳定性:

适用场景:

选择排序是一种简单直观的排序算法,适用于小规模数据的排序。由于它的时间复杂度为O(n^2),因此不适合处理大量数据的排序任务。在实际应用场景中,选择排序可能被用于以下情况:

  1. 数据规模较小:当待排序的数据规模比较小时,选择排序可以快速完成排序任务,并且代码简单易懂。
  2. 数据分布不均匀:如果要排序的数据分布不均匀,如有大量重复元素或有序部分,那么选择排序的效率可能会比其他排序算法高一些。
  3. 内存限制:由于选择排序只需要一个额外空间来交换元素位置,所以在内存受限的环境下,选择排序可能是一个可行的排序算法选择。

总体来说,选择排序虽然简单,但是其性能在大规模数据排序中不佳。因此,在实际应用中,更多地使用高效的排序算法,例如快速排序、归并排序和堆排序等

堆排序

交换排序

冒泡排序

算法启蒙,没啥说的

package SwapSort;import java.util.Arrays;public class BubbleSort {public static void main(String[] args) {int[] arr = {2,3,10,8,7};System.out.println("排序前的数组:");System.out.println(Arrays.toString(arr));bubbleSort(arr);System.out.println("排序后的数组:");System.out.println(Arrays.toString(arr));}static void bubbleSort(int[] arr){for(int i=0; i< arr.length-1; i++){boolean flag = false;for(int j=0; j< arr.length-1-i; j++){if (arr[j] > arr[j+1]){swap(arr,j,j+1);flag = true;}}if (!flag){break;}}}static void swap(int[] arr,int i,int j){int tmp = arr[j];arr[j] = arr[i];arr[i] = tmp;}
}
排序前的数组:
[2, 3, 10, 8, 7]
排序后的数组:
[2, 3, 7, 8, 10]

快速排序(☆)

快排是Hoare于1962年提出的一种二叉树结构的交换排序方法

其基本思想为: 任取待排序元素序列中的某元素作为基准值

按照该排序码将待排序集合分割成两子序列

左子序列中所有元素均小于基准值

右子序列中所有元素均大于基准值

让后最左右子序列重复该过程

直到所有元素都排列在相应位置上为止

//递归形式
package SwapSort;import java.util.Arrays;public class QuickSort {public static void main(String[] args) {int[] arr = {6,8,3,9,11,33,22};System.out.println("排序前的数组:");System.out.println(Arrays.toString(arr));quickSort(arr);System.out.println("排序后的数组:");System.out.println(Arrays.toString(arr));}static void quickSort(int[] arr){quick(arr,0, arr.length-1);}private static void quick(int[] arr,int left, int right){if (left >= right){return;}int pivot = partition(arr,left,right);quick(arr,0,pivot-1);quick(arr,pivot+1,right);}private static int partition(int[] arr, int start, int end){int i = start;int key = arr[start];while (start < end){while (start < end && arr[end] >= key){end--;}while (start < end && arr[start] <= key){start++;}swap(arr,start,end);}swap(arr,start,i);return start;}static void swap(int[] arr,int i,int j){int tmp = arr[j];arr[j] = arr[i];arr[i] = tmp;}
}
排序前的数组:
[6, 8, 3, 9, 11, 33, 22]
排序后的数组:
[3, 6, 8, 9, 11, 22, 33]
//循环

时间复杂度:

O(N * logN)

空间复杂度:

O(1)

稳定性:

不稳定

适用场景:

快速排序是一种高效的排序算法,它的时间复杂度为O(N*logN),在大多数情况下都能够快速地将数据排序。由于其实现简单,同时可扩展性强,因此被广泛应用于各种场景。

快速排序适用于以下场景:

  1. 数据量较大:快速排序的时间复杂度为O(N*logN) ,比其他排序算法更适用于大规模的数据排序。
  2. 需要内部排序(即不需要使用外部存储器):快速排序只需要占用少量的内存空间,就可以完成排序操作,因此特别适用于对内存占用要求比较高的场景。
  3. 稳定性不是关键需求:快速排序采用分治思想,不保证相同元素的顺序不变,因此不适用于需要稳定排序的场景。
  4. 适用于随机数:快速排序的平均时间复杂度为O(nlogn),在数据随机分布的情况下可以发挥最佳性能。

总之,快速排序适用于大数据量、内部排序、不需要稳定性排序的场景,并且数据随机分布时效果最佳。

//快速排序非递归实现
static void quickSortNot(int[] arr){Stack<Integer> stack = new Stack<>();int left = 0;int right = arr.length-1;int pivot = partition(arr,left,right);if (pivot > left+1){//pivot左边元素大于一个stack.push(left);stack.push(pivot-1);}if (pivot < right -1){//pivot右边大于一个stack.push(pivot+1);stack.push(right);}while (!stack.isEmpty()){right = stack.pop();left = stack.pop();pivot = partition(arr,left,right);//找基准的函数if (pivot > left+1){//pivot左边元素大于一个stack.push(left);stack.push(pivot-1);}if (pivot < right -1){//pivot右边大于一个stack.push(pivot+1);stack.push(right);}}}
排序前的数组:
[6, 11, 55, 77, 88, 22, 33, 99, 10]
排序后的数组:
[6, 10, 11, 22, 33, 55, 77, 88, 99]

归并排序

归并排序是一种常见的基于分治策略的排序算法,其主要思想是将待排序序列拆分为若干子序列,对每个子序列进行排序,然后再将已经排好序的子序列合并成一个有序序列。具体步骤如下:

  1. 将待排序序列递归地拆分成两个子序列,直到每个子序列只剩下一个元素为止;
  2. 对每个子序列进行归并排序,将它们合并成一个有序序列,即将两个有序子序列合并成一个有序序列;
  3. 重复以上步骤,直到最终所有子序列都被合并成一个有序序列为止。

归并排序的时间复杂度为 O(nlogn),其中 n 为待排序序列的长度。空间复杂度为 O(n),因为在排序过程中需要申请额外的数组存储有序序列,但是这些数组会随着归并过程不断被释放。归并排序是一种稳定的排序算法,它保证相等的元素在排序前后的相对位置不变。

归并排序适用于任何情况下的排序,尤其是在对数据量较大的序列进行排序时,其效率较高。同时,由于归并排序是一种稳定的排序算法,因此在需要保留相等元素顺序的场合也很常用。

package MergeSort;import java.util.Arrays;public class MergeSort {public static void main(String[] args) {int[] arr = {2,3,10,8,7};System.out.println("排序前的数组:");System.out.println(Arrays.toString(arr));mergeSort(arr);System.out.println("排序后的数组:");System.out.println(Arrays.toString(arr));}static void mergeSort(int[] arr){mergeSortFunc(arr,0,arr.length-1);}static void mergeSortFunc(int[] arr, int left, int right){if (left >= right){return;}int mid = (left + right)/2;//1. 分解左边mergeSortFunc(arr,left,mid);//2. 分解右边mergeSortFunc(arr,mid+1,right);//3. 进行合并merge(arr,left,right,mid);}private static void merge(int[] arr, int start, int end,int midIndex){int[] tmpArr = new int[end-start+1];int k = 0;//tmpArr数组的下标int s1 = start;int e1 = midIndex;int s2 = midIndex+1;int e2 = end;while (s1 <= e1 && s2 <= e2){//两个归并段都有数据if (arr[s1] <= arr[s2]){tmpArr[k++] = arr[s1++];}else {tmpArr[k++] = arr[s2++];}}//把剩余的数据复制到数组while (s1 <= e1){tmpArr[k++] = arr[s1++];}while (s2 <= e2){tmpArr[k++] = arr[s2++];}//把排好序的数字拷贝回原数组for (int i=0; i<k; i++){arr[i+start] = tmpArr[i];}}
}
排序前的数组:
[2, 3, 10, 8, 7]
排序后的数组:
[2, 3, 7, 8, 10]
//非递归实现
public class MergeSort {public void sort(int[] arr) {if (arr == null || arr.length < 2) {return;}int n = arr.length;for (int i = 1; i < n; i = i << 1) {for (int j = 0; j < n - i; j += (i << 1)) {merge(arr, j, j + i - 1, Math.min(j + (i << 1) - 1, n - 1));}}}private void merge(int[] arr, int left, int mid, int right) {int[] temp = new int[right - left + 1];int i = left, j = mid + 1, k = 0;while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}while (i <= mid) {temp[k++] = arr[i++];}while (j <= right) {temp[k++] = arr[j++];}for (int p = 0; p < temp.length; p++) {arr[left + p] = temp[p];}}
}
在这个示例中, sort 方法使用循环而不是递归来对数组进行排序。 它首先从每个元素(单独形成一组)开始,并通过合并较小的有序子序列来构建更大的有序子序列。 在每次迭代中, i 递增一个倍数,并且 j 每次递增 i << 1(等于 i * 2)来计算待合并的子序列的范围。 然后,将两个子数组合并为一个有序数组的过程与递归实现相同。这种非递归的归并排序方法在实际应用中效率较高,尤其是在处理大型数据集时。

计数排序

在这个示例中, sort 方法使用循环而不是递归来对数组进行排序。 它首先从每个元素(单独形成一组)开始,并通过合并较小的有序子序列来构建更大的有序子序列。 在每次迭代中, i 递增一个倍数,并且 j 每次递增 i << 1(等于 i * 2)来计算待合并的子序列的范围。 然后,将两个子数组合并为一个有序数组的过程与递归实现相同。

这种非递归的归并排序方法在实际应用中效率较高,尤其是在处理大型数据集时。

public class CountingSort {public void sort(int[] arr) {if (arr == null || arr.length < 2) {return;}int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;for (int num : arr) {max = Math.max(max, num);min = Math.min(min, num);}int[] count = new int[max - min + 1];for (int num : arr) {count[num - min]++;}for (int i = 1; i < count.length; i++) {count[i] += count[i - 1];}int[] sorted = new int[arr.length];for (int i = arr.length - 1; i >= 0; i--) {sorted[--count[arr[i] - min]] = arr[i];}System.arraycopy(sorted, 0, arr, 0, arr.length);}
}
在这个示例中, sort 方法接收一个整数数组作为参数,并执行计数排序算法以对其进行排序。 它首先找出最大值和最小值,创建并初始化长度为 max - min + 1 的计数数组。之后遍历原始数组,将每个元素减去 min 作为下标,在计数数组对应的位置上计数。然后通过累加计数数组来确定有序序列中每个元素应该出现的位置。 最后,反向遍历原始数组,并根据其在计数数组中的值从有序序列的末尾开始将其放置到相应的位置上。

桶排序

桶排序是一种线性排序算法,适用于待排序元素在一个比较小的区间内。它将区间划分为若干个桶(bucket),对于每个桶收集区间内符合特定范围的元素,再对每个桶中的元素进行排序,最后遍历每个桶,按照顺序把所有元素依次放回原来的序列中。

具体实现过程如下:

  1. 找出待排序序列的最大值 max 和最小值 min
  2. 创建若干个桶,并确定一个映射函数,将待排序元素映射到对应的桶中;
  3. 遍历待排序序列,并根据其值应该被放置到哪个桶中,将其放入相应的桶中;
  4. 对每个桶中的元素进行排序,可以使用任何一种算法;
  5. 遍历每个桶,按照顺序将其中的元素复制回原序列中;
  6. 结束排序。

由于排序过程涉及到创建和维护多个桶以及对每个桶中的元素进行排序,因此桶排序的时间复杂度通常为 O(n+k),其中 n 为待排序序列的长度,k 是桶的数量。空间复杂度也很高,为 O(n+k)。

以下是Java代码实现桶排序的示例:

public class BucketSort {public void sort(double[] arr) {if (arr == null || arr.length < 2) {return;}double max = Double.NEGATIVE_INFINITY, min = Double.POSITIVE_INFINITY;for (double num : arr) {max = Math.max(max, num);min = Math.min(min, num);}int bucketNum = (int) Math.ceil((max - min) / arr.length) + 1;List<List<Double>> buckets = new ArrayList<>(bucketNum);for (int i = 0; i < bucketNum; i++) {buckets.add(new ArrayList<>());}for (double num : arr) {int index = (int) ((num - min) / arr.length * (bucketNum - 1));buckets.get(index).add(num);}for (List<Double> bucket : buckets) {Collections.sort(bucket);}int index = 0;for (List<Double> bucket : buckets) {for (double num : bucket) {arr[index++] = num;}}}
}
在这个示例中, sort 方法接收一个双精度浮点数组作为参数,并执行桶排序算法以对其进行排序。 它首先找出最大值和最小值,然后根据 arr.length 计算桶数量,并创建相应数量的桶。然后遍历待排序序列,将每个元素映射到相应的桶中。 每个桶都可以使用标准库中的任何排序算法进行排序,但在这个示例中使用了 Collections.sort() 方法。 最后,按照顺序遍历所有桶并将其中的元素复制回原始序列中。

桶排序最适用的情况是待排序元素在一个确定的区间内且分布越均匀越好,其时间复杂度主要受到桶的数量影响。

这种算法通常用于解决一些计数问题,如成绩统计、年龄分布等。例如,如果有一份成绩单,里面包含了许多学生的分数,而分数的范围是 0 到 100 分。现在要计算每种分数出现的次数,可以使用桶排序算法:将 0 到 100 分的范围划分为 101 个区间,每个区间作为一个桶,并将分数根据其所属的区间放入相应的桶中,然后遍历每个桶,统计其中元素的数量即可。

桶排序还可以与其他排序算法结合使用以提高效率。例如,在一个整数数组中,如果待排序元素在一个较小的范围内(例如 0-65535),可以使用桶排序按照元素值进行排序; 如果待排序元素的范围比较大,则可以使用基数排序(radix sort)先对元素的各个位数进行排序,然后再使用桶排序。

基数排序

基数排序(Radix Sort)是一种非比较排序算法,适用于待排序元素的位数固定的情况。

基数排序的主要思想是:将待排序元素按照其位数从低到高依次进行排序。具体实现过程如下:

  1. 找出待排序序列中最大的数字,并确定其位数 d
  2. 对个位数字进行排序,根据排序结果得到一个新的序列;
  3. 对十位数字进行排序,再次根据排序结果得到一个新的序列;
  4. 重复上述过程,直到对最高位数字进行排序完成为止;
  5. 结束排序。

在每次排序时,可以使用任何一种稳定的排序算法,如计数排序、桶排序等。对于位数小于 d 的数字,可以在前面填充零以与其他数字位数保持一致。当然,也可以从高位到低位排序,这取决于具体实现。

基数排序的时间复杂度为 O(d*(n+k)),其中 d 是待排序元素的最大位数,n 是待排序序列长度,k 是基数,即数据范围。空间复杂度为 O(n+k)。

以下是Java代码实现基数排序的示例:

public class RadixSort {public void sort(int[] arr) {if (arr == null || arr.length < 2) {return;}int max = Integer.MIN_VALUE;for (int num : arr) {max = Math.max(max, num);}int maxLength = String.valueOf(max).length();int mod = 10, div = 1;List<List<Integer>> buckets = new ArrayList<>(mod);for (int i = 0; i < mod; i++) {buckets.add(new ArrayList<>());}for (int i = 0; i < maxLength; i++, mod *= 10, div *= 10) {for (int num : arr) {buckets.get((num % mod) / div).add(num);}int index = 0;for (List<Integer> bucket : buckets) {for (int num : bucket) {arr[index++] = num;}bucket.clear();}}}
}
在这个示例中, sort 方法接收一个整数数组作为参数,并执行基数排序算法以对其进行排序。 它首先找出最大值,并确定位数,然后创建相应数量的桶。然后通过循环按照从低到高的顺序依次比较每个数字的各个位上的数值,将其分配到相应的桶中,从桶中取出结果并复制回原数组中。循环结束后,原始数组已经按照指定的列数排序完成。

在互联网领域,使用最广泛的排序算法如下:

  1. 快速排序(Quick Sort):由于其时间复杂度为 O(n*log n) 和高效性,在互联网领域被广泛应用于海量数据的排序和查找。比如常见的搜索引擎中,需要对海量的网页进行排序,快速排序是一个比较好的选择。
  2. 堆排序(Heap Sort):在互联网领域,堆排序主要用于大数据集上的排序。例如,在分布式系统中,需要对每台机器收集的数据进行排序,这时可以使用 MapReduce 模型进行处理,并用堆排序对结果进行排序。
  3. 归并排序(Merge Sort):由于其稳定性和可靠性,在互联网领域被广泛应用于外部排序和文件合并等场景。外部排序指需要对超过内存容量的文件进行排序,归并排序可以通过拆分文件、读取一部分数据到内存中等方式实现对大文件的排序。

总之,对于互联网领域的排序需求,选用不同的排序算法主要考虑数据规模、排序稳定性、算法效率等方面的因素。