iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >手撕排序之快速排序
  • 576
分享到

手撕排序之快速排序

算法排序算法数据结构 2023-10-25 08:10:31 576人浏览 安东尼
摘要

快排的思想(霍尔版本): 如何实现单趟排序: 先假设key是数列的首元素,然后分别定义left和right,left指向首元素的下一个元素,right指向最后一个元素。 先遍历右边,如果比key小,就停止遍历,如果比key大就right-

快排的思想(霍尔版本):

如何实现单趟排序

先假设key是数列的首元素,然后分别定义left和right,left指向首元素的下一个元素,right指向最后一个元素。

先遍历右边,如果比key小,就停止遍历,如果比key大就right--;注意这是一个while循环

当右边的while循环停止时,进行左边的遍历,如果比key小就left++,如果大就停止记录这个下标。(这也是一个while循环)

当两个while循环都终止时,将left位置的值和right位置的值进行交换。

最后当left和right相等时,注意此时两个相同的下标指向的值与key交换,因为该值一定小于key

单趟排序完成!

该单趟排序的意义:

key元素已经正确到达位置,因为左边都是比他小的元素,右边都是比他大的元素。

单趟排序过后,只有key元素到达了指定位置,我们如何让key左边和右边的元素也一样排好序呢?

这时候我们可以采用二叉树分治的思想,左边也一样找出key,重复相同的算法步骤,右边同样,

控制左右的数据范围

直到分割到只有一个元素为止,这样排序就完成啦。

解答疑问:

为什么最后left和right的值指向同一个元素,那个元素一定小于key元素的值?

(相遇位置的元素为什么一定比key小)

右边先走做到的!

分析两种相遇的情况:

  1. R动L不动,去跟L相遇:相遇位置是L的位置。L和R在上一轮交换过,交换以后L 的位置比key小。
  2. L动R不动,去跟R相遇:R先走,找大比key小的,已经停下来,这是L找大没有找到,跟R相遇了,相遇位置比key小。

总而言之,不论哪种相遇情况,相遇的位置总是比key小。

利用递归思路实现快排的一些坑点:

  1. 找大和找小的判断条件容易出错。如果不写等号,left和right的值都可能指向等于key的元素,导致一直无效交换key,造成死循环。加上等号后,还要加上判断条件左值要小于右值,以免极端条件出现,一直--或++,造成越界
  2. 最后交换key元素的值,我们应该记录key元素的下标,而不是key,因为key只是一个局部变量,交换key的值并不影响数组中的顺序。

利用递归思路实现快排

优化版本:(三数取中)

该快排在理想的情况下,时间复杂度是O(N*logN)。

何为理想状态?

每次key的值大小顺序都在数列的中间左右

就是最后key每次交换元素都在数列的中间,明显的二分,也就造成了logN的算法。

但也有可能key的值很极端,在两头,如果每次都这样,(基本有序的情况)算法就变成了N^2.

为了尽量避免这种情况出现,我们为key加上一层保险,尽量保证key的值不是那么极端。

下面加上三数取中的算法~

优化版本代码:

//三数取中int GetMidi(int* a, int left, int right){int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[right] > a[mid])return mid;else{if (a[left] < a[right])return right;elsereturn left;}}else{if (a[right] < a[mid])return mid;else{if (a[left] > a[right])return right;elsereturn left;}}}int PartSort1(int* a, int left, int right){int midi = GetMidi(a, left, right);Swap(&a[midi], &a[left]);int keyi = left;while (left= a[keyi]){right--;}//再从左边找大while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[keyi], &a[left]);return left;}//利用递归的思路实现void QuickSort(int* a, int left, int right){if (left >= right)return;int mid = PartSort1(a, left, right);QuickSort(a, left, mid - 1);QuickSort(a, mid + 1, right);}

实现挖坑法:

挖坑法本质上与递归是一个思路,只不过在思想上做了优化。

有人觉得上一个方法难以理解,不知道为何最后交换的值一定比key小。

所以诞生了挖坑法。

所谓挖坑法,也是先从左边找到一个key,取出key元素,使得key位置形成一个坑,从右边开始遍历,当比key小时,就将右边的值填左边的坑位,此时右边又形成一个坑位,再从左边遍历,找到比key大的,填到右边的坑位,左边又形成了坑位,以此类推……

直到左右遍历到同一个坑位时,将最先取出key的元素放到这个坑位,这样,单趟排序就完成了,然后也是递归,形成完整的排序。

总结挖坑法是上一个方法的思想的一个优化~

挖坑法代码:

