当前位置:   article > 正文

Java实现十大排序算法_java 排序算法

java 排序算法

时间/空间复杂度对比:

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)稳定

一、插入排序(Insertion Sort

基本思想:

       通过构造一个有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

时间复杂度:

       最好情况(输入数组已经是有序的):只需遍历一次数组即可完成排序,时间复杂度为 O(n)。

       最坏情况(输入数组完全逆序):需要进行 n*(n-1)/2 次比较和最多同样次数的交换操作,时间复杂度为 O(n^2)。

       平均情况:时间复杂度也为 O(n^2),因为平均情况下,插入排序需要进行接近于 n^2/2 的比较和交换操作。

空间复杂度:

       插入排序是原地排序算法,不需要额外的空间存储数据,仅需要常数级别的临时空间存放中间变量,所以空间复杂度为 O(1)。

  1. public class InsertionSort {
  2. public static void insertionSort(int[] arr) {
  3. for (int i = 1; i < arr.length; i++) {
  4. // 获取当前数组里面的值并赋值给 key
  5. int key = arr[i];
  6. int j = i - 1;
  7. // 将大于key的元素向后移动一位
  8. while (j >= 0 && arr[j] > key) {
  9. arr[j + 1] = arr[j];
  10. j--;
  11. }
  12. // 插入key到正确的位置
  13. arr[j + 1] = key;
  14. }
  15. }
  16. public static void main(String[] args) {
  17. int[] array = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1};
  18. insertionSort(array);
  19. System.out.println("排序后的数组:");
  20. for (int num : array) {
  21. System.out.print(num + " ");
  22. }
  23. }
  24. }

二、冒泡排序(Bubble Sort)

基本思想:

       通过相邻元素之间的比较和交换来逐渐将最大(或最小)的元素“浮”到数组的一端。 

时间复杂度:

       最好情况(已排序数组):冒泡排序只需遍历一次即可完成排序,因此最好情况下的时间复杂度为 O(n)。

       最坏情况(逆序数组):需要进行 n*(n-1)/2 次比较和交换,时间复杂度为 O(n^2)。

       平均情况:也是 O(n^2),因为在大多数情况下,需要进行接近于 n^2/2 的比较和交换操作。

空间复杂度:

       冒泡排序是原地排序算法,它不需要额外的空间存储数据,只需要常数级别的临时空间存放中间变量,所以空间复杂度为 O(1)。

  1. public class BubbleSort {
  2. public static void bubbleSort(int[] arr) {
  3. // 获取数组长度
  4. int n = arr.length;
  5. // 遍历所有数组元素
  6. for (int i = 0; i < n - 1; i++) {
  7. // 每轮遍历将最大的元素"冒泡"到末尾
  8. for (int j = 0; j < n - 1 - i; j++) {
  9. // 如果当前元素比下一个元素大,则交换它们的位置
  10. if (arr[j] > arr[j + 1]) {
  11. // 交换 arr[j] 和 arr[j+1]
  12. int temp = arr[j];
  13. arr[j] = arr[j + 1];
  14. arr[j + 1] = temp;
  15. }
  16. }
  17. }
  18. }
  19. public static void main(String[] args) {
  20. int[] array = {64, 34, 25, 12, 22, 11, 90};
  21. bubbleSort(array);
  22. System.out.println("Sorted array: ");
  23. for (int num : array) {
  24. System.out.print(num + " ");
  25. }
  26. }
  27. }

 三、选择排序(Selection Sort)

基本思想:

       每一次从未排序的部分中找到最小(或最大)的元素,并将其放到已排序部分的末尾。

时间复杂度:

       最好情况(输入数组已经是有序的):即使数组已经有序,选择排序仍然需要进行 n-1 轮比较,每轮都需要遍历剩下的元素来确认最小值,所以时间复杂度仍为 O(n^2)。

       最坏情况(输入数组完全逆序):同样需要进行 n*(n-1)/2 次比较和最多同样次数的交换操作,时间复杂度为 O(n^2)。

       平均情况:由于无论初始顺序如何,选择排序都需要进行接近于 n^2/2 的比较,因此平均时间复杂度也是 O(n^2)。

