Java经典快排思想以及快排的改进讲解

一.经典快排思想

前提条件:给定一个无序数组arr

  1. 取这个数组最后一个数 num 作为标准,将前面部分的数分为两部分,使得<=num的部分在左边,>num的数在右边;
  2. 然后将最后一个数和>num部分的第一个数进行交换,就使得原本在数组最后位置的num找到了正确的位置,它的左边都是比它小的以及和它一样的数,右边都是比它大的数
  3. 回到1,进行递归或迭代,使得所有的数都找到正确的位

二.通过荷兰国旗问题改进快排

什么是荷兰国旗问题?

已知一个整形数组arr,和一个整数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。

解决思路:

遍历数组

  • 1. 若比num小,当前位置和小于的最后一个位置+1的值交换,并当前位置++;
  • 2. 若比num大,当前位置和大于的第一个位置-1的值交换;
  • 3. 若等于num的值,当前位置++;

附上代码:

public static void NetherlandsFlag(int[] arr, int L, int R, int num) {
 int i = L;
 int p1 = L-1;
 int p2 = R+1;
 //终止条件:当前数的位置在大于区的前一个
 while(i < p2) {
  if(arr[i] < num) {
   //当前数比num小,放左边,i位置上的数和L上的数进行交换,并且i++,L++
   swap(arr, i++, ++p1);
  } else if(arr[i] == num) {
   //当前数和num相等,i++
   i++;
  } else {
   //当前数比num大,放右边,i位置上的数和R上的数进行交换,并且i++,R--
   swap(arr, i, --p2);
  }
 }
}

我们可以发现,荷兰国旗问题和经典快排不同的就只是将<=num改为了< num和=num两部分,借用这个思想使得原来每次只可以让一个数找到正确的位置改进为了每次至少让一个数找到位置。

三.在这基础上将其改为随机快排

随机快排改进的地方只是在选取数的时候,将每次都选取最后位置的数改为选取随机的一个数作为num,这样做的好处是什么呢?

1.选取最后一个数:如果是一个已经排好序的数组,每次找到位置之后,左边是要进行排序的部分,数组长度是原长度-1,它的时间复杂度就是O(N^2);如果每次找到的数都是中间的位置,它的时间复杂度就只有O(logN)

2.然而以随机数作为选取的标准num的时候,因为是随机的,就只能通过数学期望去计算它的时间复杂度,时间复杂度是O(logN)

下面附上最终的快排代码及注释

/*
 * swap(int[] arr, int i, int j);是将arr数组的i和j位置上的数交换的方法
 */
