快速排序(java细节实现)

目录

快速排序:

Hoare版:

挖坑法

快速排序的优化

快速排序的非递归实现

小结


从小到大排序

快速排序:

基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有 元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

简单来说就是给定一个数组:

选取其中的一个数,以这一个数为分界线, 左边都比这个数小, 右边都比这个数大, 然后分为左边和右边的两组再次进行重复操作. 直到这组数只有一个, 听起来麻烦, 实则简单, 重点是找到区间的基准值, 就是找到 作为每一组分界线的那个数.  

将区间按照基准值划分为左右两半部分的常见方式有:Hoare版和挖坑法.

Hoare版:

选取第一个数A作为分界线, 然后右边从后往前进行遍历, 如果这个数B小于A那么停下来, 然后左边从A后便开始遍历, 如果这个数C大于A停下来,   C和A进行交换, 然后重复上述操作, 知道B和C相遇,(B和C指两边正在走的数字), 相遇之后和第一个数字进行交换位置, 得到以交换后的数字为分界线的左右两组数据. 最后在让左右两组数据重复以上操作.

代码实现:

    /*** 快速排序* 时间复杂度: 最好: O(N*logN)*            最坏:O(N^2)* 空间复杂度:最好:O(logN)*           最坏:O(N)* @param array*/public static void quickSort(int[] array) {quick(array, 0, array.length-1);}private static void quick(int[] array, int start, int end) {if (start > end) {return;}int pivot = partitionHoare(array, start, end);quick(array, start, pivot-1);quick(array, pivot+1, end);}private static int partitionHoare(int[] array, int left, int right) {int tmp = array[left];int i = left;while(left < right) {while(left < right && array[right] >= tmp) {right--;}while(left < right && array[left] <= tmp) {left++;}swap(array, left, right);}swap(array, left, i);return left;}public static void swap(int[] array, int i, int j) {int temp = array[i];array[i] = array[j];array[j] = temp;}

易错点:

1> 判断什么时候停止递归, 当start > end的时候. 这个条件的上一个是: end = start+1;

递归时可能交换也可能不交换, 如图所示用 left 和 right 替换start 和 end , 这次递归之后排序完成之后, start > end, 本次左边的递归结束, 然后开始右边的递归, 最后停止.

2> 在partitionHoare函数中, 在循环条件里面, 需要注意在里面循环也需要增加left < right的条件,假如没有这个条件,那么会出现如下图的情况:  这时候right越来越小, 然后right小于0了, 数组越界报错, 要注意里面循环的条件也要遵循外层循环.  并且left < right 要写在前面

3> 在partitionHoare函数中, 在循环条件里面, 注意为什么当array[i] == tmp时也需要right--或者left++, 因为这样的话才能让循环进行下去, 否则会当值陷入死循环.

举个例子: 如图下图, 如果==跳出循环的话, 6和6进行交换 之后会一直进行这个操作.

4> 为什么需要从右边开始比较移动数据呢. 如果从左边开始会出现什么情况呢?

举个例子:

而我们选择先从右边筛选数据的结果则是正确的.

这就是为什么必须一定要从右边开始进行选择的原因, 就是为了尽量让这个数停在靠左的位置, 把比较大的数放在右边.

挖坑法

