C++ 中快排的递归和非递归实现

下面是关于C++中快排的递归和非递归实现的详细攻略。

快速排序

快速排序是一种基于分治的排序算法,其主要思想是将待排序序列划分为三部分,左边是小于等于基准值的部分,右边是大于等于基准值的部分,中间是分界点,基准值一般选取序列的第一个数或者随机选取一个数。然后对左右两个部分递归调用快排算法,直到每个小部分只有一个数或为空。

递归实现

递归实现快速排序的核心是 partition 函数,它负责将待排序数组划分成左右两部分并返回分界点位置。以下是 C++ 代码:

int partition(int nums[], int left, int right) {
    int pivot = nums[left]; // 选取第一个数作为枢轴
    int i = left + 1, j = right;
    while (i <= j) {
        if (nums[i] > pivot && nums[j] < pivot) {
            swap(nums[i++], nums[j--]);
        }
        if (nums[i] <= pivot) i++;
        if (nums[j] >= pivot) j--;
    }
    swap(nums[left], nums[j]);
    return j;
}

void quicksort(int nums[], int left, int right) {
    if (left >= right) return;
    int pivotIndex = partition(nums, left, right);
    quicksort(nums, left, pivotIndex - 1);
    quicksort(nums, pivotIndex + 1, right);
}

partition 函数中,ij 指针分别从左右两端开始扫描,只要 i 所指向的数大于枢轴(即该数应该在右边部分),j所指向的数小于枢轴(即该数应该在左边部分),就将他们交换位置。最终 j就是分界点的位置,将枢轴值与其交换即将数组划分为两部分。

quicksort 函数中的 if (left >= right) return; 是递归终止条件。 pivotIndex是分界点,根据分界点将数组划分为两部分,分别递归调用 quicksort 函数。

非递归实现

快排递归实现算法虽然简单易懂,但是递归调用会占用大量的函数调用栈空间,对于大规模数据来说,如果采用递归实现会导致栈溢出,因此需要采用非递归实现。

非递归实现快速排序需要用到栈,可以手动模拟函数调用栈的操作。以下是 C++ 代码:

void quicksort(int nums[], int left, int right) {
    stack<int> stk;
    if (left < right) {
        stk.push(right);
        stk.push(left);
        while (!stk.empty()) {
            int l = stk.top(); stk.pop();
            int r = stk.top(); stk.pop();
            int p = partition(nums, l, r);
            if (l < p - 1) {
                stk.push(p - 1);
                stk.push(l);
            }
            if (p + 1 < r) {
                stk.push(r);
                stk.push(p + 1);
            }
        }
    }
}

使用栈模拟函数调用栈的过程就是在分治过程中选用堆栈换取递归的便利性。将堆栈初始化为整个序列,然后逐步缩小范围,最终使序列有序。

示例说明

下面用两个示例说明递归和非递归实现快速排序的过程。

假设有一个数组 arr = [3, 1, 5, 2, 4, 6, 7],首先是递归实现:

  1. 选取第一个数 3 作为枢轴,执行 partition 操作后,得到数组状态为 arr = [2, 1, 3, 5, 4, 6, 7],分界点为2,左边是 [2, 1],右边是 [5, 4, 6, 7]
  2. 对左边的 [2, 1],选取第一个数 2 作为枢轴,执行 partition 操作后,得到数组状态为 arr = [1, 2, 3, 5, 4, 6, 7],分界点为1,左边是 [1],右边是 [2, 3, 5, 4, 6, 7]
  3. 对右边的 [2, 3, 5, 4, 6, 7],选取第一个数 2 作为枢轴,执行 partition 操作后,得到数组状态为 arr = [1, 2, 3, 4, 5, 6, 7],分界点为4,左边是 [2, 3, 4], 右边是 [5, 6, 7]

最终数组 arr 已经有序。

再来看非递归实现。按照相同的过程,使用栈来模拟分治的过程:

  1. 选取第一个数 3 作为枢轴,执行 partition 操作后,得到数组状态为 arr = [2, 1, 3, 5, 4, 6, 7],分界点为2,左边是 [2, 1],右边是 [5, 4, 6, 7],将 [5, 4, 6, 7] 和 [2, 1] 两个区间分别入栈
  2. 取出栈顶区间 [5, 4, 6, 7],选取第一个数 5 作为枢轴,执行 partition 操作后,得到数组状态为arr = [2, 1, 3, 4, 5, 6, 7],分界点为5,左边是 [4],右边是 [6, 7]。将右边的区间 [6, 7] 和原来的区间 [5, 4, 6, 7] 继续入栈,栈内状态为 [7, 6, 2, 1]
  3. 取出栈顶区间 [7, 6],选取第一个数 7 作为枢轴,执行 partition 操作后,得到数组状态为 arr = [2, 1, 3, 4, 5, 6, 7],区间已经有序,不需要入栈。取出栈顶区间 [5, 4, 6],选取第一个数 5 作为枢轴,执行 partition 操作后,得到数组状态为 arr = [2, 1, 3, 4, 5, 6, 7],分界点为4,左边是 [4],右边是 [6],将右边的区间 [6] 和原来的区间 [5, 4, 6] 继续入栈。栈内状态为 [6, 2, 1]
  4. 取出栈顶区间 [6],这是一个只有一个元素的区间,不需要排序。

