冒泡排序(Bubble Sort)是一种简单的比较排序算法,它的基本思想是重复地交换相邻的两个元素,直到整个数组都是有序的。冒泡排序是一种稳定排序算法,因为它不会改变相等元素的相对顺序。
冒泡排序的基本步骤如下:
虽然冒泡排序是一种简单的排序算法,但它的时间复杂度较高,为O(n^2),其中n是待排序元素的数量。因此,它对于大规模数据集不太高效。冒泡排序通常用于小规模数据排序。
public class BubbleSort {
public static void sort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
boolean swapped = false;
for (int j = 0; j < nums.length - i - 1; j++) {
if (nums[j] > nums[j + 1]) {
int t = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = t;
swapped = true;
}
}
if (!swapped) break;
}
}
}
选择排序(Selection Sort)是一种简单的排序算法,它的基本思想是将数组分成已排序和未排序两部分,每次从未排序部分选择最小(或最大)的元素,然后将其放到已排序部分的末尾。选择排序不像冒泡排序那样每次都交换元素,而是只在最后确定了最小元素的位置后进行交换。选择排序也是一种不稳定的排序算法,因为它可能改变相等元素的相对顺序。
以下是选择排序的基本步骤:
尽管选择排序在最坏情况下和平均情况下的时间复杂度都是O(n^2),但它不需要额外的存储空间,因此在某些情况下可能是一个有用的排序算法,特别是当内存有限时。
public class SelectionSort {
public static void sort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
int minIndex = i;
for (int j = i + 1; j < nums.length; j++) {
if (nums[minIndex] > nums[j]) minIndex = j;
}
int t = nums[minIndex];
nums[minIndex] = nums[i];
nums[i] = t;
}
}
}
插入排序(Insertion Sort)是一种简单而有效的排序算法。它的基本思想是将数组分成已排序和未排序两部分,从未排序部分选择一个元素,插入到已排序部分的合适位置,重复这个过程,直到整个数组都有序。插入排序是一种稳定排序算法,因为它不会改变相等元素的相对顺序。
以下是插入排序的基本步骤:
插入排序在平均和最坏情况下的时间复杂度都是O(n^2),其中n是待排序元素的数量。然而,在某些情况下,插入排序可能比其他排序算法更加高效,尤其是当待排序的数据集已经基本有序时。
public class InsertionSort {
public static void sort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int t = nums[i];
int j = i - 1;
while (j >= 0 && nums[j] > t) {
nums[j + 1] = nums[j--];
}
nums[j + 1] = t;
}
}
}
希尔排序(Shell Sort)是一种改进的插入排序算法,也称为缩小增量排序。它的基本思想是将整个数组分成多个子序列,对每个子序列进行插入排序,然后逐渐减小子序列的长度,最终使整个数组有序。希尔排序的关键在于选择合适的间隔序列,称为增量序列,以确定子序列的长度。
希尔排序的主要步骤如下:
希尔排序的关键在于增量序列的选择,不同的增量序列可能导致不同的性能。希尔排序的时间复杂度取决于增量序列的选择,但通常在O(n2)和O(n1.25)之间。希尔排序的优点是可以在不使用额外内存的情况下进行排序,因此适用于对内存限制的情况。
public class ShellSort {
public static void sort(int[] nums) {
shellSort(nums);
}
public static void shellSort(int[] nums) {
int gap = nums.length / 2;
while (gap > 0) {
for (int i = gap; i < nums.length; i++) {
int t = nums[i];
int j = i - gap;
while (j >= 0 && nums[j] > t) {
nums[j + gap] = nums[j];
j -= gap;
}
nums[j + gap] = t;
}
gap >>= 1;
}
}
}
归并排序(Merge Sort)是一种经典的分治算法,它的核心思想是将一个大的问题分解成多个小的子问题,解决每个子问题,然后将它们合并起来以得到最终的解决方案。归并排序的主要步骤包括分解、解决和合并。
以下是归并排序的详细步骤:
归并排序是一种稳定的排序算法,它的时间复杂度为O(n log n),其中n是数组的大小。这使得归并排序在大多数情况下都比较高效,特别是对于大型数据集。
public class MergeSort {
public static void sort(int[] nums) {
mergeSort(nums, 0, nums.length - 1);
}
public static void mergeSort(int[] nums, int left, int right) {
if (left < right) {
int middle = left + (right - left) / 2;
mergeSort(nums, left, middle);
mergeSort(nums, middle + 1, right);
merge(nums, left, middle, right);
}
}
public static void merge(int[] nums, int left, int middle, int right) {
int[] leftNums = new int[middle - left + 1];
int[] rightNums = new int[right - middle];
int l = 0;
int r = 0;
for (int i = left; i <= right; i++) {
if (i <= middle) leftNums[l++] = nums[i];
else rightNums[r++] = nums[i];
}
l = 0;
r = 0;
int p = left;
while (l < leftNums.length || r < rightNums.length) {
if (l == leftNums.length) nums[p++] = rightNums[r++];
else if (r == rightNums.length) nums[p++] = leftNums[l++];
else if (leftNums[l] <= rightNums[r]) nums[p++] = leftNums[l++];
else nums[p++] = rightNums[r++];
}
}
}
快速排序(Quick Sort)是一种常用的排序算法,它也是一种分治算法。它的核心思想是选择一个基准元素(通常是数组的第一个元素),将数组分成两个子数组,其中一个子数组的元素都比基准元素小,另一个子数组的元素都比基准元素大,然后对这两个子数组递归地进行快速排序。
以下是快速排序的主要步骤:
快速排序是一种高效的排序算法,平均情况下的时间复杂度为O(n log n),最坏情况下为O(n^2),但最坏情况很少发生。它通常比其他O(n log n)复杂度的排序算法快,因为它在内部排序中是原地排序(不需要额外的内存),而且适用于大型数据集。
public class QuickSort {
public static void sort(int[] nums) {
quickSort(nums, 0, nums.length - 1);
}
public static void quickSort(int[] nums, int start, int end) {
if (start >= end) return;
int pivot = nums[start];
int left = start + 1;
int right = end;
while (true) {
while (left <= right && nums[left] <= pivot) left++;
while (left <= right && nums[right] >= pivot) right--;
if (left <= right) swap(nums, left, right);
else break;
}
swap(nums, start, right);
quickSort(nums, start, right - 1);
quickSort(nums, right + 1, end);
}
public static void swap(int[] nums, int x, int y) {
int t = nums[x];
nums[x] = nums[y];
nums[y] = t;
}
}
堆排序(Heap Sort)是一种基于二叉堆数据结构的排序算法。它利用了堆的性质来进行排序,其中堆可以分为最大堆和最小堆。堆排序通常使用最大堆来进行升序排序。
堆排序的主要思想是将待排序的数组构建成一个二叉堆(最大堆),然后不断将堆顶元素(最大元素)与堆的最后一个元素交换,然后将堆的大小减一,再对剩余的元素进行堆的调整,使其满足最大堆的性质,重复这个过程直到整个数组排序完成。
以下是堆排序的主要步骤:
堆排序的时间复杂度为 O(n log n),并且是一种原地排序算法,因为它不需要额外的存储空间。
public class HeapSort {
public static void sort(int[] nums) {
heapSort(nums);
}
public static void heapSort(int[] nums) {
for (int i = nums.length / 2 - 1; i >= 0; i--) {
heapify(nums, nums.length, i);
}
for (int i = nums.length - 1; i >= 0; i--) {
int t = nums[0];
nums[0] = nums[i];
nums[i] = t;
heapify(nums, i, 0);
}
}
public static void heapify(int[] nums, int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && nums[left] > nums[largest]) largest = left;
if (right < n && nums[right] > nums[largest]) largest = right;
if (largest != i) {
int t = nums[i];
nums[i] = nums[largest];
nums[largest] = t;
heapify(nums, n, largest);
}
}
}
桶排序(Bucket Sort)是一种分布式排序算法,它通过将待排序元素分散到多个桶中,然后分别对每个桶中的元素进行排序,最后将所有桶中的元素合并成有序序列。桶排序适用于输入数据均匀分布在某个范围内的情况,例如浮点数排序。
下面是桶排序的基本思想和步骤:
public class BucketSort {
public static void sort(int[] nums) {
bucketSort(nums, nums.length);
}
public static void bucketSort(int[] nums, int numBuckets) {
if (nums == null || nums.length <= 1) return;
int max = nums[0];
int min = nums[0];
for (int num : nums) {
if (num > max) {
max = num;
}
if (num < min) {
min = num;
}
}
int bucketSize = (max - min + 1) / numBuckets + 1;
List<Integer>[] buckets = new ArrayList[numBuckets];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<>();
}
for (int num : nums) {
buckets[(num - min) / bucketSize].add(num);
}
int p = 0;
for (List<Integer> bucket : buckets) {
Collections.sort(bucket);
for (Integer value : bucket) {
nums[p++] = value;
}
}
}
}
基数排序(Radix Sort)是一种非比较性的整数排序算法,它通过将待排序的整数按照位数进行排序。基数排序的核心思想是从最低位(个位数)开始,按照位数的顺序依次进行排序,直到最高位(最高位数)。每次排序都是稳定的计数排序或桶排序,保持了元素的相对顺序。这样,多次排序后,最终得到的结果就是整体有序的。
下面是基数排序的基本步骤:
public class RadixSort {
public static void sort(int[] nums) {
radixSort(nums);
}
public static void radixSort(int[] nums) {
int max = Arrays.stream(nums).max().getAsInt();
int exp = 1;
int[] bucket = new int[nums.length];
while (max / exp > 0) {
int[] count = new int[10];
for (int num : nums) {
count[(num / exp) % 10]++;
}
for (int i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
for (int i = nums.length - 1; i >= 0; i--) {
bucket[--count[(nums[i] / exp) % 10]] = nums[i];
}
System.arraycopy(bucket, 0, nums, 0, nums.length);
exp *= 10;
}
}
}
计数排序(Counting Sort)是一种非比较性的整数排序算法,它通过统计待排序整数中每个整数出现的次数,然后按照整数的值顺序输出排好序的结果。计数排序适用于整数排序,特别是当待排序的整数范围相对较小时(不过这个范围不能太大,否则需要大量的内存来存储计数数组)。
以下是计数排序的基本思想和步骤:
max - min + 1
的计数数组,用于存储每个整数出现的次数。数组的索引对应于整数的值,数组的值表示该整数出现的次数。计数排序的时间复杂度是 O(n+k),其中 n 是待排序整数的个数,k 是整数范围。在整数范围不大的情况下,计数排序可以非常高效。
public class CountingSort {
public static void sort(int[] nums) {
countingSort(nums);
}
public static void countingSort(int[] nums) {
if (nums == null || nums.length <= 1) return;
int max = nums[0];
int min = nums[0];
for (int num : nums) {
if (num > max) {
max = num;
}
if (num < min) {
min = num;
}
}
int k = max - min + 1;
int[] countArr = new int[k];
for (int num : nums) {
countArr[num - min]++;
}
for (int i = 1; i < countArr.length; i++) {
countArr[i] += countArr[i - 1];
}
int[] temp = new int[k];
for (int i = nums.length - 1; i >= 0; i--) {
temp[--countArr[nums[i] - min]] = nums[i];
}
System.arraycopy(temp, 0, nums, 0, nums.length);
}
}
上面排序算法可以使用下面代码进行验证
public class SortTest {
public static void test(Class aclass, Method method, String name) {
int count = 1000;
while (count-- > 0) {
int[] nums1 = new int[1000];
for (int i = 0; i < nums1.length; i++) {
nums1[i] = (int) (Math.random() * 10000);
}
int[] nums2 = Arrays.copyOf(nums1, nums1.length);
Arrays.sort(nums1);
try {
method.invoke(aclass, nums2);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
boolean flag = Arrays.equals(nums1, nums2);
if (!flag) {
System.out.println("=========================================");
System.out.println(name + "排序错误");
System.out.println(Arrays.toString(nums1));
System.out.println(Arrays.toString(nums2));
System.out.println("=========================================");
return;
}
}
System.out.println(name + "OK");
}
public static void main(String[] args) throws NoSuchMethodException {
test(QuickSort.class, QuickSort.class.getMethod("sort", int[].class), "快速排序");
test(SelectionSort.class, SelectionSort.class.getMethod("sort", int[].class), "选择排序");
test(BubbleSort.class, BubbleSort.class.getMethod("sort", int[].class), "冒泡排序");
test(InsertionSort.class, InsertionSort.class.getMethod("sort", int[].class), "插入排序");
test(MergeSort.class, MergeSort.class.getMethod("sort", int[].class), "归并排序");
test(ShellSort.class, ShellSort.class.getMethod("sort", int[].class), "希尔排序");
test(HeapSort.class, HeapSort.class.getMethod("sort", int[].class), "堆排序");
test(BucketSort.class, BucketSort.class.getMethod("sort", int[].class), "桶排序");
test(CountingSort.class, CountingSort.class.getMethod("sort", int[].class), "计数排序");
test(RadixSort.class, RadixSort.class.getMethod("sort", int[].class), "基数排序");
}
}
上面代码会随机生成1000个[0,9999]的数,然后进行排序,重复1000次来验证代码的正确性,上面代码输入如下
以下是十大常见排序算法的平均时间复杂度、最坏时间复杂度、最好时间复杂度、空间复杂度以及是否稳定的表格展示:
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 是否稳定排序 |
---|---|---|---|---|---|
冒泡排序 (Bubble Sort) | O(n^2) | O(n^2) | O(n) | O(1) | 是 |
选择排序 (Selection Sort) | O(n^2) | O(n^2) | O(n^2) | O(1) | 否 |
插入排序 (Insertion Sort) | O(n^2) | O(n^2) | O(n) | O(1) | 是 |
希尔排序 (Shell Sort) | O(n1.3-2) | O(n^2) | O(n log n) | O(1) | 否 |
归并排序 (Merge Sort) | O(n log n) | O(n log n) | O(n log n) | O(n) | 是 |
快速排序 (Quick Sort) | O(n log n) | O(n^2) | O(n log n) | O(log n) | 否 |
堆排序 (Heap Sort) | O(n log n) | O(n log n) | O(n log n) | O(1) | 否 |
计数排序 (Counting Sort) | O(n + k) | O(n + k) | O(n + k) | O(k) | 是 |
桶排序 (Bucket Sort) | O(n + k) | O(n^2) | O(n) | O(n + k) | 是 |
基数排序 (Radix Sort) | O(n * k) | O(n * k) | O(n * k) | O(n + k) | 是 |
说明: