408真题解析-2010-10-数据结构-快速排序

一 真题2010-10

2010-10. 采用递归方式对顺序表进行快速排序。下列关于递归次数的叙述中,正确的是( )。

A. 递归次数与初始数据的排列次序无关

B. 每次划分后,先处理较短的分区可以减少递归次数

C. 每次划分后,先处理较长的分区可以减少递归次数

D. 递归次数与每次划分后得到的分区的处理顺序无关

二 题目要素解析

核心概念快速排序(Quick Sort) 的递归过程。

  • 基本思想:分治法。选取一个枢轴(Pivot),将数组划分为两部分,左部分小于等于枢轴,右部分大于等于枢轴,然后分别对两部分递归排序。
  • 递归树:递归过程可以看作是一棵二叉树。根节点是整个数组,左孩子是左分区,右孩子是右分区。

递归次数的定义

  • 指的是递归函数被调用的总次数(或者递归栈的深度,视具体语境,但在本题选项分析中主要指逻辑上的调用次数)。

关键区别

  • 递归次数 vs 递归深度 vs 时间复杂度

三 哔哔详解

A. 递归次数与初始数据的排列次序无关

错误

  • 若初始序列已有序,且选首元素为基准 → 每次划分极不平衡(如 [1], [2,3,...,n]
  • 递归次数多(接近 nn 次)
  • 若初始序列随机 → 划分较平衡 → 递归次数少(约 log⁡nlogn 层)
  • 结论 :递归次数与初始排列密切相关

B. 每次划分后,先处理较短的分区可以减少递归次数

错误(常见误解)

  • 先处理短分区 是一种工程优化技巧 ,用于减少最大递归深度(栈空间)
  • 但它不改变总的递归调用次数
  • 无论先处理左还是右,最终都要处理两个子分区(只要长度 ≥ 2)
  • 总递归次数不变

💡 举例:

划分后得到 [a](短)和 [b,c,d,e](长)

  • 先排 [a]:递归深度小(因为大分区在栈底)
  • 先排 [b,c,d,e]:递归深度大(可能栈溢出)
  • 总递归调用次数相同
C. 每次划分后,先处理较长的分区可以减少递归次数

错误

  • 同上,处理顺序不影响总次数
  • 反而可能导致栈溢出,是更差的选择
D. 递归次数与每次划分后得到的分区的处理顺序无关

正确

  • 递归次数由需要排序的子问题数量决定
  • 处理顺序(先左后右 or 先右后左)只是执行顺序不同
  • 所有子问题都必须被处理,因此总递归次数不变

✅ 类比:遍历二叉树,先序/中序/后序遍历的节点访问总数相同,只是顺序不同

四 参考答案

D✅

五 考点精析

5.1 快速排序基本概念和性质

5.1.1 基本概念

1. 定义

快速排序基于分治法思想,通过选取一个「枢轴(Pivot)」将待排序数组划分为两部分:左部分元素≤枢轴,右部分元素≥枢轴;然后递归地对左右两部分重复上述过程,直到子数组长度为 1(天然有序),最终整个数组有序。

2. 核心术语
  • 枢轴(Pivot):划分的基准元素,选取方式直接影响快排性能(如选第一个元素、最后一个元素、中间元素、随机元素);
  • 划分(Partition):快排的核心操作,将数组按枢轴分成左右两部分的过程;
  • 递归划分:对划分后的子数组重复划分,直到子数组有序。

5.1.2 基本性质

性质 说明
时间复杂度 - 最好/平均: O(n \\log n) - 最坏: O(n\^2) (如已排序数组选首元素为 pivot)
空间复杂度 - 平均: O(\\log n) (递归栈深度) - 最坏: O(n)
稳定性 ❌ 不稳定(相同元素可能因交换改变相对位置)
原地排序 ✅ 是(仅需 O(1) 额外空间,不计递归栈)
适用场景 大规模数据、通用排序(工业界主流)

💡 优化策略

  • 随机选 pivot → 避免最坏情况
  • 三数取中(median-of-three)
  • 小数组切换为插入排序

5.2 算法思想及代码实现

5.2.1 算法思想(Hoare 分区法)

步骤:
  1. 选基准(Pivot):通常选首、尾、中位数或随机元素
  2. 分区(Partition):
    • 设置左右指针 i, j
    • i 从左向右找 ≥ pivot 的元素
    • j 从右向左找 ≤ pivot 的元素
    • 交换 arr[i]arr[j]
    • 直到 i >= j,返回分割点
  3. 递归排序:
    • [low, pivot_index-1][pivot_index+1, high] 递归调用

关键 :每次 partition 后,pivot 就位于其最终排序位置

5.2.2 代码实现

5.2.2.1 C 语言(经典 Hoare 分区 + 递归)
c 复制代码
#include <stdio.h>

// 分区函数(Hoare 版)
int partition(int arr[], int low, int high) {
    int pivot = arr[low];  // 选首元素为基准
    int i = low - 1;
    int j = high + 1;

    while (1) {
        do { i++; } while (arr[i] < pivot);
        do { j--; } while (arr[j] > pivot);
        if (i >= j) return j;
        // 交换
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

// 快速排序主函数
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi);        // 注意:Hoare 返回 j,左区间为 [low, pi]
        quickSort(arr, pi + 1, high);   // 右区间为 [pi+1, high]
    }
}