空间复杂度:

       选择排序是原地排序算法,不需要额外的空间存储数据,仅需要常数级别的临时空间存放中间变量,所以空间复杂度为 O(1)。

  1. public class SelectionSort {
  2. public static void selectionSort(int[] arr) {
  3. // 获取数组长度
  4. int n = arr.length;
  5. // 遍历所有数组元素
  6. for (int i = 0; i < n - 1; i++) {
  7. // 找到[i, n)区间内的最小值的索引
  8. int minIndex = i;
  9. for (int j = i + 1; j < n; j++) {
  10. if (arr[j] < arr[minIndex]) {
  11. minIndex = j;
  12. }
  13. }
  14. // 将找到的最小值与当前位置的元素交换
  15. int temp = arr[minIndex];
  16. arr[minIndex] = arr[i];
  17. arr[i] = temp;
  18. }
  19. }
  20. public static void main(String[] args) {
  21. int[] array = {64, 34, 25, 12, 22, 11, 90};
  22. selectionSort(array);
  23. System.out.println("Sorted array: ");
  24. for (int num : array) {
  25. System.out.print(num + " ");
  26. }
  27. }
  28. }

四、快速排序 (Quick Sort)

基本思想:

       快速排序是一种分治策略的排序算法,通过选取一个“基准”元素将数组划分为两个子序列,左边的元素小于基准,右边的元素大于基准。然后递归地对左右子序列进行同样的操作。 

时间复杂度:

       最好情况(每次划分都很均匀): O(n log n)

       平均情况: O(n log n)

       最坏情况(输入已经有序或逆序): O(n^2)

空间复杂度:

       栈空间消耗: O(log n),递归栈深度在最坏情况下为O(n),但平均情况下是logn。

       原地排序算法,不需要额外空间,辅助空间复杂度: O(1)

  1. public class QuickSort {
  2. // 快速排序方法,采用分治法策略
  3. public static void quickSort(int[] arr, int low, int high) {
  4. if (low < high) {
  5. // 找到分区点(pivot),将数组分为两部分:小于pivot的元素和大于pivot的元素
  6. int pivotIndex = partition(arr, low, high);
  7. // 对左右两边的子数组进行递归排序
  8. quickSort(arr, low, pivotIndex - 1);
  9. quickSort(arr, pivotIndex + 1, high);
  10. }
  11. }
  12. // 分区操作,返回pivot的位置
  13. private static int partition(int[] arr, int low, int high) {
  14. // 选取最后一个元素作为基准值
  15. int pivot = arr[high];
  16. int i = (low - 1); // index of smaller element
  17. for (int j = low; j <= high - 1; j++) {
  18. // 如果当前元素小于或等于pivot,则交换位置并将较小元素索引后移
  19. if (arr[j] <= pivot) {
  20. i++;
  21. // 交换arr[i]和arr[j]
  22. int temp = arr[i];
  23. arr[i] = arr[j];
  24. arr[j] = temp;
  25. }
  26. }
  27. // 将基准值与i+1位置上的元素交换,保证基准值左边的都比它小,右边的都比它大
  28. int temp = arr[i + 1];
  29. arr[i + 1] = arr[high];
  30. arr[high] = temp;
  31. return i + 1;
  32. }
  33. public static void main(String[] args) {
  34. int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
  35. quickSort(array, 0, array.length - 1);
  36. System.out.println("Sorted array: ");
  37. for (int num : array) {
  38. System.out.print(num + " ");
  39. }
  40. }
  41. }

 五、归并排序 (Merge Sort)

基本原理:
       归并排序是一种稳定的排序算法,它将待排序的序列分成若干个子序列,分别进行排序,最后再将结果进行合并。其核心步骤是将两个已经排序好的子序列合并成一个新的、更大的已排序序列。

时间复杂度:
       归并排序无论在最好、最坏还是平均情况下,时间复杂度都是 O(n log n)。

