Java数组篇[9]:数组排序算法大比拼

2024-09-15 18:05:04 浏览数 (1)

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

排序算法是计算机科学中的基石之一。在Java编程中,掌握不同的排序算法对于处理数据集合至关重要。

摘要

本文将介绍几种常见的排序算法,并在Java中实现它们。我们还将比较它们的性能和适用场景。

概述

排序算法可以根据时间复杂度、空间复杂度和稳定性等不同标准进行分类。常见的排序算法包括冒泡排序、选择排序、插入排序、归并排序和快速排序等。

冒泡排序

冒泡排序通过重复遍历要排序的数列,比较每对相邻元素的大小,并在必要时交换它们的位置。

代码语言:java复制
void bubbleSort(int[] array) {
    boolean swapped;
    for (int i = 0; i < array.length - 1; i  ) {
        swapped = false;
        for (int j = 0; j < array.length - 1 - i; j  ) {
            if (array[j] > array[j   1]) {
                // Swap elements
                int temp = array[j];
                array[j] = array[j   1];
                array[j   1] = temp;
                swapped = true;
            }
        }
        if (!swapped) break; // Optimization if the array is already sorted
    }
}

  针对如上示例代码,这里我给大家详细的代码剖析下,以便于帮助大家理解的更为透彻,帮助大家早日掌握。