// 测试
int main() {
    int arr[] = {10, 80, 30, 90, 40, 50, 70};
    int n = sizeof(arr) / sizeof(arr[0]);
    quickSort(arr, 0, n - 1);
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    return 0;
}

⚠️ 注意:Hoare 分区返回的 pi 与 Lomuto 不同,递归区间也不同

5.2.2.2 Java(Lomuto 分区 + 随机 pivot)
java 复制代码
import java.util.Random;

public class QuickSort {
    private static Random rand = new Random();

    // Lomuto 分区(更易理解)
    private static int partition(int[] arr, int low, int high) {
        // 随机选 pivot 并移到末尾
        int randomIndex = low + rand.nextInt(high - low + 1);
        swap(arr, randomIndex, high);
        
        int pivot = arr[high];
        int i = low - 1;  // 小于 pivot 区的末尾

        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high);  // pivot 归位
        return i + 1;
    }

    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 测试
    public static void main(String[] args) {
        int[] arr = {10, 80, 30, 90, 40, 50, 70};
        quickSort(arr, 0, arr.length - 1);
        for (int x : arr) System.out.print(x + " ");
    }
}

✅ 推荐使用 Lomuto + 随机 pivot,逻辑清晰且避免最坏情况

5.2.2.3 Python(简洁递归版)
python 复制代码
import random

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    # 随机选 pivot
    pivot = random.choice(arr)
    left = [x for x in arr if x < pivot]
    mid = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quick_sort(left) + mid + quick_sort(right)

# 测试
if __name__ == "__main__":
    arr = [10, 80, 30, 90, 40, 50, 70]
    print(quick_sort(arr))

✅ 优点:代码极简;缺点:非原地排序,空间开销大

📌 面试可用,工程慎用(内存敏感场景)

六 考点跟踪

年份 题号 考查内容 CSDN 参考链接 VX参考链接
2010 第10题 快速排序特征
2012 第10题 快速排序特征

说明 :本文内容基于公开资料整理,参考了包括但不限于《数据结构》(严蔚敏)、《计算机操作系统》(汤小丹)、《计算机网络》(谢希仁)、《计算机组成原理》(唐朔飞)等国内高校经典教材,以及其他国际权威著作。同时,借鉴了王道、天勤、启航等机构出版的计算机专业考研辅导系列丛书 中的知识体系框架与典型题型分析思路。文中所有观点、例题解析及文字表述均为作者结合自身理解进行的归纳与重述,未直接复制任何出版物原文。内容仅用于学习交流,若有引用不当或疏漏之处,敬请指正。

相关推荐
鱼跃鹰飞2 小时前
LeetCode热题100:5.最长回文子串
数据结构·算法·leetcode
季明洵2 小时前
力扣反转链表、两两交换链表中的节点、删除链表的倒数第N个节点
java·算法·leetcode·链表
历程里程碑2 小时前
Linux 4 指令结尾&&通过shell明白指令实现的原理
linux·c语言·数据结构·笔记·算法·排序算法
猿小羽2 小时前
Java 架构演进史:从咖啡杯到云原生霸主
java·云原生·架构
chilavert3182 小时前
技术演进中的开发沉思-330 : 虚拟机命令行工具
java·jvm
Java程序员威哥2 小时前
使用Java自动加载OpenCV来调用YOLO模型检测
java·开发语言·人工智能·python·opencv·yolo·c#
亲爱的非洲野猪2 小时前
动态规划进阶:树形DP深度解析
算法·动态规划·代理模式
亲爱的非洲野猪2 小时前
动态规划进阶:其他经典DP问题深度解析
算法·动态规划
啊阿狸不会拉杆2 小时前
《计算机操作系统》第四章-存储器管理
人工智能·算法·计算机组成原理·os·计算机操作系统