数据结构和算法之快速排序

2023-09-21 16:33:46

快速排序是一种基于分治法的排序算法。它通过不断地将数组分成较小的子数组,并按照递归的方式对每个子数组进行排序,最终将整个数组排序。

快速排序算法的原理如下:

  1. 从待排序数组中选择一个元素作为枢轴元素。
  2. 将小于枢轴元素的所有元素移动到枢轴元素的左边,大于枢轴元素的所有元素移动到枢轴元素的右边。
  3. 对左子数组和右子数组进行递归调用快速排序算法,直至子数组只剩一个元素或为空。
  4. 递归结束后,整个数组就变得有序。

通过不断地选择枢轴元素并分割数组,快速排序算法可以将数组划分为越来越小的子数组,直到最后将整个数组排序完成。该算法的时间复杂度为O(nlogn)

通常采用双指针法来找到基准元素的正确位置。首先,将左指针指向数组的起始位置,右指针指向数组的末尾位置。然后,左指针从左往右移动,直到找到比基准元素大的元素,并停止。右指针从右往左移动,直到找到比基准元素小的元素,并停止。接着,交换左右指针所指向的元素。重复上述步骤,直到左指针和右指针相遇。最后,将基准元素与左指针所指向的元素进行交换,使得基准元素位于正确的位置

具体实现如下:

// 定义一个交换函数,用于交换数组中的两个元素
function swap(arr, i, j) {
  [arr[i], arr[j]] = [arr[j], arr[i]];
}

// 定义一个分区函数,用于对数组进行分区操作
function partition(arr, left, right) {
  const pivot = arr[right - 1]; // 将分区点设置为最右边的元素
  let i = left,
    j = right - 1; // 初始化指针i和j
  while (i !== j) {
    // 当i和j指针不相遇时,进行循环
    arr[i] <= pivot ? i++ : swap(arr, i, --j); // 如果arr[i]小于等于分区点,则i指针向右移动;否则进行交换,并将j指针向左移动
  }
  swap(arr, j, right - 1); // 将分区点放置在正确的位置
  return j; // 返回分区点的索引
}

// 定义一个快速排序函数
function qsort(arr, left = 0, right = arr.length) {
  if (right - left <= 1) return; // 当要排序的元素个数小于等于1时,直接返回
  const p = partition(arr, left, right); // 进行分区操作,并获取分区点的索引p
  qsort(arr, left, p); // 对分区点左侧的子数组进行快速排序
  qsort(arr, p + 1, right); // 对分区点右侧的子数组进行快速排序
}

const arr = [5, 3, 8, 4, 2, 1, 10, 11, -4, 55];
qsort(arr); // [ -4, 1, 2, 3, 4, 5, 8, 10, 11, 55 ]
console.log(arr);

其他的一下实现方式:

/** 
1. 第一段程序使用的是递归的方式实现快速排序。它选择数组中间的元素作为基准元素,然后将数组分割为左右两个子数组,将比基准元素小的元素放在左子数组,将比基准元素大的元素放在右子数组,然后递归地对左右两个子数组进行快速排序,最后将左右两个子数组和基准元素拼接起来作为排序结果。

2. 第二段程序使用的是基于指针的方式实现快速排序。它通过定义一个分区函数来选择基准元素,并将数组分割为左右两个子数组,然后再递归地对左右两个子数组进行快速排序。分区函数中使用了双指针的方法,从左到右找到第一个大于基准元素的元素,从右到左找到第一个小于基准元素的元素,然后交换它们的位置。这样,最终基准元素的位置就确定下来了,并且左边的元素都小于等于基准元素,右边的元素都大于基准元素。

总的来说,这两段程序的思路是相同的,都是通过不断地将数组分割为更小的子数组并排序,最后再将子数组合并成排序后的结果。它们的实现细节略有不同,但最终的结果是相同的。
*/