空间复杂度:
       归并排序需要额外的空间来存储临时数组,所以空间复杂度为 O(n)。

  1. public class MergeSort {
  2. // 归并排序方法,采用分治法策略
  3. public static void mergeSort(int[] arr, int left, int right) {
  4. if (left < right) {
  5. int mid = (left + right) / 2;
  6. // 对左半边数组进行归并排序
  7. mergeSort(arr, left, mid);
  8. // 对右半边数组进行归并排序
  9. mergeSort(arr, mid + 1, right);
  10. // 合并两个有序的半边数组
  11. merge(arr, left, mid, right);
  12. }
  13. }
  14. // 合并两个有序数组的方法
  15. private static void merge(int[] arr, int left, int mid, int right) {
  16. int n1 = mid - left + 1;
  17. int n2 = right - mid;
  18. // 创建临时数组用于存储左右两个有序数组
  19. int[] L = new int[n1];
  20. int[] R = new int[n2];
  21. // 复制数据到临时数组
  22. System.arraycopy(arr, left, L, 0, n1);
  23. System.arraycopy(arr, mid + 1, R, 0, n2);
  24. // 初始化指针
  25. int i = 0, j = 0, k = left;
  26. // 比较两个数组的元素,并按照升序合并到原数组中
  27. while (i < n1 && j < n2) {
  28. if (L[i] <= R[j]) {
  29. arr[k++] = L[i++];
  30. } else {
  31. arr[k++] = R[j++];
  32. }
  33. }
  34. // 将剩余未合并的部分复制回原数组
  35. while (i < n1) {
  36. arr[k++] = L[i++];
  37. }
  38. while (j < n2) {
  39. arr[k++] = R[j++];
  40. }
  41. }
  42. public static void main(String[] args) {
  43. int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
  44. mergeSort(array, 0, array.length - 1);
  45. System.out.println("Sorted array: ");
  46. for (int num : array) {
  47. System.out.print(num + " ");
  48. }
  49. }
  50. }

六、堆排序 (Heap Sort)

基本原理:
       堆排序是一种基于比较的排序算法,通过构建一个“大顶堆”或“小顶堆”,使得父节点总是大于(或小于)其子节点。首先构建初始堆,然后把堆顶元素和最后一个元素交换位置,再重新调整堆,重复这个过程直到所有元素都有序。

时间复杂度:
       堆排序在最好、最坏和平均情况下时间复杂度都是 O(n log n),其中 n 是待排序数组的长度。

空间复杂度:
       堆排序是原地排序算法,只需要常数级别的额外空间用于临时存储交换的元素,所以空间复杂度为 O(1)。

  1. public class HeapSort {
  2. // 建立大顶堆
  3. public static void heapify(int[] arr, int n, int i) {
  4. int largest = i; // 初始化最大值索引为根节点
  5. int left = 2 * i + 1;
  6. int right = 2 * i + 2;
  7. // 如果左子节点存在且大于根节点
  8. if (left < n && arr[left] > arr[largest]) {
  9. largest = left;
  10. }
  11. // 如果右子节点存在且大于当前最大值
  12. if (right < n && arr[right] > arr[largest]) {
  13. largest = right;
  14. }
  15. // 如果最大值不是根节点,交换它们并继续向下调整堆
  16. if (largest != i) {
  17. swap(arr, i, largest);
  18. heapify(arr, n, largest);
  19. }
  20. }
  21. // 交换数组中的两个元素
  22. private static void swap(int[] arr, int i, int j) {
  23. int temp = arr[i];
  24. arr[i] = arr[j];
  25. arr[j] = temp;
  26. }
  27. // 对整个数组进行堆排序
  28. public static void heapSort(int[] arr) {
  29. int n = arr.length;
  30. // 构建初始大顶堆
  31. for (int i = n / 2 - 1; i >= 0; i--) {
  32. heapify(arr, n, i);
  33. }
  34. // 依次将堆顶元素与末尾元素交换,并对剩余部分重新调整为大顶堆
  35. for (int i = n - 1; i > 0; i--) {
  36. swap(arr, 0, i);
  37. heapify(arr, i, 0);
  38. }
  39. }
  40. public static void main(String[] args) {
  41. int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
  42. heapSort(array);
  43. System.out.println("Sorted array: ");
  44. for (int num : array) {
  45. System.out.print(num + " ");
  46. }
  47. }
  48. }

 七、计数排序 (Counting Sort)

