快速排序模板及优化

快速排序模板及优化

算法特性

  1. 时间复杂度:O(N*logN)~O(N^2)
    • 最好情况:哨兵划分操作为线性时间复杂度 O(N);递归轮数共 O(logN)
    • 最坏情况:若每轮哨兵划分操作都将长度为 N 的数组划分为长度为 1 和 N - 1的两个子数组,此时递归轮数达到 N轮 。
  2. 空间复杂度:O(N)
  3. 非稳定: 哨兵划分操作可能改变相等元素的相对顺序。

常规:哨兵+递归

  1. 将数据组中最左/右侧的数据作为基准数,将它作为哨兵元素,值小于哨兵的放在其左侧,大于的放在其右侧。

  2. 一轮划分结束后,转换成两个较短数据组内的排序问题。

  3. 对每个短的数据组内递归进行哨兵划分,直到划分得到的子数组长度为1。

  4. 这里的swap函数我们没有使用位运算,因为这种思路的快速排序是非稳定排序,所以无法使用位运算。

        static void quickSort(int[] nums, int l, int r) {
            // 子数组长度为 1 时终止递归
            if (l >= r) return;
            // 哨兵划分操作
            int i = partition(nums, l, r);
            // 递归左(右)子数组执行哨兵划分
            quickSort(nums, l, i - 1);
            quickSort(nums, i + 1, r);
        }
    
        static int partition(int[] nums, int l, int r) {
            // 以 nums[l] 作为基准数
            int i = l, j = r;
            while (i < j) {
                //从左向右找到第一个大于等于哨兵值的元素
                while (i < j && nums[j] >= nums[l]) j--;
                //从右向左找到第一个小于等于哨兵值的元素
                while (i < j && nums[i] <= nums[l]) i++;
                //分别满足两个条件的元素交换位置
                swap(nums, i, j);
            }
            //完成一次哨兵划分后,将哨兵元素放到中间隔开两个短数组
            swap(nums, i, l);
            return i;
        }
    
    
    
    
    
    static void swap(int[] nums, int i, int j) {
        // 交换 nums[i] 和 nums[j]
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
    

算法优化

Tail Call:空间复杂度优化策略
  1. 对于快排的空间复杂度分析,我们可以知道,最坏情况下,整个数组完全按照倒序排列,那么此时划分的递归深度为N。

  2. 如果我们首先完成一次划分后,其余每次只选择两个数组中较短的一个进行递归划分,就能保证最差的递归深度为O(logN),也就是深度最大为N/2级别

       static void quickSort(int[] nums, int l, int r) {
            // 子数组长度为 1 时终止递归
            while (l < r) {
                // 哨兵划分,得到两个数组
                int mid = partition(nums, l, r);
                // 比较两个数组大小,选择长度短的进行递归
                if (mid - l < r - mid) {
                    quickSort(nums, l, mid - 1);
                    //每次结束后变化边界,防止溢出
                    l = mid + 1;
                } else {
                    quickSort(nums, mid + 1, r);
                    r = mid - 1;
                }
            }
        }
    
随机基准:时间复杂度优化策略
  1. 分析影响快排时间复杂度的因素可知,我们默认选择最左侧元素作为基准时,如果整个数组恰好按照倒序排序,那么每轮划分我们能完成N-1与1这种最差情况的划分。

  2. 为了大概率上避免最差情况,我们每轮划分时都可以选择随机的数据作为哨兵元素。

    int partition(int[] nums, int l, int r) {
       
        int ra = (int)(l + Math.random() * (r - l + 1));
        //找到随机基准后,再将其与最左侧元素位置互换,继续按照之前的策略就可以了
        swap(nums, l, ra);
       
        int i = l, j = r;
        while (i < j) {
            while (i < j && nums[j] >= nums[l]) j--;
            while (i < j && nums[i] <= nums[l]) i++;
            swap(nums, i, j);
        }
        swap(nums, i, l);
        return i;
    }
    
    

快排是O(nlogn)级排序算法中效率最高的算法吗?

  1. 最差情况稀疏性: 虽然快速排序的最差时间复杂度为 O(N^2),差于归并排序和堆排序,但统计意义上看,这种情况出现的机率很低。大部分情况下,快速排序以 O(NlogN) 复杂度运行。
  2. 缓存使用效率高: 哨兵划分操作时,将整个子数组加载入缓存中,访问元素效率很高;堆排序需要跳跃式访问元素,因此不具有此特性。
  3. 常数系数低: 在提及的三种算法中,快速排序的 比较、赋值、交换 三种操作的综合耗时最低(类似于插入排序快于冒泡排序的原理)。

作者:Krahets
链接:https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/p57uhr/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java中的快速排序可以使用以下模板写出:public static void quickSort(int[] arr, int low, int high) { if (arr == null || arr.length == 0) return; if (low >= high) return; // 找到分界点 int middle = low + (high - low) / 2; int pivot = arr[middle]; // 将数组分为两部分 int i = low, j = high; while (i <= j) { // 从左向右找到第一个大于等于pivot的数的位置 while (arr[i] < pivot) { i++; } // 从右向左找到第一个小于等于pivot的数的位置 while (arr[j] > pivot) { j--; } // 交换位置 if (i <= j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } // 递归排序左右两部分 if (low < j) quickSort(arr, low, j); if (high > i) quickSort(arr, i, high); } ### 回答2: 快速排序是一种常用的排序算法,它的基本思想是通过将一个数组分成两部分,将比基准值小的元素放在一边,比基准值大的元素放在另一边,然后递归地对两部分进行排序,直到排序完成。 在Java中,可以使用如下的代码实现快速排序: ```java public class QuickSort { public static void main(String[] args) { int[] arr = {5, 2, 8, 3, 9, 1, 6, 4, 7}; quickSort(arr, 0, arr.length - 1); for (int num : arr) { System.out.print(num + " "); } } public static void quickSort(int[] arr, int low, int high) { if (low < high) { int partitionIndex = partition(arr, low, high); quickSort(arr, low, partitionIndex - 1); quickSort(arr, partitionIndex + 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++; swap(arr, i, j); } } swap(arr, i + 1, high); return i + 1; } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } ``` 以上是一个简单的快速排序实现的示例代码。通过递归调用`quickSort`方法来完成排序过程。首先选择一个pivot(基准值),然后通过`partition`方法将比pivot小的元素放在pivot的左边,比pivot大的元素放在pivot的右边。然后继续对左右两部分进行递归排序,直到排序完成。 在上述示例代码中,`partition`方法选取数组最后一个元素作为pivot,并使用双指针的方式将比pivot小的元素移到左边。最后返回pivot的位置,方便后续的递归调用。 需要注意的是,以上示例代码中只是一个简单的实现,没有考虑一些边界条件和性能优化。在实际应用中,可能需要对算法进行更多的优化和改进。 ### 回答3: 快速排序是一种常用的排序算法,其基本思想是通过分治的方法将一个待排序的数组分成两部分,一部分小于等于基准元素,一部分大于基准元素,然后再对这两部分分别进行快速排序,最终得到有序的结果。 具体实现快速排序的步骤如下: 1. 选择一个基准元素,一般是数组的第一个元素。 2. 设定两个指针,左指针指向数组的第一个元素,右指针指向数组的最后一个元素。 3. 左指针向右移动,直到找到一个大于基准元素的值;右指针向左移动,直到找到一个小于基准元素的值;如果左指针位置小于右指针位置,则交换这两个值。 4. 重复步骤3,直到左指针位置大于等于右指针位置。 5. 将基准元素与左指针位置的值进行交换。 6. 分别对左边和右边的子数组进行递归快速排序。 下面是一个示例代码实现: ```java public class QuickSort { public static void quickSort(int[] arr, int left, int right) { if (left >= right) { return; } int pivot = arr[left]; // 设置基准元素 int i = left; int j = right; while (i < j) { while (i < j && arr[j] >= pivot) { j--; } if (i < j) { arr[i] = arr[j]; i++; } while (i < j && arr[i] <= pivot) { i++; } if (i < j) { arr[j] = arr[i]; j--; } } arr[i] = pivot; // 将基准元素放到正确的位置 quickSort(arr, left, i - 1); // 递归快速排序左边的子数组 quickSort(arr, i + 1, right); // 递归快速排序右边的子数组 } public static void main(String[] args) { int[] arr = {5, 2, 9, 1, 3, 6}; quickSort(arr, 0, arr.length - 1); for (int num : arr) { System.out.print(num + " "); } } } ``` 这个示例代码实现了快速排序算法,在`quickSort`方法中,通过设置左右指针来进行元素的交换和分割,最终得到有序的结果。在`main`方法中,我们可以看到对一个数组进行快速排序的使用方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值