Java常用的排序算法有以下几种:
冒泡排序(Bubble Sort)
选择排序(Selection Sort)
插入排序(Insertion Sort)
希尔排序(Shell Sort)
归并排序(Merge Sort)
快速排序(Quick Sort)
堆排序(Heap Sort)
计数排序(Counting Sort)
桶排序(Bucket Sort)
基数排序(Radix Sort)
这些排序算法都有各自的优缺点,应根据具体情况选择适合的算法。
算法复杂度
排序算法
时间复杂度(平均)
时间复杂度(最坏)
时间复杂度(最好)
空间复杂度
稳定性
冒泡排序
O(n^2)
O(n^2)
O(n)
O(1)
稳定
选择排序
O(n^2)
O(n^2)
O(n^2)
O(1)
不稳定
插入排序
O(n^2)
O(n^2)
O(n)
O(1)
稳定
希尔排序
O(n log n)
O(n^2)
O(n)
O(1)
不稳定
归并排序
O(n log n)
O(n log n)
O(n log n)
O(n)
稳定
快速排序
O(n log n)
O(n^2)
O(n log n)
O(log n)
不稳定
堆排序
O(n log n)
O(n log n)
O(n log n)
O(1)
不稳定
计数排序
O(n+k)
O(n+k)
O(n+k)
O(k)
稳定
桶排序
O(n^2)
O(n^2)
O(n)
O(n+k)
稳定
基数排序
O(n*k)
O(n*k)
O(n*k)
O(n+k)
稳定
其中,n表示输入元素的数量,k表示元素的取值范围大小。
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机 内执行时所需存储空间的度量,它也是数据规模n的函数。
1. 冒泡排序 (Bubble Sort) 冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就交换位置。这个过程持续对数列的末尾进行,直到整个数列都排序完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import org.junit.jupiter.api.Assertions;import java.util.Arrays;public class Bubble { public static void bubbleSort (int [] arr) { int n = arr.length; for (int i = 0 ; i < n - 1 ; i++) { for (int j = 0 ; j < n - i - 1 ; j++) { if (arr[j] > arr[j + 1 ]) { int temp = arr[j]; arr[j] = arr[j + 1 ]; arr[j + 1 ] = temp; } } } } public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 3 , 1 , 6 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 }; Bubble.bubbleSort(arr); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } }
在上面的代码中, bubble[Sort 函数](https://so.csdn.net/so/search?q=Sort 函数&spm=1001.2101.3001.7020)接受一个整数数组作为输入,并使用冒泡排序算法对其进行排序。外部循环运行 n - 1 次,其中 n 是数组的长度,内部循环运行 n - i - 1 次,其中 i 是外部循环的当前迭代次数。这是因为在每次外部循环迭代后,最大的元素肯定在数组的末尾,因此我们不需要再次比较它。
在内部循环中,我们比较相邻的元素并交换它们,如果它们的顺序不正确。这样,最大的元素就“冒泡”到数组的末尾。在每次遍历数组后,最大的元素都处于它的最终位置,因此我们可以将内部循环的大小减少1。
冒泡排序的时间复杂度为O(n^2),这使得它在大型列表和实际应用中效率低下。但是,由于其简单性,它是向初学者教授排序的好算法。
2. 选择排序(Selection Sort) 选择排序是一种简单的排序算法,它的基本思想是每次从待排序的元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的元素排完。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import org.junit.jupiter.api.Assertions;import java.util.Arrays;public class Selection { public static void selectionSort (int [] arr) { int n = arr.length; for (int i = 0 ; i < n - 1 ; i++) { int minIndex = i; for (int j = i + 1 ; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 3 , 1 , 6 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 }; Selection.selectionSort(arr); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } }
在上面的代码中, selectionSort 函数接受一个整数数组作为输入,并使用选择排序算法对其进行排序。外部循环运行 n - 1 次,其中 n 是数组的长度。在内部循环中,我们找到未排序部分中的最小元素,并将其索引存储在 minIndex 变量中。然后,我们将最小元素与已排序部分的末尾交换。在每次外部循环迭代后,已排序部分的长度增加1,未排序部分的长度减少1。
选择排序的时间复杂度为O(n^2),这使得它在大型列表和实际应用中效率低下。但是,由于其简单性,它是向初学者教授排序的好算法。
3. 插入排序(Insertion Sort) 插入排序是一种简单的排序算法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import org.junit.jupiter.api.Assertions;import java.util.Arrays;public class Insertion { public static void insertionSort (int [] arr) { int n = arr.length; for (int i = 1 ; i < n; i++) { int key = arr[i]; int j = i - 1 ; while (j >= 0 && arr[j] > key) { arr[j + 1 ] = arr[j]; j--; } arr[j + 1 ] = key; } } public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 3 , 1 , 6 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 }; Insertion.insertionSort(arr); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } }
在上面的代码中, insertionSort 函数接受一个整数数组作为输入,并使用插入排序算法对其进行排序。外部循环从第二个元素开始,因为我们将第一个元素视为已排序部分。在内部循环中,我们将要插入的元素与已排序部分的元素进行比较,如果要插入的元素小于已排序部分的元素,则将已排序部分的元素向右移动一位,以便为要插入的元素腾出空间。在内部循环结束后,我们将要插入的元素插入到正确的位置。在每次外部循环迭代后,我们可以确保前i个元素已经被排序。
插入排序的时间复杂度为O(n^2),这使得它在大型列表和实际应用中效率低下。但是,插入排序的实现非常简单,它在小型列表上的性能非常好。
4. 希尔排序(Shell Sort) 希尔排序是一种改进的插入排序算法,它的基本思想是将待排序的数组按照一定的间隔进行分组,对每组使用插入排序算法进行排序,然后缩小间隔,再对分组进行排序,直到间隔为1为止。
逐渐减小间隔大小的方法有助于提高排序过程的效率,可以减少比较和交换的次数。这是希尔排序算法的一个关键特点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import org.junit.jupiter.api.Assertions;import java.util.Arrays;public class Shell { public static void shellSort (int [] arr) { int n = arr.length; for (int gap = n / 2 ; gap > 0 ; gap /= 2 ) { for (int i = gap; i < n; i++) { int temp = arr[i]; int j = i; while (j >= gap && arr[j - gap] > temp) { arr[j] = arr[j - gap]; j -= gap; } arr[j] = temp; } } } public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 3 , 1 , 6 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 }; Shell.shellSort(arr); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } }
在上面的代码中, shellSort 函数接受一个整数数组作为输入,并使用希尔排序算法对其进行排序。外部循环使用一个间隔变量 gap ,初始值为数组长度的一半,每次循环将 gap 除以2,直到 gap 为1。内部循环从第 gap 个元素开始,将要插入的元素与已排序部分的元素进行比较,如果要插入的元素小于已排序部分的元素,则将已排序部分的元素向右移动 gap 个位置,以便为要插入的元素腾出空间。在内部循环结束后,我们将要插入的元素插入到正确的位置。在每次外部循环迭代后,我们可以确保数组的前 gap 个元素已经被排序。
希尔排序的时间复杂度为O(n^2),但实际上它的性能比插入排序要好得多,特别是在大型列表上。希尔排序的性能取决于间隔序列的选择,但是目前还没有一种最优的间隔序列。
5. 归并排序(Merge Sort) 归并排序是一种分治思想的排序算法,它的基本思想是将待排序的数组分成若干个子序列,每个子序列都是有序的,然后再将子序列合并成一个有序的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import org.junit.jupiter.api.Assertions;import java.util.Arrays;public class Merge { public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 3 , 1 , 6 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 }; Merge.mergeSort(arr, 0 , arr.length - 1 ); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } public static void mergeSort (int [] arr, int left, int right) { if (left < right) { int mid = (left + right) / 2 ; mergeSort(arr, left, mid); mergeSort(arr, mid + 1 , right); merge(arr, left, mid, right); } } public static void merge (int [] arr, int left, int mid, int right) { int n1 = mid - left + 1 ; int n2 = right - mid; int [] L = new int [n1]; int [] R = new int [n2]; for (int i = 0 ; i < n1; i++) { L[i] = arr[left + i]; } for (int j = 0 ; j < n2; j++) { R[j] = arr[mid + 1 + j]; } int i = 0 , j = 0 , k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } }
在上面的代码中, mergeSort 函数接受一个整数数组、一个左索引和一个右索引作为输入,并使用归并排序算法对指定范围内的数组元素进行排序。该函数使用递归将数组分成两个子数组,然后对它们进行排序,并最后将它们合并成一个有序数组。 merge 函数用于将两个有序数组合并成一个有序数组。它创建两个临时数组 L 和 R ,将左子数组的元素存储在 L 中,将右子数组的元素存储在 R 中,然后将它们合并成一个有序数组并存储在原始数组中。
归并排序的时间复杂度为O(nlogn),它的性能比冒泡排序和插入排序要好得多,特别是在大型列表上。
6. 快速排序(Quick Sort) 快速排序是一种分治思想的排序算法,它的基本思想是通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后再分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import org.junit.jupiter.api.Assertions;import java.util.Arrays;public class Quick { public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 6 , 1 , 3 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 }; Quick.quickSort(arr, 0 , arr.length - 1 ); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } public static void quickSort (int [] arr, int low, int high) { if (low < high) { int pivot = partition(arr, low, high); quickSort(arr, low, pivot - 1 ); quickSort(arr, pivot + 1 , high); } } private static int partition (int [] arr, int low, int high) { int pivot = arr[high]; int i = low - 1 ; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; swap(arr, i, j); } } swap(arr, i + 1 , high); return i + 1 ; } private static void swap (int [] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
在上面的代码中, quickSort 函数接受一个整数数组、一个低索引和一个高索引作为输入,并使用快速排序算法对指定范围内的数组元素进行排序。该函数使用递归将数组分成两个子数组,然后对它们进行排序,并最后将它们合并成一个有序数组。 partition 函数用于将数组分成两个子数组。它选择数组中的最后一个元素作为基准元素,然后将小于基准元素的元素放在左边,将大于基准元素的元素放在右边,并返回基准元素的索引。 swap 函数用于交换数组中的两个元素。
快速排序的时间复杂度为O(nlogn),它的性能比冒泡排序和插入排序要好得多,特别是在大型列表上。
7. 堆排序(Heap Sort) 堆排序是一种树形选择排序算法,它的基本思想是将待排序的数组构建成一个大根堆(或小根堆),然后将堆顶元素与堆底元素交换位置,再将剩余元素重新构建成堆,重复执行交换和重构堆的操作,直到整个数组有序。
堆排序是一种基于堆数据结构的排序算法,它的时间复杂度为O(nlogn)。
7.1.堆的概念 集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1(左节点) 且 Ki<=K2i+2(右节点),则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。完全二叉树(除了最后一层以外上面的节点但是非空的,最后一层节点是从左到右依次排布的)
7.2.堆的性质 1.堆中某个节点的值总是不大于或不小于其父节点的值; 2.堆总是一棵完全二叉树。
7.3.完全二叉树 完全二叉树的特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。需要注意的是,满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树。
完全二叉树的第 i 层至多有 2^i - 1 个结点。
完全二叉树的第 i 层至少有 2^(i - 1) 个结点。
完全二叉树的叶子结点只出现在最底层和次底层。
完全二叉树每一层的结点个数都达到了最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class Heap { public static void heapSort (int [] arr) { int n = arr.length; for (int i = n / 2 - 1 ; i >= 0 ; i--) { heapify(arr, n, i); } for (int i = n - 1 ; i >= 0 ; i--) { swap(arr, 0 , i); heapify(arr, i, 0 ); } } private static void heapify (int [] arr, int heapSize, int i) { int largest = i; int left = 2 * i + 1 ; int right = 2 * i + 2 ; if (left < heapSize && arr[left] > arr[largest]) { largest = left; } if (right < heapSize && arr[right] > arr[largest]) { largest = right; } if (largest != i) { swap(arr, i, largest); heapify(arr, heapSize, largest); } } private static void swap (int [] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 3 , 12 , 35 , 57 , 1 , 6 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 , 12 , 35 , 57 }; Heap.heapSort(arr); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } }
在上面的代码中, heapSort 函数接受一个整数数组作为输入,并使用堆排序算法对该数组进行排序。该函数首先构建一个大根堆,然后依次取出堆顶元素,得到有序序列。 heapify 函数用于对一个节点进行堆化操作。它接受三个参数:待堆化的数组、数组的大小和要堆化的节点的索引。该函数首先找到左右子节点中的最大值,如果最大值不是根节点,则交换根节点与最大值节点,并递归地对最大值节点进行堆化。 swap 函数用于交换数组中的两个元素。
8. 计数排序(Counting Sort) 计数排序是一种非比较排序算法,它的基本思想是统计数组中每个元素出现的次数,然后根据元素出现的次数依次将元素放入有序的数组中。
计数排序时间复杂度为O(n+k),其中k为待排序的元素的最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import org.junit.jupiter.api.Assertions;import java.util.Arrays;public class Counting { public static void countingSort (int [] arr) { int n = arr.length; int max = getMax(arr); int [] count = new int [max + 1 ]; for (int i = 0 ; i < n; i++) { count[arr[i]]++; } for (int i = 1 ; i <= max; i++) { count[i] += count[i - 1 ]; } int [] sortedArr = new int [n]; for (int i = n - 1 ; i >= 0 ; i--) { int item = arr[i]; int itemPos = count[item]; sortedArr[itemPos - 1 ] = item; count[item]--; } System.arraycopy(sortedArr, 0 , arr, 0 , n); } private static int getMax (int [] arr) { int max = arr[0 ]; for (int i = 1 ; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } public static void main (String[] args) { int [] arr = {5 , 2 , 6 , 8 , 3 , 1 , 6 , 5 , 12 }; int [] expectedArr = {1 , 2 , 3 , 5 , 5 , 6 , 6 , 8 , 12 }; Counting.countingSort(arr); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } }
9. 桶排序(Bucket Sort) 桶排序是一种非比较排序算法,它的基本思想是将待排序的数组分到有限数量的桶里,然后对每个桶进行排序,最后依次将所有桶中的元素取出来,组成有序的数组。
桶排序的时间复杂度为O(n),其中n为待排序元素的个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import org.junit.jupiter.api.Assertions;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.List;public class Bucket { public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 3 , 12 , 35 , 57 , 1 , 6 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 , 12 , 35 , 57 }; Bucket.bucketSort(arr, 20 ); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } public static void bucketSort (int [] arr, int bucketSize) { if (arr.length == 0 ) { return ; } int minValue = arr[0 ]; int maxValue = arr[0 ]; for (int i = 1 ; i < arr.length; i++) { if (arr[i] < minValue) { minValue = arr[i]; } else if (arr[i] > maxValue) { maxValue = arr[i]; } } int bucketCount = (maxValue - minValue) / bucketSize + 1 ; List<List<Integer>> buckets = new ArrayList <>(bucketCount); for (int i = 0 ; i < bucketCount; i++) { buckets.add(new ArrayList <>()); } for (int i = 0 ; i < arr.length; i++) { int bucketIndex = (arr[i] - minValue) / bucketSize; buckets.get(bucketIndex).add(arr[i]); } int currentIndex = 0 ; for (int i = 0 ; i < bucketCount; i++) { List<Integer> bucket = buckets.get(i); Collections.sort(bucket); for (int j = 0 ; j < bucket.size(); j++) { arr[currentIndex++] = bucket.get(j); } } } }
在上面的代码中, bucketSort 函数接受一个整数数组和桶的大小作为输入,并使用桶排序算法对该数组进行排序。该函数首先找到输入数组中的最小值和最大值,并计算桶的个数。然后,该函数创建一个大小为桶的个数的桶列表,用于存储每个桶中的元素。接下来,该函数依次遍历输入数组,将每个元素放入相应的桶中。然后,该函数对每个桶中的元素进行排序,并将排序后的元素按顺序合并起来得到有序序列。
10. 基数排序(Radix Sort) 基数排序是一种非比较排序算法,它的基本思想是将待排序的数组按照位数(个位、十位、百位)进行划分,然后依次对每个位上的数字进行排序,最终得到有序的数组。
基数排序的时间复杂度为O(d(n+k)),其中d为最大元素的位数,n为待排序元素的个数,k为桶的个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class Radix { public static void radixSort (int [] arr) { if (arr.length == 0 ) { return ; } int maxNum = arr[0 ]; for (int i = 1 ; i < arr.length; i++) { if (arr[i] > maxNum) { maxNum = arr[i]; } } int maxDigit = 0 ; while (maxNum != 0 ) { maxNum /= 10 ; maxDigit++; } List<List<Integer>> buckets = new ArrayList <>(10 ); for (int i = 0 ; i < 10 ; i++) { buckets.add(new ArrayList <>()); } int mod = 10 ; int div = 1 ; for (int i = 0 ; i < maxDigit; i++, mod *= 10 , div *= 10 ) { for (int j = 0 ; j < arr.length; j++) { int bucketIndex = (arr[j] % mod) / div; buckets.get(bucketIndex).add(arr[j]); } int currentIndex = 0 ; for (int j = 0 ; j < 10 ; j++) { List<Integer> bucket = buckets.get(j); for (int k = 0 ; k < bucket.size(); k++) { arr[currentIndex++] = bucket.get(k); } bucket.clear(); } } } public static void main (String[] args) { int [] arr = {5 , 2 , 8 , 3 , 12 , 35 , 57 , 1 , 6 }; int [] expectedArr = {1 , 2 , 3 , 5 , 6 , 8 , 12 , 35 , 57 }; Radix.radixSort(arr); System.out.println("arr = " + Arrays.toString(arr)); Assertions.assertArrayEquals(expectedArr, arr); } }
在上面的代码中, radixSort 函数接受一个整数数组作为输入,并使用基数排序算法对该数组进行排序。该函数首先找到输入数组中的最大值,并计算最大值的位数。然后,该函数创建一个大小为10的桶列表,用于存储每个桶中的元素。接下来,该函数依次遍历输入数组的每一位,将每个元素放入相应的桶中。然后,该函数将每个桶中的元素按顺序合并起来得到有序序列。
参考