public static void quickSort(int[] arr) {
 // 如果为空或长度为1不需要排序,直接返回
 if(arr == null || arr.length < 2)
  return;
 else
  quickSort(arr, 0, arr.length - 1);
}
// 递归排序
public static void quickSort(int[] arr, int L, int R) {
 if(L < R) {
  /*
   * 随机快排的随机就在这
   * 是随机选取了一个数,和 R 进行了交换,然后使用这个数作为num,
   * 所以每次选取的num是随机的,
   * 在计算时间复杂度时,是没有最优最差情况的
   * 而是通过一个长期的数学期望计算的,结果是O(N*logN)
   */
  swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
  int[] border = partition(arr, L, R);
  // 小于区和大于区进行递归
  quickSort(arr, L, border[0] - 1);
  quickSort(arr, border[1] + 1, R);
 }
}
// 将给定数组划分为小于区、等于区和大于区
public static int[] partition(int[] arr, int L, int R) {
 int num = arr[R];
 int less = L - 1;
 int more = R + 1;
 int curr = L;
 // 分为小于区等于区和大于区
 while(curr < more) {
  if(arr[curr] < num) {
   swap(arr, curr++, ++less);
  } else if(arr[curr] > num) {
   swap(arr, curr, --more);
  } else {
   curr++;
  }
 }
 //返回等于区的左右边界的下标,通过下标确定小于区和大于区递归时的参数
 return new int[] {less + 1, more - 1};
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

(0)

相关推荐

  • Java语言实现非递归实现树的前中后序遍历总结

    前言 三种遍历的递归写法都很好写,所以总结一下非递归写法. 先贴一张图复习一下三种遍历方式就进入正文啦~ [注:本文所有代码实现中树的结点定义如下: public class Node { int val; Node left; Node right; Node parent; Node() {} Node(int val) { this.val = val; } } 1.前序遍历 实现思路: 前序遍历的顺序是:根结点 -> 左孩子 -> 右孩子 借助一个栈结构先将根结点压入栈,然后循环每次取

  • 原生javascript实现连连看游戏

    本文实例为大家分享了js实现连连看游戏的具体代码,供大家参考,具体内容如下 <!DOCTYPE html> <html> <head> <title>连连看</title> <meta charset="gbk"> <style type="text/css"> body { text-align: center; } .text {text-align: center; margi

  • JavaScript实现动态添加、移除元素或属性的方法分析

    本文实例讲述了JavaScript实现动态添加.移除元素或属性的方法.分享给大家供大家参考,具体如下: JavaScript 动态添加.移除元素 appendChild(newnode) 向节点的子节点列表的末尾添加新的子节点. insertBefore(newnode, existingnode) 在已有子节点之前插入新的子节点. removeChild(node) 删除元素的某个指定的子节点,并以 Node 对象返回被删除的节点,如果节点不存在则返回 null. innerHTML 属性设置

  • Java二叉树的遍历思想及核心代码实现

    二叉树在计算机中的存储方式往往线性结构,线性存储分为顺序存储和链式存储,将二叉树按层序编号. 顺序结构:按编号的顺序进行存储,对于完全二叉树而言,顺序存储可以反映二叉树的逻辑,但是对于大多数的二叉树则无法反映其逻辑关系,不过可以用 ^ 来代替不存在的结点,但是如果这个树是一个右斜树,就非常浪费存储空间.所以二叉树的存储形式一般为链式存储结构. 链式存储:每一个结点都分有一个数据域(data)和两个指针域(lchild和rchild),指针域分别指向左孩子和右孩子,若为空则为null.遍历方式有四

  • Java排序算法之堆排思想及代码实现

    在介绍堆排序前,我们需要了解一下一种数据结构 -- 顶堆. 什么是顶堆? 它是一颗完全二叉树,顶堆有大顶堆和小顶堆两种.所谓大顶堆就是在这颗完全二叉树中,任何一颗子树都满足:父结点的值 > 孩子结点的值:小顶堆则相反. 如图: 什么是堆排序(Heapsort)? 利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种.可以利用数组的特点快速定位指定索引的元素. 现在给我们一个无序数组,我们将其从小到大排序,使用堆排序的实现步骤和思想如下: 1.让这个数组变成一个大根堆 2.将最后一

  • JavaScript键盘事件常见用法实例分析

    本文实例讲述了JavaScript键盘事件常见用法.分享给大家供大家参考,具体如下: JavaScript 键盘事件有以下3种 keydown 键盘按键按下(如果按着不放,会持续触发该事件),可以捕获组合键. keypress 键盘非功能按键按下(在keydown之后触发,如果按着不放会持续触发该事件),只能捕获单个键. keyup 键盘按键弹起,可以捕获组合键. 全局事件对象event event.ctrlKey 功能键"ctrl"键是否按下. event.altKey 功能键&qu

  • Java计算器核心算法代码实现

    在进行一个表达式的计算时,先将表达式分割成数字和字符串然后利用出入栈将分割后的表达式进行中缀转后缀,再将后缀表达式进行计算得到结果(思想在上一篇写过)现在贴下Java语言的代码实现.(学习Java时间不长所以可能会有很多不足的地方,我会改进也欢迎大神可以给我一些意见和建议~谢谢啦) 我将这部分分成三个方法完成功能,并在getResult方法调用(getResult方法被主方法调用) private String getResult(String str) { //分割 String[] Str

  • Java多边形重心计算

    多边形重心计算 三角形重心 顶点为a,b,c的三角形重心为x = (xa + xb + xc) / 3,y = (ya + yb + yc) / 3 多边形重心 x = (x1w1 + x2w2 + - + xnwn)/W y = (y1w1 + y2w2 + - + ynwn)/W import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Polygon; import org.locationt

  • java连连看游戏菜单设计

    本文实例为大家分享了java连连看游戏菜单的具体实现代码,供大家参考,具体内容如下 先写GUI. 首先初始化框架,菜单,按钮,需要把菜单和按钮都添加在框架中.注意添加的顺序,首先要设置菜单,再设置框架,再设置按钮,如果交换了设置菜单和框架的顺序,会导致菜单显示不出,被框架挡住.对菜单设置了三个选项,第一个选项有五个下拉按键,用循环添加,第二个和第三个选项的下拉按键直接添加. GUI代码如下: package gui; import java.awt.Font; import javax.swin

  • 浅谈Java 8 新增函数式接口到底是什么

    从 Java 8 开始便出现了函数式接口(Functional Interface,以下简称FI) 定义为: 如果一个接口只有唯一的一个抽象接口,则称之为函数式接口.为了保证接口符合 FI ,通常会在接口类上添加 @FunctionalInterface 注解.理解了函数式接口可以为 Java 函数式编程打下基础,最终可通过运用函数式编程极大地提高编程效率. 函数式接口 (Functional Interface) 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口. 函数式接口可以对

随机推荐