1101. Quick Sort (25)

本文介绍了一种快速查找快排算法中主元的高效方法,通过构建两个表来快速判断每个元素是否满足成为主元的条件。这种方法在时间复杂度上优于传统方法,适用于大规模数据处理。

题目如下:

There is a classical process named partition in the famous quick sort algorithm. In this process we typically choose one element as the pivot. Then the elements less than the pivot are moved to its left and those larger than the pivot to its right. Given N distinct positive integers after a run of partition, could you tell how many elements could be the selected pivot for this partition?

For example, given N = 5 and the numbers 1, 3, 2, 4, and 5. We have:

  • 1 could be the pivot since there is no element to its left and all the elements to its right are larger than it;
  • 3 must not be the pivot since although all the elements to its left are smaller, the number 2 to its right is less than it as well;
  • 2 must not be the pivot since although all the elements to its right are larger, the number 3 to its left is larger than it as well;
  • and for the similar reason, 4 and 5 could also be the pivot.

    Hence in total there are 3 pivot candidates.

    Input Specification:

    Each input file contains one test case. For each case, the first line gives a positive integer N (<= 105). Then the next line contains N distinct positive integers no larger than 109. The numbers in a line are separated by spaces.

    Output Specification:

    For each test case, output in the first line the number of pivot candidates. Then in the next line print these candidates in increasing order. There must be exactly 1 space between two adjacent numbers, and no extra space at the end of each line.

    Sample Input:
    5
    1 3 2 4 5
    
    Sample Output:
    3
    1 4 5
    



题目要求找出序列中的所有x,使得x满足≥前面所有的数,≤后面所有的数,这样的x称为快排中的主元。

为了快速的判断,显然我们需要x左侧的最大值和右侧的最小值,而且他们一直在变动,一个思路是用两个vector或者数组记录每个位置之前最大值、之后最小值,称为maxBefore和minBehind,它们的实现逻辑如下:

①第一个元素没有左侧元素,因此maxBefore[0]=-1作为初始化条件,这样就保证了必然满足。

②最后一个元素没有右侧元素,因此minBehind[N-1]=INF(注意INF>10的9次方)。

③对于中间部分,只需要定义max和min两个变量实时判断赋值,对于maxBefore,在输入过程中完成;minBehind通过一次反向遍历建立。

建立好了两个表,就可以对每个元素进行查询,满足了存入res,如果res规模大于0,则先输出规模,再输出排序后的序列;否则输出0,因为序列为空,因此需要空一行,也就是两个回车符。

代码如下:

#include <iostream>
#include <vector>
#include <stdio.h>
#include <algorithm>

using namespace std;

int main()
{
    int N;
    cin >> N;
    vector<int> maxBefore(N);
    vector<int> minBehind(N);
    int max = -1, min = 1000000001;
    int num;
    maxBefore[0] = max;
    vector<int> nums(N);
    for(int i = 0; i < N; i++){
        scanf("%d",&num);
        nums[i] = num;
        if(num > max){
            max = num;
        }
        if(i == N - 1) break;
        maxBefore[i+1] = max;
    }
    minBehind[N-1] = min;
    for(int i = N - 1; i >= 1; i--){
        int num = nums[i];
        if(num < min){
            min = num;
        }
        minBehind[i-1] = min;
    }
    vector<int> res;
    for(int i = 0; i < N; i++){
        int num = nums[i];
        if(num >= maxBefore[i] && num <= minBehind[i]){
            res.push_back(num);
        }
    }
    if(res.size()){
        printf("%d\n",res.size());
        sort(res.begin(),res.end());
        printf("%d",res[0]);
        for(int i = 1; i < res.size(); i++) printf(" %d",res[i]);
        cout << endl;
    }else{
        printf("0\n\n");
    }
    return 0;
}