基本原理:

       计数排序是一种非基于比较的排序算法,它适用于整数排序且数值范围不大的情况。通过对每一个输入元素x,确定出不大于x的元素个数,就可以直接得到x在输出序列中的位置。最后根据计数结果重建排序后的序列。

时间复杂度:

       当输入数据范围不是特别大时,计数排序的时间复杂度为 O(n + k),n 是待排序数组的长度,k 是输入数据的最大值与最小值的差加一。

空间复杂度:

       计数排序需要额外的空间来存储计数数组和输出数组,所以空间复杂度为 O(n + k)。但如果k远大于n,则该算法可能不适用。

  1. public class CountingSort {
  2. public static void countingSort(int[] arr) {
  3. // 查找数组中的最大值
  4. int maxVal = Arrays.stream(arr).max().getAsInt();
  5. // 初始化计数数组,大小为最大值+1
  6. int[] count = new int[maxVal + 1];
  7. // 统计每个数出现的次数
  8. for (int num : arr) {
  9. count[num]++;
  10. }
  11. // 计算累计出现次数
  12. for (int i = 1; i < count.length; i++) {
  13. count[i] += count[i - 1];
  14. }
  15. // 创建一个新的输出数组
  16. int[] output = new int[arr.length];
  17. // 按照顺序将元素放入输出数组
  18. for (int i = arr.length - 1; i >= 0; i--) {
  19. output[count[arr[i]] - 1] = arr[i];
  20. count[arr[i]]--;
  21. }
  22. // 将排序好的元素复制回原数组
  23. System.arraycopy(output, 0, arr, 0, arr.length);
  24. }
  25. public static void main(String[] args) {
  26. int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
  27. countingSort(array);
  28. System.out.println("Sorted array: ");
  29. for (int num : array) {
  30. System.out.print(num + " ");
  31. }
  32. }
  33. }

 八、希尔排序 (Shell Sort)

基本原理:

       希尔排序是插入排序的一种改进版本,通过定义一个间隔序列来分组元素,对每组使用直接插入排序。随着增量逐渐减少,元素变得越来越接近有序,最后当增量为1时,整个数组已经基本有序,此时执行一次插入排序就能得到完全有序的结果。

时间复杂度:

       最好情况下的时间复杂度可以达到O(n log n),最坏情况下和平均情况下的时间复杂度取决于所使用的增量序列。经典的Hibbard增量序列下,希尔排序的时间复杂度为O(n^(3/2))。

空间复杂度:

       希尔排序是原地排序算法,空间复杂度为O(1)。

  1. public class ShellSort {
  2. // 希尔排序实现
  3. public static void shellSort(int[] arr) {
  4. int len = arr.length;
  5. // 增量序列,这里使用h(n) = 3^k - 1的方式,逐步减小增量
  6. for (int gap = len / 2; gap > 0; gap /= 2) {
  7. // 对每一个增量值,进行插入排序
  8. for (int i = gap; i < len; i++) {
  9. // 将arr[i]插入到正确的位置(根据当前的增量)
  10. int temp = arr[i];
  11. int j;
  12. for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
  13. arr[j] = arr[j - gap];
  14. }
  15. arr[j] = temp;
  16. }
  17. }
  18. }
  19. public static void main(String[] args) {
  20. int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
  21. shellSort(array);
  22. System.out.println("Sorted array: ");
  23. for (int num : array) {
  24. System.out.print(num + " ");
  25. }
  26. }
  27. }

九、桶排序 (Bucket Sort) 

基本原理:

       桶排序是一种分布排序算法,它将要排序的数据分布到有限数量的桶里,对每个桶内的数据进行排序,然后依次合并所有桶中的数据即可完成排序。桶内排序可以采用任何适合的排序算法。

时间复杂度:

       当输入数据均匀分布且桶的数量足够多时,桶排序的时间复杂度可以达到O(n + k),其中n是待排序元素个数,k是桶的数量。但如果元素分布不均匀或桶划分不合理,时间复杂度会变差。

