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

yizhihongxing

下面是关于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日

相关文章

  • vue 动态添加的路由页面刷新时失效的原因及解决方案

    问题描述: 在使用 Vue.js 动态添加路由时,我们通常会使用 router.addRoutes() 方法实现,但是在这种情况下,动态添加的路由在页面刷新时会失效,导致无法访问相关页面。 原因分析: Vue.js 的路由机制是基于浏览器的 History API 实现的,因此当页面进行刷新时,浏览器会重新发送请求并加载页面,此时如果没有对动态添加的路由进行…

    other 2023年6月26日
    00
  • springboot整合@scheduled定时任务的使用-从精通到陌生…

    SpringBoot整合@Scheduled定时任务的使用-从精通到陌生… SpringBoot的定时任务是非常常用的功能,而@Scheduled注解则是SpringBoot实现定时任务最常用的一种方式。本文将从以下几个方面详细讲解SpringBoot整合@Scheduled定时任务的使用,帮助读者逐渐掌握使用到陌生的整个过程。 一、@Scheduled…

    其他 2023年3月28日
    00
  • 刷机精灵刷机提示1002错误号怎么办?刷机精灵错误号1002解决方法介绍

    刷机精灵刷机提示1002错误号解决方法介绍 什么是刷机精灵1002错误号? 刷机精灵是一款常用的手机刷机工具,可以帮助用户将手机刷成不同的系统版本。当使用刷机精灵时,有时会出现1002错误号,这是因为刷机精灵在执行任务时,发现当前手机连接的电脑或数据线出现问题,无法正常刷机。 刷机精灵1002错误号解决方法 方法一:更换数据线或电脑 刷机精灵在刷机过程中需要…

    other 2023年6月27日
    00
  • Android开发之Socket通信传输简单示例

    下面是针对“Android开发之Socket通信传输简单示例”的完整攻略: 1. 简介 本示例将介绍如何使用Android开发中的Socket通信来进行数据传输,其中Android作为客户端发送数据,Java服务器进行接收和处理数据。 2. 创建服务器端 首先,我们需要在Java中创建一个服务器端,用于接收来自Android客户端的数据。代码如下: impo…

    other 2023年6月27日
    00
  • 荣耀9x开发者选项在哪?荣耀9x打开开发者选项的方法介绍

    下面是详细讲解荣耀9X开发者选项的方法介绍。 什么是开发者选项? 开发者选项是Android系统中的一个设置项,主要为开发者提供了一些高级功能和调试选项。一般情况下,这个选项是隐藏的,需要手动打开。 在荣耀9X手机中,开启开发者选项可以让您更方便地进行一些高级设置和调试操作,例如USB调试、模拟位置、设置绘制边界等。 如何开启荣耀9X的开发者选项? 下面是荣…

    other 2023年6月26日
    00
  • 浅析JavaScript预编译和暗示全局变量

    浅析JavaScript预编译和暗示全局变量 在JavaScript中,预编译是指在代码执行之前,JavaScript引擎会对代码进行一些处理和准备工作。其中一个重要的预编译过程是变量和函数的声明提升。另外,暗示全局变量是一种在严格模式下使用未声明的变量的方式。本文将详细讲解这两个概念,并提供示例说明。 1. JavaScript预编译 JavaScript…

    other 2023年7月29日
    00
  • 快速解决ip地址与网络上的其他系统有冲突不能上网

    快速解决IP地址与网络上的其他系统有冲突不能上网的攻略 当您的IP地址与网络上的其他系统发生冲突时,您可能无法正常上网。这种情况通常是由于网络中存在重复的IP地址引起的。下面是一些解决此问题的步骤: 步骤一:确认IP地址冲突 首先,您需要确认是否存在IP地址冲突。您可以通过以下步骤来检查: 打开命令提示符(Windows)或终端(Mac和Linux)。 输入…

    other 2023年7月30日
    00
  • Linux文件管理方法介绍

    Linux文件管理方法介绍 在Linux系统下,文件管理是非常重要的一部分,本文将介绍Linux下常用的文件管理方法。 使用命令行管理文件 Linux下最基础的文件管理方式就是使用命令行终端进行操作。以下是几个常用的命令: ls 命令 ls命令用于列出指定目录下的文件和子目录。 ls 以上命令列出当前目录下的文件和子目录。 ls -l 以上命令列出当前目录下…

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