//---------------------------------1------------------------------------
function quickSort(arr) {
  // 如果数组长度小于等于1,则直接返回
  if (arr.length <= 1) {
    return arr;
  }

  // 选择一个基准元素
  const pivot = arr[Math.floor(arr.length / 2)];

  // 定义左右两个子数组
  const left = [];
  const right = [];

  // 将元素分割到左右两个子数组
  for (let i = 0; i < arr.length; i++) {
    if (i === Math.floor(arr.length / 2)) {
      continue; // 跳过基准元素
    }
    if (arr[i] <= pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }

  // 递归地对左右两个子数组进行快速排序
  return quickSort(left).concat([pivot], quickSort(right));
}

// 测试
const arr = [5, 3, 8, 4, 2, 1, 10];
const sortedArr = quickSort(arr);
console.log(sortedArr); // 输出 [1, 2, 3, 4, 5, 8, 10]


//---------------------------------2------------------------------------
// 快速排序函数
function quickSort1(arr, left, right) {
  // 递归结束条件
  if (left >= right) {
    return;
  }

  // 设置左右指针及基准值
  let pivot = arr[left];
  let i = left;
  let j = right;

  // 开始一轮排序
  while (i < j) {
    // 从右侧找到比基准值小的元素
    while (i < j && arr[j] >= pivot) {
      j--;
    }

    // 将该元素放到左侧
    arr[i] = arr[j];

    // 从左侧找到比基准值大的元素
    while (i < j && arr[i] <= pivot) {
      i++;
    }

    // 将该元素放到右侧
    arr[j] = arr[i];
  }

  // 将基准值放回数组
  arr[i] = pivot;

  // 递归调用快速排序函数对左右两个子数组进行排序
  quickSort1(arr, left, i - 1);
  quickSort1(arr, i + 1, right);
}

// 测试
let arr2 = [5, 8, 2, 6, 3, 9, 1, 7, 4];
quickSort1(arr2, 0, arr2.length - 1);
console.log(arr2);

更多推荐

CVPR 2023 | UniMatch: 重新审视半监督语义分割中的强弱一致性

在这里和大家分享一下我们被CVPR2023录用的工作"RevisitingWeak-to-StrongConsistencyinSemi-SupervisedSemanticSegmentation"。在本工作中,我们重新审视了半监督语义分割中的“强弱一致性”方法。我们首先发现,最基本的约束强弱一致性的方法FixMat

数据结构:线性表之-队列

目录什么是队列?详解:功能介绍代码实现定义队列基本结构1,初始化2,销毁3,尾入数据4,头出数据5,取队头的数据6,取队尾的数据7,判断是否为空8,计算队列中的元素成品Queue.hQueue.ctest.c队列的讲解将建立在有双向链表的基础之上进行讲解当然队列只是链表的一种分支单项链表详解:#数据结构:线性表之-单向

Spring底层原理之 BeanFactory 与 ApplicationContext

🐌个人主页:🐌叶落闲庭💨我的专栏:💨c语言数据结构javaEE操作系统Redis石可破也,而不可夺坚;丹可磨也,而不可夺赤。Spring底层原理一、BeanFactory与ApplicationContext二、BeanFactory功能三、ApplicationContext功能3.1getMessage3.

【计算机网络】IP协议第一讲(协议格式介绍)

IP协议1.协议头格式1.1概念介绍1.2补充说明1.2.18位生存时间---TTL1.2.216位首部检验和首先明确一个概念:TCP/IP协议是配合使用的,TCP负责可靠传输策略,IP则是负责传输,TCP协议是位于传输层提供的是策略解决可靠性问题,IP协议在网络层,提供的是网络传输服务,实现A主机到B主机的跨网络通信

Python爬虫异常处理实用技巧分享

当我们编写爬虫程序时,经常会遇到各种各样的异常情况,比如网络连接失败、页面解析错误、请求被拒绝等等。这些异常情况可能导致程序中断或者无法正常运行,给我们的数据采集工作带来一定的困扰。所以,掌握一些实用的异常处理技巧对于提高爬虫的稳定性和效率非常重要。在Python中,我们可以使用try-except语句来处理异常。下面

DSOX3012A是德科技keysight DSOX3012A示波器

181/2461/8938是德科技DSOX3012A(安捷伦)示波器是德科技DSOX3012A(安捷伦)是InfiniiVision3000X系列中的双通道型号。这款可升级示波器采用突破性技术设计,提供卓越的性能和功能。其独特的5仪器合一设计为相同的预算提供了更大的范围。是德科技DSOX3012A示波器的特性和规格包括

网络安全深入学习第五课——热门框架漏洞(RCE— Apache Shiro 1.2.4反序列化漏洞)

文章目录一、序列化和反序列化二、反序列化漏洞原理三、ApacheShiro1.2.4反序列化漏洞1、漏洞描述:2、漏洞影响的版本3、Shiro反序列化漏洞原理4、工作原理:5、shiro反序列化的特征:四、ApacheShiro1.2.4反序列化漏洞手工复现1、使用DNSlog严重漏洞是否存在2、VPS监听端口3、构造

【Vue】如何搭建SPA项目--详细教程

🎬艳艳耶✌️:个人主页🔥个人专栏:《Spring与Mybatis集成整合》《springMvc使用》⛺️生活的理想,为了不断更新自己!目录1.什么是vue-cli2.安装2.1.创建SPA项目2.3.一问一答模式答案3.运行SPA项目3.1.导入项目3.2.运行项目4.基于SPA项目完成路由4.1.案例实操5.基于

Python灰帽子编程————网页信息爬取

爬取图片,问题分解:获取网页内容;从网页内容中提取图片地址;通过图片地址,将图片下载到本地。1.相关模块1.1requests模块获取网页内容。requests模块:主要是用来模拟浏览器行为,发送HTTP请求,并处理HTTP响应的功能。importrequests#被认为,最贴近与人的操作的模块importurllib

【C++】C++ 引用详解 ⑥ ( 普通变量 / 一级指针 / 二级指针 做函数参数的作用 )

文章目录一、普通变量/一级指针/二级指针做函数参数的作用1、普通变量做函数参数的作用2、一级指针做函数参数的作用3、二级指针做函数参数的作用4、代码示例-二级指针做函数参数的作用一、普通变量/一级指针/二级指针做函数参数的作用1、普通变量做函数参数的作用普通变量的作用:将普通变量传入函数作为参数,则可以在函数中,访问到

【Java 基础篇】Java 运算符宝典:Java编程的关键

在Java编程中,运算符是用于执行各种操作的特殊符号。它们可以用于操作各种数据类型,执行算术、逻辑和比较等操作。本篇博客将详细介绍Java中常见的运算符,以及它们的使用和示例。算术运算符算术运算符用于执行基本的数学运算,如加法、减法、乘法和除法。加法运算符(+)加法运算符用于将两个值相加,并返回它们的和。示例:inta

热文推荐