空间复杂度:

       空间复杂度主要取决于桶的数量,若假设每个桶大小固定,则空间复杂度为O(n+k),其中n是待排序元素个数,k是桶的数量。

  1. public class BucketSort {
  2. // 桶排序实现,假设输入数组为非负整数
  3. public static void bucketSort(int[] arr, int maxVal) {
  4. if (arr == null || arr.length == 0) {
  5. return;
  6. }
  7. // 创建足够数量的空桶(这里以最大值 + 1来确定桶的数量)
  8. List<List<Integer>> buckets = new ArrayList<>();
  9. for (int i = 0; i <= maxVal; i++) {
  10. buckets.add(new ArrayList<>());
  11. }
  12. // 将元素分配到各个桶中
  13. for (int num : arr) {
  14. buckets.get(num).add(num);
  15. }
  16. // 对每个桶内的元素进行排序,可以使用插入排序等简单算法
  17. for (List<Integer> bucket : buckets) {
  18. Collections.sort(bucket);
  19. }
  20. // 合并所有已排序的桶
  21. int index = 0;
  22. for (List<Integer> bucket : buckets) {
  23. for (Integer value : bucket) {
  24. // 防止处理空桶
  25. if (!bucket.isEmpty()) {
  26. arr[index++] = value;
  27. }
  28. }
  29. }
  30. }
  31. public static void main(String[] args) {
  32. int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
  33. int maxVal = Arrays.stream(array).max().getAsInt();
  34. bucketSort(array, maxVal);
  35. System.out.println("Sorted array: ");
  36. for (int num : array) {
  37. System.out.print(num + " ");
  38. }
  39. }
  40. }

十、基数排序(Radix Sort)

基本原理:
       基数排序是一种非比较型整数排序算法,其基本思想是对要排序的数据按位数进行划分,从低位到高位逐次进行排序。它借助了稳定的计数排序,每次针对一位数字进行排序,直到所有位数都排序完成。

时间复杂度:
       在基数排序中,每一趟都是对不同位进行计数排序,所以每一轮的时间复杂度为O(n),由于一共需要log_radix(maxValue)轮,因此总的时间复杂度为O(k * n),其中k通常为radix(如10进制则k=10)。

空间复杂度:
       空间复杂度为O(n + k),其中n是待排序元素个数,k是基数(比如在十进制下k=10)。

  1. public class RadixSort {
  2. // 基数排序实现,假设输入数组为非负整数
  3. public static void radixSort(int[] arr) {
  4. // 获取数组中的最大数,用于确定需要遍历的位数
  5. int maxNum = Arrays.stream(arr).max().getAsInt();
  6. // 定义一个基数(例如十进制下基数为10)
  7. final int RADIX = 10;
  8. // 从最低有效位开始,对每一位进行排序
  9. for (int exp = 1; maxNum / exp > 0; exp *= RADIX) {
  10. // 使用计数排序对当前位进行排序
  11. countingSortByDigit(arr, exp, RADIX);
  12. }
  13. }
  14. // 计数排序对特定位数进行排序
  15. private static void countingSortByDigit(int[] arr, int exp, int radix) {
  16. int[] output = new int[arr.length];
  17. int[] count = new int[radix];
  18. // 初始化计数数组
  19. Arrays.fill(count, 0);
  20. // 统计每个桶中元素的数量
  21. for (int i = 0; i < arr.length; i++) {
  22. count[(arr[i] / exp) % radix]++;
  23. }
  24. // 计算每个桶的起始位置
  25. for (int i = 1; i < radix; i++) {
  26. count[i] += count[i - 1];
  27. }
  28. // 根据每个桶的位置逆序填充输出数组,并更新原数组
  29. for (int i = arr.length - 1; i >= 0; i--) {
  30. output[count[(arr[i] / exp) % radix] - 1] = arr[i];
  31. count[(arr[i] / exp) % radix]--;
  32. arr[i] = output[i];
  33. }
  34. }
  35. public static void main(String[] args) {
  36. int[] array = {93, 72, 51, 112, 12, 234, 34, 10, 67};
  37. radixSort(array);
  38. System.out.println("Sorted array: ");
  39. for (int num : array) {
  40. System.out.print(num + " ");
  41. }
  42. }
  43. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/909935
推荐阅读
相关标签
  

闽ICP备14008679号