这段Java代码实现了冒泡排序算法。冒泡排序是一种简单的排序算法,它重复地遍历要排序的数组,比较相邻的元素,并在必要时交换它们的位置。以下是对代码的逐行解释:

  1. void bubbleSort(int[] array) {:定义了一个名为bubbleSort的方法,它接受一个整型数组array作为参数。
  2. boolean swapped;:声明了一个布尔类型的变量swapped,用于跟踪在内层循环中是否发生了元素交换。
  3. for (int i = 0; i < array.length - 1; i ) {:外层循环,从索引0开始,直到数组的倒数第二个元素。array.length - 1确保了循环不会超出数组边界。
  4. swapped = false;:在每次外层循环开始时,将swapped标志设置为false
  5. for (int j = 0; j < array.length - 1 - i; j ) {:内层循环,从索引0开始,每次迭代后减少遍历的范围,因为经过每次外层循环,最大的元素都会被放到它应该在的位置。
  6. if (array[j] > array[j 1]) {:比较当前元素array[j]和下一个元素array[j 1]
  7. int temp = array[j];:如果当前元素大于下一个元素,准备交换它们,首先保存当前元素的值到临时变量temp
  8. array[j] = array[j 1];:将下一个元素的值赋给当前元素。
  9. array[j 1] = temp;:将临时变量temp的值(原当前元素的值)赋给下一个元素,完成交换。
  10. swapped = true;:设置swapped标志为true,表示发生了交换。
  11. if (!swapped) break;:如果在一次完整的内层循环中没有发生任何交换,那么数组已经排序完成,可以提前退出循环。

这种优化可以提高冒泡排序在部分或完全有序的数组上的性能。然而,即使有这种优化,冒泡排序在最坏情况下的时间复杂度仍然是O(n^2),其中n是数组的长度。冒泡排序是稳定的排序算法,因为它不会改变相同元素之间的顺序。

选择排序

选择排序通过在未排序的元素中找到最小(或最大)元素,然后将其放置在已排序序列的末尾。

代码语言:java复制
void selectionSort(int[] array) {
    for (int i = 0; i < array.length - 1; i  ) {
        int minIndex = i;
        for (int j = i   1; j < array.length; j  ) {
            if (array[j] < array[minIndex]) {
                minIndex = j;
            }
        }
        // Swap elements
        int temp = array[i];
        array[i] = array[minIndex];
        array[minIndex] = temp;
    }
}

  针对如上示例代码,这里我给大家详细的代码剖析下,以便于帮助大家理解的更为透彻,帮助大家早日掌握。

这段Java代码实现了选择排序算法。选择排序通过在未排序的部分找到最小(或最大)的元素,然后将其与已排序序列的最后一个元素交换位置。以下是对代码的逐行解释:

  1. void selectionSort(int[] array) {:定义了一个名为selectionSort的方法,它接受一个整型数组array作为参数。
  2. for (int i = 0; i < array.length - 1; i ) {:外层循环,从索引0开始,直到数组的倒数第二个元素。循环变量i表示已排序部分的最后一个元素的索引。
  3. int minIndex = i;:初始化minIndex为当前循环的起始索引i,这是假设当前位置的元素就是未排序部分的最小值。
  4. for (int j = i 1; j < array.length; j ) {:内层循环,从i 1开始遍历未排序的部分。
  5. if (array[j] < array[minIndex]) {:比较当前遍历到的元素array[j]和当前记录的最小元素array[minIndex]
  6. minIndex = j;:如果找到更小的元素,则更新minIndex为当前元素的索引。
  7. int temp = array[i];:在内层循环结束后,如果minIndex发生了变化,说明找到了一个新的最小值。在交换前,先保存当前i位置的元素值。
  8. array[i] = array[minIndex];:将找到的最小元素值放到已排序序列的末尾,即i位置。
  9. array[minIndex] = temp;:将原来i位置的元素放到minIndex位置,完成交换。

选择排序是一种简单的排序算法,但它不是稳定的排序算法,因为它可能会改变相同元素的顺序。选择排序的时间复杂度在最坏和平均情况下都是O(n^2),其中n是数组的长度。尽管选择排序在理论上不如其他O(n log n)的排序算法,它的实现简单,在数据集较小或部分有序的情况下仍然表现良好。

插入排序

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

代码语言:java复制
void insertionSort(int[] array) {
    for (int i = 1; i < array.length; i  ) {
        int key = array[i];
        int j = i - 1;
        while (j >= 0 && array[j] > key) {
            array[j   1] = array[j];
            j--;
        }
        array[j   1] = key;
    }
}

  针对如上示例代码,这里我给大家详细的代码剖析下,以便于帮助大家理解的更为透彻,帮助大家早日掌握。

这段Java代码实现了插入排序算法。插入排序是一种简单直观的排序算法,它通过构建有序序列,对未排序的数据在已排序的序列中从后向前扫描,找到相应位置并插入。以下是对代码的逐行解释:

  1. void insertionSort(int[] array) {:定义了一个名为insertionSort的方法,它接受一个整型数组array作为参数。
  2. for (int i = 1; i < array.length; i ) {:外层循环从数组的第二个元素开始(索引1),直到数组的最后一个元素。第一个元素(索引0)默认认为是已排序的。
  3. int key = array[i];:将当前遍历到的元素array[i]保存到变量key中,这个元素将被插入到已排序的部分。
  4. int j = i - 1;:初始化j为当前元素的前一个元素的索引,这是开始比较的起始点。
  5. while (j >= 0 && array[j] > key) {:使用while循环从后向前扫描已排序的序列,如果找到第一个小于或等于key的元素,则退出循环。
  6. array[j 1] = array[j];:将当前元素array[j]向后移动一个位置,为key腾出插入空间。
  7. j--;:将索引j向前移动,继续比较前一个元素。
  8. array[j 1] = key;:在找到key应该插入的位置后,将其插入到正确的位置。

插入排序是一种稳定的排序算法,因为它不会改变相同元素之间的顺序。它的平均和最坏情况时间复杂度都是O(n^2),其中n是数组的长度。然而,对于小规模数据或基本有序的数据,插入排序可以表现得非常高效。

插入排序的一个优势是它不需要额外的存储空间(除了变量keyj之外),这使得它是一个就地排序算法。此外,插入排序在排序过程中可以逐步产生部分排序的数组,这在某些应用场景中非常有用。

归并排序

归并排序采用分治法,将数组分为两部分,分别对它们进行排序,然后合并结果。

代码语言:java复制
void mergeSort(int[] array) {
    if (array.length < 2) return;

    int[] left = Arrays.copyOfRange(array, 0, array.length / 2);
    int[] right = Arrays.copyOfRange(array, array.length / 2, array.length);
    mergeSort(left);
    mergeSort(right);

    merge(array, left, right);
}

void merge(int[] array, int[] left, int[] right) {
    int i = 0, j = 0, k = 0;
    while (i < left.length && j < right.length) {
        if (left[i] < right[j]) {
            array[k  ] = left[i  ];
        } else {
            array[k  ] = right[j  ];
        }
    }
    while (i < left.length) {
        array[k  ] = left[i  ];
    }
    while (j < right.length) {
        array[k  ] = right[j  ];
    }
}

  针对如上示例代码,这里我给大家详细的代码剖析下,以便于帮助大家理解的更为透彻,帮助大家早日掌握。

这段Java代码实现了归并排序算法,它是一种分治算法,通过递归地将数组分成更小的部分,然后合并这些部分以生成有序数组。以下是对代码的逐行解释:

  1. void mergeSort(int[] array) {:定义了一个名为mergeSort的方法,它接受一个整型数组array作为参数。
  2. if (array.length < 2) return;:如果数组长度小于2,即数组为空或只有一个元素,则不需要排序,直接返回。
  3. int[] left = Arrays.copyOfRange(array, 0, array.length / 2);:将原数组从开始到中间的部分复制到left数组。
  4. int[] right = Arrays.copyOfRange(array, array.length / 2, array.length);:将原数组从中间到结束的部分复制到right数组。
  5. mergeSort(left);:递归调用mergeSort方法对left数组进行排序。
  6. mergeSort(right);:递归调用mergeSort方法对right数组进行排序。
  7. merge(array, left, right);:调用merge方法将排序后的leftright数组合并回原数组array
  8. void merge(int[] array, int[] left, int[] right) {:定义了merge方法,它将两个已排序的数组leftright合并到原数组array中。
  9. int i = 0, j = 0, k = 0;:初始化三个索引变量ijkij分别用于遍历leftright数组,k用于遍历结果数组array
  10. while (i < left.length && j < right.length) { ... }:当leftright数组中都有元素时,比较left[i]right[j]的值,将较小的元素复制到结果数组array中,并相应地递增索引。
  11. if (left[i] < right[j]) { ... } else { ... }:根据比较结果,复制较小的元素到结果数组,并递增相应的索引。
  12. while (i < left.length) { ... }:当left数组中还有剩余元素时,将它们复制到结果数组中。
  13. while (j < right.length) { ... }:当right数组中还有剩余元素时,将它们复制到结果数组中。

归并排序是一种稳定的排序算法,它不会改变相同元素之间的顺序。它的平均和最坏情况时间复杂度都是O(n log n),其中n是数组的长度。归并排序需要O(n)的额外空间来存储递归调用中创建的临时数组,这使得它在空间复杂度上不如一些就地排序算法高效。然而,归并排序的高效率和稳定性使其在处理大量数据时非常有用。

快速排序

快速排序采用分治法,通过一个划分操作选择一个“基准”元素,将数组分为两个子数组。

代码语言:java复制
void quickSort(int[] array, int begin, int end) {
    if (begin < end) {
        int partitionIndex = partition(array, begin, end);

        quickSort(array, begin, partitionIndex - 1);
        quickSort(array, partitionIndex   1, end);
    }
}

int partition(int[] array, int begin, int end) {
    int pivot = array[end];
    int i = (begin - 1);

    for (int j = begin; j < end; j  ) {
        if (array[j] <= pivot) {
            i  ;
            // Swap elements
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }
    // Swap elements
    int temp = array[i   1];
    array[i   1] = array[end];
    array[end] = temp;

    return i   1;
}

  针对如上示例代码,这里我给大家详细的代码剖析下,以便于帮助大家理解的更为透彻,帮助大家早日掌握。

这段Java代码实现了快速排序算法,它是一种高效的分治算法,通过选择一个“基准”元素并将数组分为两部分,一部分包含比基准小的元素,另一部分包含比基准大的元素。然后递归地在这两部分上重复这个过程。以下是对代码的逐行解释:

  1. void quickSort(int[] array, int begin, int end) {:定义了一个名为quickSort的方法,它接受一个整型数组array以及两个整数beginend作为参数,表示要排序的数组部分的起始和结束索引。
  2. if (begin < end) {:如果起始索引小于结束索引,说明有多个元素需要排序。
  3. int partitionIndex = partition(array, begin, end);:调用partition方法获取基准元素的索引,这是分区操作的一部分。
  4. quickSort(array, begin, partitionIndex - 1);:递归地对基准元素左边的部分进行快速排序。
  5. quickSort(array, partitionIndex 1, end);:递归地对基准元素右边的部分进行快速排序。
  6. }quickSort方法的结束括号。
  7. int partition(int[] array, int begin, int end) {:定义了partition方法,它负责执行分区操作并返回基准元素的最终索引。
  8. int pivot = array[end];:选择基准元素为结束索引end处的元素。
  9. int i = (begin - 1);:初始化索引ibegin - 1i将用于跟踪比基准小的元素的最后一个位置。
  10. for (int j = begin; j < end; j ) { ... }:遍历从beginend - 1的元素。
  11. if (array[j] <= pivot) { ... }:如果当前元素小于或等于基准元素,执行交换操作。
  12. int temp = array[i];:将i位置的元素保存到临时变量temp
  13. array[i] = array[j];:将当前元素array[j]复制到i位置。
  14. array[j] = temp;:将临时变量temp的值复制到j位置,完成交换。
  15. i ;:由于进行了交换,递增i以指向新的比基准小的元素的最后一个位置。
  16. int temp = array[i 1];:在循环结束后,将基准元素array[end]i 1位置的元素交换。
  17. array[i 1] = array[end];:将基准元素移动到它的最终位置。
  18. array[end] = temp;:将i 1位置的元素复制到end位置。
  19. return i 1;:返回基准元素的最终索引。

快速排序的平均时间复杂度为O(n log n),其中n是数组的长度。在最坏情况下,当输入数组已经排序或所有元素相等时,时间复杂度会退化到O(n^2)。然而,通过选择一个好的基准元素(例如使用随机选择或中位数),可以避免最坏情况的发生。

快速排序是不稳定的排序算法,因为它可能会改变相同元素之间的顺序。尽管如此,由于其高效率,快速排序在实际应用中非常广泛。

性能比较

  • 时间复杂度:大多数排序算法的平均时间复杂度为O(n log n),但冒泡排序、选择排序和插入排序在最坏情况下为O(n^2)。
  • 空间复杂度:归并排序需要O(n)的额外空间,而快速排序、选择排序、冒泡排序和插入排序的空间复杂度为O(log n)或O(1)。
  • 稳定性:冒泡排序和插入排序是稳定的,而快速排序和选择排序通常是不稳定的。

总结

不同的排序算法有不同的性能特点和适用场景。开发者应根据实际需求和数据特性选择合适的排序算法。

... ...

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

... ...

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!

***

⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。

0 人点赞