【快速排序学习记录】

算法描述

快速排序(Quick Sort)是一种常用的排序算法,它的原理基于分治法(Divide and Conquer)。

快速排序的基本思想是选择一个基准元素(pivot),通过一趟排序将待排序的元素分割成独立的两部分,其中一部分的所有元素都比基准元素小,另一部分的所有元素都比基准元素大。然后对这两部分继续递归地进行快速排序,直到整个序列有序。

  • 算法稳定性

快速排序在进行元素交换的过程中可能改变相等元素的相对顺序。这是因为快速排序是通过不断地将元素分割成两个部分来进行排序,而不考虑相等元素之间的顺序关系。因此,如果需要保持相等元素的相对顺序,应选择稳定的排序算法。

  • 详细流程
  1. 选择基准元素:从待排序的序列中选择一个元素作为基准元素(通常选择第一个或最后一个元素)。

  2. 分割操作:通过一趟排序将序列分割成两个部分,使得左边部分的所有元素都比基准元素小,右边部分的所有元素都比基准元素大。具体操作如下:

    • 定义两个指针:左指针指向序列的起始位置,右指针指向序列的末尾位置。
    • 左指针从左往右移动,直到找到一个大于等于基准元素的元素。
    • 右指针从右往左移动,直到找到一个小于等于基准元素的元素。
    • 如果左指针位置小于右指针位置,则交换左右指针所指向的元素。
    • 重复上述过程,直到左指针位置大于等于右指针位置。
  3. 递归排序:对分割后的两个部分,分别递归地进行快速排序操作,直到每个部分只剩下一个元素或为空。

  4. 合并结果:将排序好的左部分、基准元素和排序好的右部分依次合并起来,得到最终的有序序列。

时间复杂度

快速排序的时间复杂度为平均情况下的O(nlogn),最坏情况下的O(n^2),其中n为待排序序列的长度。它是一种原地排序算法,不需要额外的辅助空间,但是由于递归调用的存在,可能会导致栈溢出问题。为了避免最坏情况下的时间复杂度,可以采用随机选择基准元素或者使用三数取中法来选择基准元素。

算法示例

数组:[7, 2, 1, 6, 8, 5, 3, 4]

步骤1:选择一个基准元素(我们选择第一个元素,7)。

步骤2:对列表进行分割:

  • 从列表的开头开始,设置两个指针,一个指向左边(左指针),一个指向右边(右指针)。
  • 将左指针向右移动,直到找到大于或等于基准元素(2)的元素。
  • 将右指针向左移动,直到找到小于或等于基准元素(4)的元素。
  • 交换左右指针所指向的元素。
  • 重复上述两个步骤,直到左指针大于或等于右指针。
  • 分割步骤完成后,列表变为:[4, 2, 1, 6, 8, 5, 3, 7]

步骤3:递归地对子列表进行排序:

  • 基准元素(7)现在位于正确的排序位置。
  • 对左子列表(小于基准的元素)应用相同的步骤:[4, 2, 1, 6, 5, 3]
  • 对右子列表(大于基准的元素)应用相同的步骤:[8]

步骤4:对剩余的子列表重复上述步骤,直到整个列表排序完成。

最终排序后的列表为:[1, 2, 3, 4, 5, 6, 7, 8]

java代码实现

public class QuickSort {
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // 对数组进行分割,获取基准元素的索引
            int pivotIndex = partition(arr, low, high);
            
            // 递归地对子数组进行排序
            quickSort(arr, low, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, high);
        }
    }

    public static int partition(int[] arr, int low, int high) {
        // 选择最右边的元素作为基准元素
        int pivot = arr[high];
        
        // 较小元素的索引
        int i = low - 1;
        
        for (int j = low; j < high; j++) {
            // 如果当前元素小于或等于基准元素
            if (arr[j] <= pivot) {
                i++;
                // 交换 arr[i] 和 arr[j]
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        
         // 交换 arr[i+1] 和 arr[high](或基准元素)
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;
        
        return i + 1;
    }

    public static void main(String[] args) {
        int[] arr = {7, 2, 1, 6, 8, 5, 3, 4};
        int n = arr.length;

        quickSort(arr, 0, n - 1);

        System.out.println("排序后的数组:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}

总结

  • 优点:
  1. 效率高:快速排序的平均时间复杂度为O(nlogn),使其成为处理大型数据集最高效的排序算法之一。
  2. 原地排序:快速排序在原地进行排序,意味着它不需要额外的内存来执行排序操作。
  3. 缓存友好:快速排序的分割方式通常具有较好的缓存性能,因为它倾向于以顺序方式访问元素。
  • 缺点:
  1. 最坏情况时间复杂度:在最坏情况下,当选择的基准元素不理想且输入数组已经排序或接近排序时,快速排序的时间复杂度可能退化为O(n^2)。可以通过随机选择基准元素或使用“三数取中”等技术来缓解这个问题。
  2. 不稳定排序:快速排序是一种不稳定的排序算法,意味着它不能保证相等元素的相对顺序。如果需要保持相等元素的顺序,应使用稳定的排序算法,如归并排序。
  3. 对基准元素选择的依赖性:快速排序的效率严重依赖于基准元素的选择。选择不好的基准元素可能导致性能下降。可以通过随机选择基准元素或使用“三数取中”等技术来缓解这个问题。

总体而言,快速排序是一种广泛使用的排序算法,因其高效性和原地排序的能力而受到青睐。然而,在选择特定用例时应考虑其最坏情况时间复杂度和不稳定性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沂蒙山旁的水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值