package QuickSort; public class Quick { public static void sort(Comparable[] a) { if(a==null&&a.length==0) ; int lo=0; int hi=a.length-1; sort(a,lo,hi); } public static void sort(Comparable[] a, int lo, int hi){ if(lo>=hi){ return; } int p = partition(a,lo,hi); sort(a,lo,p-1); sort(a,p+1,hi); } public static int partition(Comparable[] a, int lo, int hi){ Comparable pivot = a[lo]; int left=lo; int right=hi+1; while(left<right){ while (less(pivot,a[--right])&&left<right){ if(right==lo){ break; } } while (less(a[++left],pivot)&&left<right){ if(left==hi){ break; } } if(left<right){ exch(a,left,right); } } exch(a,lo,right); return right; } public static void exch(Comparable[] v,int i,int j){ Comparable temp=v[i]; v[i]=v[j]; v[j]=temp; } public static boolean less(Comparable a, Comparable b){ return a.compareTo(b) < 0; } } public static void testQuick(Integer[] a){ long startTime = System.currentTimeMillis(); Quick.sort(a); long endTime = System.currentTimeMillis(); System.out.println("快速排序的执行时间为:"+(endTime-startTime)+"毫秒"); } public static void main(String[] args) throws IOException { String FilePath="D:\\idea\\IdeaProjects\\basic-code\\Mysuanfa\\src\\sort.txt"; ArrayList<Integer> list = new ArrayList<>(); BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(FilePath))); String line=null; while((line=br.readLine())!=null){ list.add(Integer.valueOf(line)); } br.close(); Integer[] arr=new Integer[list.size()]; list.toArray(arr); testQuick(arr); } 为什么我测试快速排序的这个方法会报错 at QuickSort.Quick.sort(Quick.java:14) at QuickSort.Quick.sort(Quick.java:15) at QuickSort.Quick.sort(Quick.java:14) at QuickSort.Quick.sort(Quick.java:15) at QuickSort.Quick.sort(Quick.java:14) at QuickSort.Quick.sort(Quick.java:15) at QuickSort.Quick.sort(Quick.java:14) at QuickSort.Quick.sort(Quick.java:15) at QuickSort.Quick.sort(Quick.java:14) at QuickSort.Quick.sort(Quick.java:15) at QuickSort.Quick.sort(Quick.java:14) at QuickSort.Quick.sort(Quick.java:15)
最新发布
10-07
你的程序报错信息显示出现了 **无限递归**,表现为 `StackOverflowError`,从堆栈跟踪可以看出: ``` at QuickSort.Quick.sort(Quick.java:14) at QuickSort.Quick.sort(Quick.java:15) ... ``` 反复在 `sort(a, lo, hi)` 的两个方法之间调用,说明递归深度太深甚至死循环了。这通常是由于 **分区逻辑错误导致无法正确缩小问题规模**,从而使得递归没有有效终止。 --- ### ✅ 问题分析与定位 我们逐段检查代码中的关键点: #### 1. 空数组判断错误 ```java if(a==null&&a.length==0) ; ``` 这里有严重的问题: - 使用了 `&&`,但应该是 `||`(只要为空或长度为0就返回) - 而且这个判断后面是空语句 `;`,即使条件成立也不做任何事! - 所以实际上你并没有提前返回! 👉 正确写法应为: ```java if (a == null || a.length == 0) return; ``` 否则,当传入空数组时,`a.length - 1` 就会出错,或者进入无效递归。 > ❗这是第一个潜在 bug。 --- #### 2. 分区函数 `partition` 存在逻辑错误 重点看这段: ```java Comparable pivot = a[lo]; int left = lo; int right = hi + 1; while (left < right) { while (less(pivot, a[--right]) && left < right) { if (right == lo) break; } while (less(a[++left], pivot) && left < right) { if (left == hi) break; } if (left < right) { exch(a, left, right); } } exch(a, lo, right); return right; ``` ##### ❌ 问题一:`less(pivot, a[--right])` 含义理解反了! 回忆 `less(a,b)` 定义为: ```java return a.compareTo(b) < 0; ``` 即:`less(A, B)` 表示 A < B。 所以: - `less(pivot, a[--right])` → `pivot < a[--right]` → 想找比 pivot **大的素**? - 实际上你应该找比 pivot **小的素**才停下来交换! 标准双指针快排中: - 左边指针找 ≥ pivot 的素 - 右边指针找 ≤ pivot 的素 但你这里是: ```java while (less(pivot, a[--right]) && left < right) ``` → 继续移动 right 直到遇到一个 **不大于 pivot 的值** 才停? 不对!因为 `less(pivot, x)` 成立表示 `pivot < x`,也就是 `x > pivot`,所以这个循环只会跳过比 pivot 大的数,直到找到一个 ≤ pivot 的数才退出 —— 这是对的。 等等,其实方向是对的: - `--right` 往左走,跳过所有 `pivot < a[j]`(即 a[j] > pivot)的情况 - 停下来的时候是 `a[j] <= pivot` - 类似地,`++left` 跳过所有 `a[i] < pivot` 的情况,停下来是 `a[i] >= pivot` 所以理论上没问题……但是注意边界和 `left < right` 判断顺序! ##### ❌ 问题二:`less(...)` 中参数顺序搞反了! 再看这一行: ```java while (less(pivot, a[--right]) && left < right) ``` ⚠️ 注意:`less(pivot, a[--right])` 等价于 `pivot < a[--right]`,即 `a[--right] > pivot` ✔️ 我们希望右指针从右往左,**跳过所有大于 pivot 的素**,碰到小于等于的就停下 —— 是对的。 同理: ```java while (less(a[++left], pivot) && left < right) ``` → `a[++left] < pivot`,只要小于就继续,碰到大于等于就停下 —— 也是对的。 ✅ 所以比较逻辑本身没错。 --- ##### ❌ 问题三:`if(right == lo)` 和 `if(left == hi)` 这些 break 是多余的、破坏逻辑! 这些强行 break 会导致指针还没完成扫描就跳出循环,可能造成 `left` 和 `right` 没有正确相遇,进而导致 `exch(a, lo, right)` 把错误位置作为 pivot 归位,更可能导致递归区间不收敛! 比如: ```java if(right == lo){ break; } ``` 你在 `--right` 时如果到了 `lo` 就 break,那岂不是不能处理 `a[lo]` 自己? 而 `pivot = a[lo]`,右边指针本应该可以走到 `lo` 并结束,但现在直接 break,可能导致 `right` 卡住不动,从而 `left < right` 永远成立,形成死循环或错误划分。 🔴 删除这些无意义的 `if(...) break;`! --- ##### ❌ 问题四:`left` 和 `right` 初始化方式有问题 你设的是: ```java int left = lo; int right = hi + 1; ``` 然后: ```java while (left < right) { --right; // 先减再判断 ++left; } ``` 这是模仿《算法(Sedgewick) 的经典实现,是可以的,但必须保证: - 内层循环不要加额外 `break` - 循环结束后 `right` 指向最后一个 ≤ pivot 的素 - 最后 `exch(a, lo, right)` 把 pivot 放到位 但如果中间插入了强制 break,就会破坏这个机制。 --- ### ✅ 修改建议(完整修复版) 下面是修正后的完整 `Quick.java` 关键部分: ```java package QuickSort; import java.io.*; import java.util.ArrayList; import java.util.Arrays; public class Quick { public static void sort(Comparable[] a) { if (a == null || a.length == 0) return; // 修复:null 或空数组直接返回 sort(a, 0, a.length - 1); } public static void sort(Comparable[] a, int lo, int hi) { if (lo >= hi) return; // 区间只有一个素或无效 int p = partition(a, lo, hi); sort(a, lo, p - 1); sort(a, p + 1, hi); } public static int partition(Comparable[] a, int lo, int hi) { Comparable pivot = a[lo]; int left = lo; int right = hi + 1; while (left < right) { // 右指针:从右向左,找 <= pivot 的素 while (left < right && less(pivot, a[--right])) { // 只要 a[right] > pivot,就继续左移 } // 左指针:从左向右,找 >= pivot 的素 while (left < right && less(a[++left], pivot)) { // 只要 a[left] < pivot,就继续右移 } if (left < right) { exch(a, left, right); } } // 将 pivot 放到正确位置 exch(a, lo, right); return right; } public static void exch(Comparable[] a, int i, int j) { Comparable temp = a[i]; a[i] = a[j]; a[j] = temp; } public static boolean less(Comparable a, Comparable b) { return a.compareTo(b) < 0; } public static void testQuick(Integer[] a) { Integer[] copy = a.clone(); // 避免原数组被修改影响其他测试 long startTime = System.currentTimeMillis(); sort(copy); long endTime = System.currentTimeMillis(); System.out.println("快速排序执行时间:" + (endTime - startTime) + " 毫秒"); } public static void main(String[] args) throws IOException { String FilePath = "D:\\idea\\IdeaProjects\\basic-code\\Mysuanfa\\src\\sort.txt"; ArrayList<Integer> list = new ArrayList<>(); BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(FilePath))); String line; while ((line = br.readLine()) != null) { if (!line.trim().isEmpty()) { list.add(Integer.valueOf(line.trim())); } } br.close(); Integer[] arr = list.toArray(new Integer[0]); System.out.println("排序前数据量:" + arr.length); // System.out.println("排序前前10个:" + Arrays.toString(Arrays.copyOf(arr, Math.min(10, arr.length)))); testQuick(arr); // System.out.println("排序后前10个:" + Arrays.toString(Arrays.copyOf(arr, Math.min(10, arr.length)))); } } ``` --- ### 🔍 总结错误原因 | 错误 | 原因 | |------|------| | 栈溢出(无限递归) | `partition` 返回的位置错误,导致子区间未缩小,递归无法终止 | | `if(a==null&&a.length==0);` | 条件错误且无返回,空数组仍进入递归 | | 强制 `break` 在 `--right==lo` 等处 | 打乱双指针逻辑,导致 `right` 提前停止,无法收敛 | | 没有克隆数组测试 | 若多次测试会有副作用 | --- ### ✅ 如何避免此类问题? 1. **单测试小数据集**:先用 `{3,1,4,1,5}` 测试是否能正常排序 2. **打印日志调试 partition 返回值** 3. **使用 IDE 调试器单步跟踪递归过程** 你可以添加如下调试代码验证 `partition` 是否正常: ```java System.out.println("After partition: " + Arrays.toString(a) + ", pivot index=" + p); ``` --- ### 💡 相关问题建议
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值