原理都差不多, 挖坑法选择了一个数作为基准, 然后挖出这个数A, 然后从左边选择比A小的数B,将B填入坑中, 然后从左边挖出比A大的数C放在右边的坑上,最后当遇到坑后直接将A填入坑中.

        /*** 快速排序* 时间复杂度: 最好: O(N*logN)*            最坏:O(N^2)* 空间复杂度:最好:O(logN)*           最坏:O(N)* @param array*/public static void quickSort(int[] array) {quick(array,0,array.length-1);}private static void quick(int[] array, int start, int end) {if(start >= end) {return;}int pivot = partitionHole(array,start,end);quick(array, start,pivot-1);quick(array, pivot+1,end);}/*** 挖洞快速排序* @param array* @param left* @param right* @return*/private static int partitionHole(int[] array, int left, int right) {int tmp = array[left];while(right>left) {//单独的循环不能一直减到超过最左边的边界while(right > left && array[right] >= tmp) {right--;}array[left] = array[right];while(right > left && array[left] <= tmp) {left++;}array[right] = array[left];}array[left] = tmp;return left;}public static void swap(int[] array, int i, int j) {int temp = array[i];array[i] = array[j];array[j] = temp;}

快速排序的优化

接下来讲一下快速排序的优化.为什么需要优化呢, 因为一组数据如果是逆序或者正序的话, 会出现类似于一个树只有一边的情况, 这样会让栈可能挤爆,我们优化目的让递归次数减少的多一点,然后让每一次分割尽量平均一点.

 三数取中法(降低了树的高度)

举个例子:

这个时候我们正常情况下选择1作为key基准值, 最后它的右子树会很高,

现在我们选择这个数组之后找到其中的left, right, mid三个数据中第二小的, 这样的话分为左右两组, 平均了左右树的高度.我们如何找到一个中位数呢.往下看.

我们得到了一组数据的下标, 然后分为几种情况L下标的值大与R下标的值,

我们判断M中间下标的值分为三种情况, 比L大, 或比R小, 或其他(L和R之间).

很容易理解.

代码实现如下:

    /*** 快速排序优化* 少于一定数时用插入排序* 选择更靠中间的key*/private static int middleNum(int[] array, int left, int right)  {int mid = (left + right) / 2;if(array[left] < array[right]){if(array[mid] > array[right]) {return right;}else if(array[mid] < array[left]) {return left;}else {return mid;}}else {if(array[mid] < array[right]) {return right;}else if(array[mid] > array[left]) {return left;}else {return mid;}}}

这个实现之后, 当我们进行分组到一定层次后, 可以用直接插入的方法减少递归次数, 当分的组数很多时, 越往下越多, 然后最后几组的数据较少, 用直接插入的方法会在一定程度上减少递归次数, 加快运算速度.

最后代码实现如下:

    /*** 快速排序* 时间复杂度: 最好: O(N*logN)*            最坏:O(N^2)* 空间复杂度:最好:O(logN)*           最坏:O(N)* @param array*/public static void quickSort(int[] array) {quick(array,0,array.length-1);}private static void quick(int[] array, int start, int end) {if(start >= end) {return;}if(end - start+1<=15) {insertSort(array, start, end);return;}int index = middleNum(array, start, end);swap(array, index, start);int pivot = partitionHole(array,start,end);quick(array, start,pivot-1);quick(array, pivot+1,end);}public static void insertSort(int[] array, int left, int right) {for(int i = 1+left; i <= right; i++) {int j = i-1;int tmp = array[i];for (;j>=left;j--) {if(tmp < array[j]) {array[j+1] = array[j];}else {break;}}array[j+1] =tmp;}}/*** Hoare版实现* @param array* @param left* @param right* @return*/private static int partitionHoare(int[] array, int left, int right) {int tmp = array[left];int i = left;while(right>left) {//单独的循环不能一直减到超过最左边的边界while(right > left && array[right] >= tmp) {right--;}while(right > left && array[left] <= tmp) {left++;}swap(array,left,right);}swap(array, i, left);return left;}/*** 挖洞快速排序* @param array* @param left* @param right* @return*/private static int partitionHole(int[] array, int left, int right) {int tmp = array[left];while(right>left) {//单独的循环不能一直减到超过最左边的边界while(right > left && array[right] >= tmp) {right--;}array[left] = array[right];while(right > left && array[left] <= tmp) {left++;}array[right] = array[left];}array[left] = tmp;return left;}/*** 快速排序优化* 少于一定数时用插入排序* 选择更靠中间的key*/private static int middleNum(int[] array, int left, int right)  {int mid = (left + right) / 2;if(array[left] < array[right]){if(array[mid] > array[right]) {return right;}else if(array[mid] < array[left]) {return left;}else {return mid;}}else {if(array[mid] < array[right]) {return right;}else if(array[mid] > array[left]) {return left;}else {return mid;}}}

快速排序的非递归实现

给定一组数据, 先第一次找到pivot的值, 然后建立一个栈, 先判断每一边的数是不是大于一个,(如何判断, pivot+1 < e 右边元素大于一个, pivot - 1 > s 左边元素大于一个) ,如果大于一个,  把左边数据的left  和   right放入栈中, 然后再把右边数据的left  和  right放入栈中,反之不放入数据.

然后判断栈空不空, 不空取出两个数r,l 

然后进行第二次调用partition方法来寻找pivot基准值, 然后将每组数据的下标放到栈以此类推.

步骤:

1> 调用partiton方法找到pivot

2> 左边有没有两个元素, 有的话放到栈中

3> 右边同理

4> 如果栈不空的话, 取出r 和 l

代码实现 

    /*** 快速排序的非递归实现* @param array*/public static void quickSortNor(int[] array) {int start = 0;int end = array.length-1;Stack<Integer> stack = new Stack<>();int pivot = partitionHoare(array, start, end);if(pivot-1>start) {stack.push(start);stack.push(pivot-1);}if(pivot+1<end) {stack.push(pivot+1);stack.push(end);}while(!stack.isEmpty()) {end = stack.pop();start = stack.pop();pivot = partitionHoare(array, start, end);if(pivot-1>start) {stack.push(start);stack.push(pivot-1);}if(pivot+1<end) {stack.push(pivot+1);stack.push(end);}}}/*** Hoare版实现* @param array* @param left* @param right* @return*/private static int partitionHoare(int[] array, int left, int right) {int tmp = array[left];int i = left;while(right>left) {//单独的循环不能一直减到超过最左边的边界while(right > left && array[right] >= tmp) {right--;}while(right > left && array[left] <= tmp) {left++;}swap(array,left,right);}swap(array, i, left);return left;}

小结

需要手敲代码,代码中的一些易错点需要认真整理理解

如有不足,望指正.

如有疑问,乐意解答. 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/322731.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python代码自动生成器原理 python 生成器原理

python生成器原理剖析 函数的调用满足“后进先出”的原则&#xff0c;也就是说&#xff0c;最后被调用的函数应该第一个返回&#xff0c;函数的递归调用就是一个经典的例子。显然&#xff0c;内存中以“后进先出”"方式处理数据的栈段是最适合用于实现函数调用的载体&…

51单片机入门:DS1302时钟

51单片机内部含有晶振&#xff0c;可以实现定时/计数功能。但是其缺点有&#xff1a;精度往往不高、不能掉电使用等。 我们可以通过DS1302时钟芯片来解决以上的缺点。 DS1302时钟芯片 功能&#xff1a;DS1302是一种低功耗实时时钟芯片&#xff0c;内部有自动的计时功能&#x…

技术分享 | 京东商品API接口|京东零售数据可视化平台产品实践与思考

导读 本次分享题目为京东零售数据可视化平台产品实践与思考。 主要包括以下四个部分&#xff1a; 1.京东API接口介绍 2. 平台产品能力介绍 3. 业务赋能案例分享 01 京东API接口介绍 02 平台产品能力介绍 1. 产品矩阵 数据可视化产品是一种利用数据分析和可视化技术&…

Ti雷达常用工具

Ti雷达常用工具 名称网站功能雷达开箱界面mmWave Demo Visualizer (ti.com)显示距离谱、RD谱图雷达参数估计mmWaveSensingEstimator根据性能设计估计参数雷达项目资料Embedded Software (ti.com)Ti雷达示例及说明书官方论坛Sensors forum - Sensors - TI E2E support forumsTi…

php傻瓜式搭建tcp及websocket服务

网络编程 随着互联网的快速发展&#xff0c;网络应用程序的需求也越来越高。为了使网页更加丰富有趣&#xff0c;许多网站都开始使用套接字(socket)实现网络的实时通信。而 tcp/ip 协议则常常用于实现此类应用程序。 TCP/IP协议是一种工业标准协议&#xff0c;是互联网使用最…

Cheetah3D for Mac - 轻松打造专业级3D作品

对于追求专业级3D作品的设计师来说&#xff0c;Cheetah3D for Mac无疑是一款不可多得的工具。 这款软件拥有强大的建模、渲染和动画功能&#xff0c;能够满足您在3D设计方面的各种需求。通过简单的操作&#xff0c;您可以轻松构建出复杂的3D模型&#xff0c;并为其添加逼真的材…

QT中的容器

Qt中的容器 关于Qt中的容器类&#xff0c;下面我们来进行一个总结&#xff1a; Qt的容器类比标准模板库&#xff08;STL&#xff09;中的容器类更轻巧、安全和易于使用。这些容器类是隐式共享和可重入的&#xff0c;而且他们进行了速度和存储的优化&#xff0c;因此可以减少可…

# 从浅入深 学习 SpringCloud 微服务架构(七)Hystrix(4)

从浅入深 学习 SpringCloud 微服务架构&#xff08;七&#xff09;Hystrix&#xff08;4&#xff09; 一、hystrix&#xff1a;使用 turbine 聚合所有的 hytrix 的监控数据测试。创建父工程 spring_cloud_hystrix_demo&#xff0c;导入相关依赖坐标。并在父工程 spring_cloud_…

牛客NC97 字符串出现次数的TopK问题【中等 哈希+优先级队列 Java/Go】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/fd711bdfa0e840b381d7e1b82183b3ee 核心 哈希&#xff0c;优先级队列Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返…

AI智能对话系统源码 内置所有支付接口 功能强大 带完整的安装代码包以及安装部署教程

在数字化日益普及的今天&#xff0c;AI智能对话系统已经成为企业与客户沟通的重要桥梁。为了满足市场的需求&#xff0c;罗峰给大家分享一款全新的AI智能对话系统源码&#xff0c;它集成了所有必要的支付接口&#xff0c;功能强大且易于部署。 以下是部分代码示例&#xff1a;…

vue3创建响应式数据ref和reactive的区别

reactive和ref在Vue.js中都是用于创建响应式数据的&#xff0c;但它们之间存在一些区别 定义数据类型不同。ref主要用于定义基本数据类型&#xff0c;如字符串、数字、布尔值等&#xff1b;reactive主要用于定义对象&#xff08;或数组&#xff09;类型的数据&#xff0c;但re…

备考2024年小学生古诗文大会:吃透10道历年真题和知识点(持续)

对上海小学生的小升初和各种评优争章来说&#xff0c;语文、数学、英语的含金量较高的证书还是很有价值和帮助的。对于语文类的竞赛&#xff0c;小学生古诗文大会和汉字小达人通常是必不可少的&#xff0c;因为这两个针对性强&#xff0c;而且具有很强的上海本地特色。 根据往…

MM模块学习一(供应商创建,物料类型的定义及功能)

物料管理流程&#xff1a; 源头&#xff1a;采购需求->采购申请 MRP&#xff1a;物料需求计划。运行物料需求计划的结果&#xff0c;根据物料的性质来判断是外购&#xff08;采购申请&#xff09;或者是生产&#xff08;计划订单->生产订单&#xff09;。 采购申请&am…

Angular基础-搭建Angular运行环境

这篇文章介绍了在Angular项目中进行开发环境搭建的关键步骤。包括node.js安装和配置、安装Angular CLI工具、安装angular-router、创建Angular项目等步骤。这篇文章为读者提供了清晰的指南&#xff0c;帮助他们快速搭建Angular开发环境&#xff0c;为后续的项目开发奠定基础。 …

改进灰狼算法优化随机森林回归预测

灰狼算法&#xff08;Grey Wolf Optimization&#xff0c;GWO&#xff09;是一种基于自然界灰狼行为的启发式优化算法&#xff0c;在2014年被提出。该算法模仿了灰狼群体中不同等级的灰狼间的优势竞争和合作行为&#xff0c;通过不断搜索最优解来解决复杂的优化问题。 灰狼算法…

如何使用ESOP电子作业指导书系统提高工作效率?

在当今工业生产和制造领域&#xff0c;实现作业标准化是提高生产效率、保证产品质量、提升企业竞争力的重要途径。而 ESOP 无纸化指导书系统作为一种创新的技术手段&#xff0c;正逐渐成为实现作业标准化的关键所在。 ESOP 无纸化指导书系统通过数字化的方式&#xff0c;将传统…

docker学习笔记(四)制作镜像

目录 第1步&#xff1a;编辑Dockerfile 第2步&#xff1a;编辑requirements.txt文件 第3步&#xff1a;编辑app.py文件&#xff0c;我们的程序文件 第4步&#xff1a;生成镜像文件 第5步&#xff1a;使用镜像&#xff0c;启动容器 第6步&#xff1a; 启动redis容器、将容器…

MySQL中JOIN连接的实现算法

目录 嵌套循环算法&#xff08;NLJ&#xff09; 简单嵌套循环&#xff08;SNLJ&#xff09; 索引嵌套循环&#xff08;INLJ&#xff09; 块嵌套循环&#xff08;BNLJ&#xff09; 三种算法比较 哈希连接算法&#xff08;Hash Join&#xff09; 注意事项&#xff1a; 工…

93、动态规划-最长回文子串

思路 首先从暴力递归开始&#xff0c;回文首尾指针相向运动肯定想等。就是回文&#xff0c;代码如下&#xff1a; public String longestPalindrome(String s) {if (s null || s.length() 0) {return "";}return longestPalindromeHelper(s, 0, s.length() - 1);…

django中的cookie与session

获取cookie request.COOKIE.GET 使用cookie response.set-cookie views.py from django.http import HttpResponse from django.shortcuts import render# Create your views here. def cookie_test(request):r HttpResponse("hello world")r.set_cookie(lan, py…