最终数组 arr 已经有序。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++ 中快排的递归和非递归实现 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • Android 模拟器的使用详细介绍

    Android 模拟器的使用详细介绍 Android 模拟器是一种软件工具,它允许开发人员在计算机上模拟 Android 设备的功能和行为。使用 Android 模拟器,开发人员可以在没有实际设备的情况下进行应用程序开发、测试和调试。下面是 Android 模拟器的使用详细攻略。 步骤一:安装 Android 模拟器 首先,确保你的计算机上已经安装了 And…

    other 2023年8月3日
    00
  • python3反转字符串的3种方法(小结)

    现在我将为您详细讲解 “python3反转字符串的三种方法(小结)” 的完整攻略。 一、方法一:使用字符串切片 使用 Python 的字符串切片功能,通过切片操作可以快速地创建新的反转字符串。 以下是使用这种方法的代码示例: str = ‘hello world’ reversed_str = str[::-1] print(reversed_str) 在这…

    other 2023年6月27日
    00
  • ThinkPHP中类的构造函数_construct()与_initialize()的区别详解

    题目要求详细讲解 “ThinkPHP中类的构造函数_construct()与_initialize()的区别详解”,下面针对这个话题,我将从以下几个方面进行详细的讲解: 什么是构造函数和初始化函数 二者的区别 示例说明 构造函数和初始化函数 在介绍二者的区别之前,我们需要了解一下什么是构造函数和初始化函数。 构造函数 构造函数(Constructor Fun…

    other 2023年6月26日
    00
  • Android 12(S) 图形显示系统 – BufferQueue的工作流程(十)

    Android 12(S) 图形显示系统 – BufferQueue的工作流程(十) BufferQueue是Android Framework层中的一个重要组件,负责管理图形缓存,将SurfaceFlinger和应用程序之间的共享缓存提供了一个通道,是实现多个图形应用程序切换和渲染的关键。本篇文章将介绍Android 12(S)中BufferQueue的工…

    其他 2023年3月28日
    00
  • react中定义变量并使用方式

    当在React中定义变量并使用时,有几种常见的方式可以实现。下面是一个详细的攻略,包含两个示例说明。 1. 使用state管理变量 React中的state是一种用于存储和管理组件内部数据的机制。通过使用state,可以在组件中定义变量并在整个组件中使用。 首先,在组件的构造函数中初始化state变量。例如,我们可以定义一个名为count的变量,并将其初始值…

    other 2023年7月29日
    00
  • 易语言调用api枚举网卡名称并且获取信息的代码

    下面是关于“易语言调用API枚举网卡名称并获取信息”的完整攻略。 1. 前提知识 在进行本操作之前,需要了解以下内容: 理解API函数调用的基本原理、参数类型和返回值类型。 理解Windows系统中的网络配置和网卡信息。 掌握基本的Windows网络编程知识。 2. 调用API枚举网卡名称并获取信息 2.1 获取网卡列表 在Windows系统中,我们可以使用…

    other 2023年6月20日
    00
  • 实践讲解SpringBoot自定义初始化Bean+HashMap优化策略模式

    讲解如下: 一、什么是初始化Bean? 初始化Bean是Spring框架中的一种非常重要的概念,它在Spring容器启动时自动执行,并提供一些便利的方法,如初始化某个Bean的属性、预处理一些数据等等。实现初始化Bean需要我们在对应的类中实现InitializingBean接口,并重写afterPropertiesSet()方法。 二、SpringBoot…

    other 2023年6月20日
    00
  • 解决spring boot 配置文件后缀的一个坑

    以下是详细讲解“解决spring boot 配置文件后缀的一个坑”的完整攻略。 背景 在 Spring Boot 项目中,我们通常通过 application.properties 或 application.yml 配置文件来配置项目的属性。然而,在实际开发中,我们可能会遇到一个问题,即当我们的配置文件名称不符合默认规则时,Spring Boot 无法正确…

    other 2023年6月25日
    00
合作推广
合作推广
分享本页
返回顶部