// 快速排序挖坑法int PartSort2(int* a, int left, int right){//记录洞的下标int midi = GetMidi(a, left, right);Swap(&a[midi], &a[left]);int holei = left;//记录需要排序元素的值int ret = a[left];while (left < right){//右边先走,找小,填到左边的坑,右边形成新的坑位while (left < right && a[right] >= ret){right--;}a[holei] = a[right];holei = right;//左边再走,找大,填到右边的坑,左边形成新的坑位while (left < right && a[left] <= ret){left++;}a[holei] = a[left];holei = left;}a[holei] = ret;return holei;}

前后指针法:

前后指针法也是递归这条路上衍生出的新思想。

总体思路:

定义前后指针,cur和prev,前指针prev指向key元素,然后cur元素指向key元素的下一个元素

cur先走,无论cur下标指向元素大小是大于还是小于key元素,cur都要向后遍历。

但对于prev前指针,如果cur指向的元素大于key,prev不动,如果cur指向的元素小于key元素,

若小于,则prev指针先向后移一位并与cur指向的内容与prev指向的内容交换,然后cur指针++。

最后循环结束的条件就是cur指针指向的元素已经超出数列范围,然后将prev指向的元素与key交换。

为何prev指向的元素一定比key元素小?

因为prev当前指向的元素已经是交换过的,之前是cur指向的元素比key小。

本质是把一段大于key的区间,推箱子似的往右推,同时小的甩到左边去。

原码:

//快速排序前后指针法int PartSort3(int* a, int left, int right){int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int key = a[left];int prev = left;int cur = left + 1;while (cur <= right){if (a[cur] < key){prev++;Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[left], &a[prev]);return prev;}

上面介绍了利用递归完成快排的三种思想,下面来介绍非递归算法实现快排。


利用非递归完成快排

思想:

因为递归函数中的栈帧是创建在操作系统中的栈上,而栈上的空间较小,一般递归5000次左右,就会报错——StackOverflow(经典的栈溢出错误),所以我们不能过于依赖递归,我们还需要掌握如何将一个递归的算法转换成非递归的算法。

思路:

我们用非递归写程序时,一般会借助到数据结构中的栈和队列,此次我们利用栈来完成非递归的快排。

提醒:

我们只需要知道数列的下标,就可以进行单趟排序,所以压栈和出栈的操作对象就是数列的两个元素的下标,并不是将整个数列进行压栈!

我们需要将数列的首元素和尾元素的下标压栈,然后分别出栈,使这两个元素作为partsort的参数,接着返回keyi元素,也就是下标,接着再压栈[left,keyi-1]  [keyi+1,right],直到栈为空,循环结束。

原码:

void QuickSortNonR(int* a, int left, int right){//注意操作的都是下标ST st;SLInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int keyi = PartSort1(a, begin, end);//[left,keyi-1]  [keyi+1,right]//根据栈的特性,想要从左到右遍历,我们先放右区间,再放左区间if (keyi+1 < end){STPush(&st, right);STPush(&st, keyi + 1);}if (keyi-1 > begin){STPush(&st, keyi - 1);STPush(&st, left);}}SLDestroy(&st);}

来源地址:https://blog.csdn.net/hanwangyyds/article/details/133616362

--结束END--

本文标题: 手撕排序之快速排序

本文链接: https://www.lsjlt.com/news/442155.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 手撕排序之快速排序
    快排的思想(霍尔版本): 如何实现单趟排序: 先假设key是数列的首元素,然后分别定义left和right,left指向首元素的下一个元素,right指向最后一个元素。 先遍历右边,如果比key小,就停止遍历,如果比key大就right-...
    99+
    2023-10-25
    算法 排序算法 数据结构
  • java 排序算法之快速排序
    目录简单介绍基本思想思路分析代码实现推导实现完整实现大数据量耗时测试性能分析简单介绍 快速排序(Quicksort) 是对 冒泡排序的一种改进。 基本思想 快速排序算法通过多次比较和...
    99+
    2022-11-12
  • 【数据结构--八大排序】之快速排序
    💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 ἴ...
    99+
    2023-10-26
    数据结构
  • 比较排序之快速排序(实例代码)
    快速排序(简称快排)因为其效率较高(平均O(nlogn))经常在笔试题中对其考查。对于快排的第一步是选取一个“基数”,将会用这个“基数”与其它数进行比较交换。而这个“基数”的选择将影响到快排的效率如何,但如果为了选择基数而选择基数则会本末倒...
    99+
    2023-05-31
    快速排序 java 比较排序
  • C语言常见排序算法之交换排序(冒泡排序,快速排序)
    目录前言1.交换排序——冒泡排序1.1 算法思想1.2 动图演示1.3 冒泡最好的情况 2. 交换排序——快速排序...
    99+
    2022-11-13
  • JAVA十大排序算法之快速排序详解
    目录快速排序问题思路荷兰国旗问题代码实现时间复杂度算法稳定性总结快速排序 快速排序是对冒泡排序的一种改进,也是采用分治法的一个典型的应用。JDK中Arrays的sort()方法,具体...
    99+
    2022-11-12
  • 数据结构与算法之手撕排序算法
    目录前言为什么要学习排序算法?一.排序的概念及其应用1.1排序的概念1.2排序运用1.3 常见的排序算法二.排序算法分类1.插入排序1.1基本思想:1.2直接插入排序:1.3 希尔排...
    99+
    2023-05-16
    Java数据结构与算法 Java排序算法 数据结构与算法 排序算法
  • Java 快速排序
    快速排序是一种常用的基于比较的排序算法,其时间复杂度为 O(nlogn),并且具有稳定性和广泛的应用场景。本文将全面详细的讲解一下 Java 中快速排序算法的原理、实现以及时间复杂度等问题。 一、快速...
    99+
    2023-09-06
    java 排序算法 算法
  • PHP 快速排序
    描述 快速排序算法是对冒泡排序算法的改进,其基本思想是通过设置一个初始的中间值,来将需要排序的数组分成3部分:小于中间值的左边数组,中间值,大于中间值的右边数组,使用递归用相同的方式来排序左边和右边,最后合并数组。 示例 function ...
    99+
    2023-09-18
    php 算法 排序算法
  • 【Java】快速排序
    文章目录 一、什么是快速排序二、基准元素的选择1、选择第一个元素2、随机选择 三、元素的交换1、双边循环法2、单边循环法 一、什么是快速排序 快速排序是由冒泡排序演变而来,比冒泡排序更快的排序算法。之所以快,是因为快速排...
    99+
    2023-08-17
    java 排序算法 算法 学习 开发语言
  • 快速掌握java排序算法-快速排序(图文)
    概念快速排序属于交换排序,主要步骤是使用基准元素进行比较,把小于基准元素的移动到一边,大于基准元素的移动到另一边。从而把数组分成两部分,然后再从这两部分中选取出基准元素,重复上面的步骤。过程如下:(推荐视频:java视频教程) 紫色:基准...
    99+
    2017-05-20
    java教程 快速排序 算法
  • golang归并排序,快速排序,堆排序的实现
    归并排序 归并排序使用经典的分治法(Divide and conquer)策略。分治法会将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得...
    99+
    2022-11-13
  • Java综合整理堆排序 快速排序 归并排序
    目录堆排序快速排序递归非递归归并排序递归非递归堆排序 时间复杂度:0(N*log(N))空间复杂度:0(1)稳定性:不稳定 private static void heapSort...
    99+
    2022-11-13
  • Python3实现快速排序、归并排序、堆
    # -*- coding: utf-8 -*- # @Time : 2019-03-26 16:46 # @Author : Jayce Wong # @ProjectName : leetcode # @Fi...
    99+
    2023-01-31
    快速
  • 排序算法图解之Java快速排序的分步刨析
    目录1.快速排序简介2.思路简介及图解3.实现代码及运行结果1.快速排序简介 快速排序是对冒泡排序的一种改进。基本思想为:通过一趟排序将要排序的数据分割为独立的两个部分,其中一部分的...
    99+
    2022-11-13
    Java快速排序算法 Java快速排序 Java 排序
  • Java的堆排序、快速排序、归并排序怎么实现
    本文小编为大家详细介绍“Java的堆排序、快速排序、归并排序怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java的堆排序、快速排序、归并排序怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。堆排序...
    99+
    2023-06-26
  • 图解Java排序算法之快速排序的三数取中法
    目录基本步骤三数取中根据枢纽值进行分割代码实现总结基本步骤 三数取中 在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。在此我们采用三数取中法,也就是...
    99+
    2022-11-12
  • Java排序算法怎么快速上手
    本篇内容主要讲解“Java排序算法怎么快速上手”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java排序算法怎么快速上手”吧!插入排序插入排序的基本思想:每步将一个待排序元素,按其排序码大小插入...
    99+
    2023-06-27
  • 【C语言】快速排序
    文章目录 一、hoare版本二、挖坑法三、前后指针法四、非递归快排五、快速排序优化1、三数取中选key值2、小区间优化 六、代码测试 一、hoare版本 快速排序是Hoare于...
    99+
    2023-10-24
    c语言 数据结构 算法
  • Java中的快速排序
    快速排序的原理快速排序是对冒泡排序的一种改进,冒泡排序是通过一个个比较,从而将小的值放在一端,而大的值放在另外一端,从而达到排序的目的。而快速排序,是先选定一个临界值,将比这临界值小的值放在一端,而比临界值大的值放在另外一端。重复上一段方法...
    99+
    2020-02-07
    java教程 Java
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作