赞
踩
n表示输入元素的数量,k表示元素的取值范围大小。
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
排序算法 | 时间复杂度-最好 | 时间复杂度-最差 | 时间复杂度-平均 | 空间复杂度 | 稳定性 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
快速排序 | 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(n) | 稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 |
希尔排序 | O(n) | O(n^2) | O(n log n) | O(1) | 不稳定 |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
桶排序 | O(n) | O(n^2) | O(n^2) | O(n+k) | 稳定 |
基本思想:
通过构造一个有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
时间复杂度:
最好情况(输入数组已经是有序的):只需遍历一次数组即可完成排序,时间复杂度为 O(n)。
最坏情况(输入数组完全逆序):需要进行 n*(n-1)/2 次比较和最多同样次数的交换操作,时间复杂度为 O(n^2)。
平均情况:时间复杂度也为 O(n^2),因为平均情况下,插入排序需要进行接近于 n^2/2 的比较和交换操作。
空间复杂度:
插入排序是原地排序算法,不需要额外的空间存储数据,仅需要常数级别的临时空间存放中间变量,所以空间复杂度为 O(1)。
- public class InsertionSort {
- public static void insertionSort(int[] arr) {
- for (int i = 1; i < arr.length; i++) {
- // 获取当前数组里面的值并赋值给 key
- int key = arr[i];
- int j = i - 1;
-
- // 将大于key的元素向后移动一位
- while (j >= 0 && arr[j] > key) {
- arr[j + 1] = arr[j];
- j--;
- }
-
- // 插入key到正确的位置
- arr[j + 1] = key;
- }
- }
-
- public static void main(String[] args) {
- int[] array = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1};
- insertionSort(array);
-
- System.out.println("排序后的数组:");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本思想:
通过相邻元素之间的比较和交换来逐渐将最大(或最小)的元素“浮”到数组的一端。
时间复杂度:
最好情况(已排序数组):冒泡排序只需遍历一次即可完成排序,因此最好情况下的时间复杂度为 O(n)。
最坏情况(逆序数组):需要进行 n*(n-1)/2 次比较和交换,时间复杂度为 O(n^2)。
平均情况:也是 O(n^2),因为在大多数情况下,需要进行接近于 n^2/2 的比较和交换操作。
空间复杂度:
冒泡排序是原地排序算法,它不需要额外的空间存储数据,只需要常数级别的临时空间存放中间变量,所以空间复杂度为 O(1)。
- public class BubbleSort {
- public static void bubbleSort(int[] arr) {
- // 获取数组长度
- int n = arr.length;
-
- // 遍历所有数组元素
- for (int i = 0; i < n - 1; i++) {
- // 每轮遍历将最大的元素"冒泡"到末尾
- for (int j = 0; j < n - 1 - i; j++) {
- // 如果当前元素比下一个元素大,则交换它们的位置
- if (arr[j] > arr[j + 1]) {
- // 交换 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[] array = {64, 34, 25, 12, 22, 11, 90};
- bubbleSort(array);
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本思想:
每一次从未排序的部分中找到最小(或最大)的元素,并将其放到已排序部分的末尾。
时间复杂度:
最好情况(输入数组已经是有序的):即使数组已经有序,选择排序仍然需要进行 n-1 轮比较,每轮都需要遍历剩下的元素来确认最小值,所以时间复杂度仍为 O(n^2)。
最坏情况(输入数组完全逆序):同样需要进行 n*(n-1)/2 次比较和最多同样次数的交换操作,时间复杂度为 O(n^2)。
平均情况:由于无论初始顺序如何,选择排序都需要进行接近于 n^2/2 的比较,因此平均时间复杂度也是 O(n^2)。
空间复杂度:
选择排序是原地排序算法,不需要额外的空间存储数据,仅需要常数级别的临时空间存放中间变量,所以空间复杂度为 O(1)。
- public class SelectionSort {
- public static void selectionSort(int[] arr) {
- // 获取数组长度
- int n = arr.length;
-
- // 遍历所有数组元素
- for (int i = 0; i < n - 1; i++) {
- // 找到[i, n)区间内的最小值的索引
- int minIndex = i;
- for (int j = i + 1; j < n; j++) {
- if (arr[j] < arr[minIndex]) {
- minIndex = j;
- }
- }
-
- // 将找到的最小值与当前位置的元素交换
- int temp = arr[minIndex];
- arr[minIndex] = arr[i];
- arr[i] = temp;
- }
- }
-
- public static void main(String[] args) {
- int[] array = {64, 34, 25, 12, 22, 11, 90};
- selectionSort(array);
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本思想:
快速排序是一种分治策略的排序算法,通过选取一个“基准”元素将数组划分为两个子序列,左边的元素小于基准,右边的元素大于基准。然后递归地对左右子序列进行同样的操作。
时间复杂度:
最好情况(每次划分都很均匀): O(n log n)
平均情况: O(n log n)
最坏情况(输入已经有序或逆序): O(n^2)
空间复杂度:
栈空间消耗: O(log n),递归栈深度在最坏情况下为O(n),但平均情况下是logn。
原地排序算法,不需要额外空间,辅助空间复杂度: O(1)
- public class QuickSort {
- // 快速排序方法,采用分治法策略
- public static void quickSort(int[] arr, int low, int high) {
- if (low < high) {
- // 找到分区点(pivot),将数组分为两部分:小于pivot的元素和大于pivot的元素
- int pivotIndex = partition(arr, low, high);
-
- // 对左右两边的子数组进行递归排序
- quickSort(arr, low, pivotIndex - 1);
- quickSort(arr, pivotIndex + 1, high);
- }
- }
-
- // 分区操作,返回pivot的位置
- private static int partition(int[] arr, int low, int high) {
- // 选取最后一个元素作为基准值
- int pivot = arr[high];
- int i = (low - 1); // index of smaller element
-
- for (int j = low; j <= high - 1; j++) {
- // 如果当前元素小于或等于pivot,则交换位置并将较小元素索引后移
- if (arr[j] <= pivot) {
- i++;
-
- // 交换arr[i]和arr[j]
- int temp = arr[i];
- arr[i] = arr[j];
- arr[j] = temp;
- }
- }
-
- // 将基准值与i+1位置上的元素交换,保证基准值左边的都比它小,右边的都比它大
- int temp = arr[i + 1];
- arr[i + 1] = arr[high];
- arr[high] = temp;
-
- return i + 1;
- }
-
- public static void main(String[] args) {
- int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
- quickSort(array, 0, array.length - 1);
-
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本原理:
归并排序是一种稳定的排序算法,它将待排序的序列分成若干个子序列,分别进行排序,最后再将结果进行合并。其核心步骤是将两个已经排序好的子序列合并成一个新的、更大的已排序序列。时间复杂度:
归并排序无论在最好、最坏还是平均情况下,时间复杂度都是 O(n log n)。空间复杂度:
归并排序需要额外的空间来存储临时数组,所以空间复杂度为 O(n)。
- public class MergeSort {
- // 归并排序方法,采用分治法策略
- 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);
- }
- }
-
- // 合并两个有序数组的方法
- private 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];
-
- // 复制数据到临时数组
- System.arraycopy(arr, left, L, 0, n1);
- System.arraycopy(arr, mid + 1, R, 0, n2);
-
- // 初始化指针
- int i = 0, j = 0, k = left;
-
- // 比较两个数组的元素,并按照升序合并到原数组中
- while (i < n1 && j < n2) {
- if (L[i] <= R[j]) {
- arr[k++] = L[i++];
- } else {
- arr[k++] = R[j++];
- }
- }
-
- // 将剩余未合并的部分复制回原数组
- while (i < n1) {
- arr[k++] = L[i++];
- }
-
- while (j < n2) {
- arr[k++] = R[j++];
- }
- }
-
- public static void main(String[] args) {
- int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
- mergeSort(array, 0, array.length - 1);
-
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本原理:
堆排序是一种基于比较的排序算法,通过构建一个“大顶堆”或“小顶堆”,使得父节点总是大于(或小于)其子节点。首先构建初始堆,然后把堆顶元素和最后一个元素交换位置,再重新调整堆,重复这个过程直到所有元素都有序。时间复杂度:
堆排序在最好、最坏和平均情况下时间复杂度都是 O(n log n),其中 n 是待排序数组的长度。空间复杂度:
堆排序是原地排序算法,只需要常数级别的额外空间用于临时存储交换的元素,所以空间复杂度为 O(1)。
- public class HeapSort {
- // 建立大顶堆
- public static void heapify(int[] arr, int n, int i) {
- int largest = i; // 初始化最大值索引为根节点
- int left = 2 * i + 1;
- int right = 2 * i + 2;
-
- // 如果左子节点存在且大于根节点
- if (left < n && arr[left] > arr[largest]) {
- largest = left;
- }
-
- // 如果右子节点存在且大于当前最大值
- if (right < n && arr[right] > arr[largest]) {
- largest = right;
- }
-
- // 如果最大值不是根节点,交换它们并继续向下调整堆
- if (largest != i) {
- swap(arr, i, largest);
- heapify(arr, n, 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 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);
- }
- }
-
- public static void main(String[] args) {
- int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
- heapSort(array);
-
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本原理:
计数排序是一种非基于比较的排序算法,它适用于整数排序且数值范围不大的情况。通过对每一个输入元素x,确定出不大于x的元素个数,就可以直接得到x在输出序列中的位置。最后根据计数结果重建排序后的序列。
时间复杂度:
当输入数据范围不是特别大时,计数排序的时间复杂度为 O(n + k),n 是待排序数组的长度,k 是输入数据的最大值与最小值的差加一。
空间复杂度:
计数排序需要额外的空间来存储计数数组和输出数组,所以空间复杂度为 O(n + k)。但如果k远大于n,则该算法可能不适用。
- public class CountingSort {
- public static void countingSort(int[] arr) {
- // 查找数组中的最大值
- int maxVal = Arrays.stream(arr).max().getAsInt();
-
- // 初始化计数数组,大小为最大值+1
- int[] count = new int[maxVal + 1];
-
- // 统计每个数出现的次数
- for (int num : arr) {
- count[num]++;
- }
-
- // 计算累计出现次数
- for (int i = 1; i < count.length; i++) {
- count[i] += count[i - 1];
- }
-
- // 创建一个新的输出数组
- int[] output = new int[arr.length];
-
- // 按照顺序将元素放入输出数组
- for (int i = arr.length - 1; i >= 0; i--) {
- output[count[arr[i]] - 1] = arr[i];
- count[arr[i]]--;
- }
-
- // 将排序好的元素复制回原数组
- System.arraycopy(output, 0, arr, 0, arr.length);
- }
-
- public static void main(String[] args) {
- int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
- countingSort(array);
-
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本原理:
希尔排序是插入排序的一种改进版本,通过定义一个间隔序列来分组元素,对每组使用直接插入排序。随着增量逐渐减少,元素变得越来越接近有序,最后当增量为1时,整个数组已经基本有序,此时执行一次插入排序就能得到完全有序的结果。
时间复杂度:
最好情况下的时间复杂度可以达到O(n log n),最坏情况下和平均情况下的时间复杂度取决于所使用的增量序列。经典的Hibbard增量序列下,希尔排序的时间复杂度为O(n^(3/2))。
空间复杂度:
希尔排序是原地排序算法,空间复杂度为O(1)。
- public class ShellSort {
- // 希尔排序实现
- public static void shellSort(int[] arr) {
- int len = arr.length;
-
- // 增量序列,这里使用h(n) = 3^k - 1的方式,逐步减小增量
- for (int gap = len / 2; gap > 0; gap /= 2) {
- // 对每一个增量值,进行插入排序
- for (int i = gap; i < len; i++) {
- // 将arr[i]插入到正确的位置(根据当前的增量)
- int temp = arr[i];
- int j;
- for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
- arr[j] = arr[j - gap];
- }
- arr[j] = temp;
- }
- }
- }
-
- public static void main(String[] args) {
- int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
- shellSort(array);
-
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本原理:
桶排序是一种分布排序算法,它将要排序的数据分布到有限数量的桶里,对每个桶内的数据进行排序,然后依次合并所有桶中的数据即可完成排序。桶内排序可以采用任何适合的排序算法。
时间复杂度:
当输入数据均匀分布且桶的数量足够多时,桶排序的时间复杂度可以达到O(n + k),其中n是待排序元素个数,k是桶的数量。但如果元素分布不均匀或桶划分不合理,时间复杂度会变差。
空间复杂度:
空间复杂度主要取决于桶的数量,若假设每个桶大小固定,则空间复杂度为O(n+k),其中n是待排序元素个数,k是桶的数量。
- public class BucketSort {
- // 桶排序实现,假设输入数组为非负整数
- public static void bucketSort(int[] arr, int maxVal) {
- if (arr == null || arr.length == 0) {
- return;
- }
-
- // 创建足够数量的空桶(这里以最大值 + 1来确定桶的数量)
- List<List<Integer>> buckets = new ArrayList<>();
- for (int i = 0; i <= maxVal; i++) {
- buckets.add(new ArrayList<>());
- }
-
- // 将元素分配到各个桶中
- for (int num : arr) {
- buckets.get(num).add(num);
- }
-
- // 对每个桶内的元素进行排序,可以使用插入排序等简单算法
- for (List<Integer> bucket : buckets) {
- Collections.sort(bucket);
- }
-
- // 合并所有已排序的桶
- int index = 0;
- for (List<Integer> bucket : buckets) {
- for (Integer value : bucket) {
- // 防止处理空桶
- if (!bucket.isEmpty()) {
- arr[index++] = value;
- }
- }
- }
- }
-
- public static void main(String[] args) {
- int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
- int maxVal = Arrays.stream(array).max().getAsInt();
- bucketSort(array, maxVal);
-
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
基本原理:
基数排序是一种非比较型整数排序算法,其基本思想是对要排序的数据按位数进行划分,从低位到高位逐次进行排序。它借助了稳定的计数排序,每次针对一位数字进行排序,直到所有位数都排序完成。时间复杂度:
在基数排序中,每一趟都是对不同位进行计数排序,所以每一轮的时间复杂度为O(n),由于一共需要log_radix(maxValue)轮,因此总的时间复杂度为O(k * n),其中k通常为radix(如10进制则k=10)。空间复杂度:
空间复杂度为O(n + k),其中n是待排序元素个数,k是基数(比如在十进制下k=10)。
- public class RadixSort {
- // 基数排序实现,假设输入数组为非负整数
- public static void radixSort(int[] arr) {
- // 获取数组中的最大数,用于确定需要遍历的位数
- int maxNum = Arrays.stream(arr).max().getAsInt();
-
- // 定义一个基数(例如十进制下基数为10)
- final int RADIX = 10;
-
- // 从最低有效位开始,对每一位进行排序
- for (int exp = 1; maxNum / exp > 0; exp *= RADIX) {
- // 使用计数排序对当前位进行排序
- countingSortByDigit(arr, exp, RADIX);
- }
- }
-
- // 计数排序对特定位数进行排序
- private static void countingSortByDigit(int[] arr, int exp, int radix) {
- int[] output = new int[arr.length];
- int[] count = new int[radix];
-
- // 初始化计数数组
- Arrays.fill(count, 0);
-
- // 统计每个桶中元素的数量
- for (int i = 0; i < arr.length; i++) {
- count[(arr[i] / exp) % radix]++;
- }
-
- // 计算每个桶的起始位置
- for (int i = 1; i < radix; i++) {
- count[i] += count[i - 1];
- }
-
- // 根据每个桶的位置逆序填充输出数组,并更新原数组
- for (int i = arr.length - 1; i >= 0; i--) {
- output[count[(arr[i] / exp) % radix] - 1] = arr[i];
- count[(arr[i] / exp) % radix]--;
- arr[i] = output[i];
- }
- }
-
- public static void main(String[] args) {
- int[] array = {93, 72, 51, 112, 12, 234, 34, 10, 67};
- radixSort(array);
-
- System.out.println("Sorted array: ");
- for (int num : array) {
- System.out.print(num + " ");
- }
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。