lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

快速排序及其实现

快速排序是一种高效而常用的排序算法,它的基本思想是通过选择一个基准元素,将数组分成两个子数组,其中一个子数组中的元素都小于基准元素,另一个子数组中的元素都大于基准元素,然后对这两个子数组递归地进行快速排序,从而实现整个数组的排序。快速排序的平均时间复杂度为O(nlogn),最坏时间复杂度为O(n^2),空间复杂度为O(logn),是一种不稳定的排序算法。

快速排序的原理

快速排序的原理可以用以下图示来说明:

 参考这个示例 https://blog.csdn.net/Czc1357618897/article/details/121600596

 

如图所示,假设我们要对数组[5, 3, 7, 4, 1, 9, 8, 6, 2]进行升序排序,我们可以采用以下步骤:

  • 第一步:选择一个基准元素,例如选择第一个元素5作为基准元素。
  • 第二步:从左到右扫描数组,找到第一个大于基准元素的元素,例如7。
  • 第三步:从右到左扫描数组,找到第一个小于基准元素的元素,例如2。
  • 第四步:交换这两个元素的位置,得到[5, 3, 2, 4, 1, 9, 8, 6, 7]。
  • 第五步:重复第二步到第四步,直到左右扫描的指针相遇,此时将基准元素和相遇位置的元素交换,得到[1, 3, 2, 4, 5, 9, 8, 6, 7]。
  • 第六步:此时,基准元素5已经在数组中的正确位置上,它左边的元素都小于它,右边的元素都大于它。然后对它左边和右边的子数组分别进行快速排序,即递归地重复第一步到第五步。

经过递归地快速排序后,数组就被排成了升序。

快速排序的思想

分治+递归

快速排序的思想

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。

快速排序的思想可以用以下几个步骤来概括:

  • 选择一个基准元素,例如选择第一个元素作为基准元素。
  • 从左到右扫描数组,找到第一个大于基准元素的元素。
  • 从右到左扫描数组,找到第一个小于基准元素的元素。
  • 交换这两个元素的位置。
  • 重复以上步骤,直到左右扫描的指针相遇,此时将基准元素和相遇位置的元素交换。
  • 此时,基准元素已经在数组中的正确位置上,它左边的元素都小于它,右边的元素都大于它。然后对它左边和右边的子数组分别进行快速排序,即递归地重复以上步骤。

 

快速排序的思想本质是:

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,然后再对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以达到整个数据变成有序序列。

快速排序的核心步骤是:

  • 从数列中挑出一个元素,称为 “基准”(pivot)
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)
  • 对左右两个分区递归地进行快速排序

快速排序的本质是二分思想:将一系列数按照某个基准,分为大于它的数和小于它的数,将这两组数分出来后,再用递归的思想使用相同的一段代码再次分数,直到最后完成排序。

快速排序的平均时间复杂度是O (NlogN),最差时间复杂度是O (N^2),空间复杂度取决于实现方式。快速排序是一种不稳定的排序算法,因为它可能会改变相等元素的相对顺序。

快速排序的Java实现

根据上面的原理,我们可以用Java语言来实现快速排序算法。我们需要用一个方法来实现一趟快速排序,即选择基准元素,并将数组分成两个子数组,并返回基准元素在数组中的最终位置。然后我们需要用另一个方法来递归地调用这个方法,对左右子数组进行快速排序。

以下是快速排序的Java代码实现:

// 快速排序 (Java)
public static void quickSort(int[] arr) {
    quickSort(arr, 0, arr.length - 1); // 对整个数组进行快速排序
}

// 对指定范围内的数组进行快速排序
public static void quickSort(int[] arr, int left, int right) {
    if (left < right) { // 如果左边界小于右边界,说明还有至少两个元素
        int pivot = partition(arr, left, right); // 进行一趟快速排序,并返回基准元素的位置
        quickSort(arr, left, pivot - 1); // 对左子数组进行快速排序
        quickSort(arr, pivot + 1, right); // 对右子数组进行快速排序
    }
}

// 进行一趟快速排序,并返回基准元素的位置
public static int partition(int[] arr, int left, int right) {
    int pivot = arr[left]; // 选择第一个元素作为基准元素
    int i = left; // 左指针,从左向右扫描
    int j = right; // 右指针,从右向左扫描
    while (i < j) { // 当左右指针相遇时结束循环
        while (i < j && arr[j] >= pivot) { // 从右向左找到第一个小于基准元素的元素
            j--;
        }
        while (i < j && arr[i] <= pivot) { // 从左向右找到第一个大于基准元素的元素
            i++;
        }
        if (i < j) { // 如果左右指针还没有相遇,交换它们所指向的元素
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    // 循环结束后,将基准元素和左右指针相遇位置的元素交换,并返回相遇位置的索引
    arr[left] = arr[i];
    arr[i] = pivot;
    return i;
}

快速排序的性能分析

快速排序的时间复杂度和空间复杂度如下:

  • 时间复杂度:O(nlogn),其中n是数组的长度。快速排序的时间复杂度取决于每趟快速排序的划分效果,如果每次划分都能将数组均匀地分成两个子数组,那么快速排序的时间复杂度为O(nlogn),其中n是数组的长度,logn是递归的深度。如果每次划分都将数组划分成一个空数组和一个非空数组,那么快速排序的时间复杂度为O(n^2),这是最坏的情况。平均情况下,快速排序的时间复杂度为O(nlogn)。
  • 空间复杂度:O(logn),其中n是数组的长度。快速排序需要使用递归栈来保存每次划分后的左右子数组的边界,递归栈的深度取决于划分效果,最好情况下为O(logn),最坏情况下为O(n),平均情况下为O(logn)。

快速排序是一种不稳定的排序算法,即相等的元素在排序后可能会改变它们的相对顺序。

快速排序的应用场景

快速排序是一种高效而常用的排序算法,它的优点是实现简单,代码易于理解,不需要额外的空间,且平均性能很好。它的缺点是不稳定,且最坏性能很差,对于大规模或者近乎有序的数据不适合。因此,快速排序适合用于中小规模或者无序程度较高的数据,或者作为其他高级排序算法的基础。

总结

本文介绍了快速排序的原理、实现和性能分析,以及它的应用场景。快速排序是一种重要的排序算法,掌握它有助于提高编程能力和算法思维。希望本文对你有所帮助,欢迎留言交流。

posted on 2023-06-28 01:27  白露~  阅读(42)  评论(0编辑  收藏  举报