百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

高级排序算法之快速排序(高效排序)

ztj100 2025-05-02 22:43 28 浏览 0 评论

前言

今天继续算法学习,本次学习的是高级排序之快速排序。本文代码部分存在调用公共方法,可在文章:简单排序算法之冒泡、插入和选择排序-Java实现版高级排序之归并排序、希尔排序。中查找相关方法,另外,本文也提供测试时使用的完整代码,对其他算法不感兴趣,可在文末找到完整源代码。

快速排序

快速排序的本质就是把一个数组划分为两个子数组,然后递归地调用自身为每一个数组进行“快速排序”来实现的。这里就涉及一个问题:如何划分?

在快速排序中,为了划分数组,提出了“枢纽”这个词,它代表待排序序列中的一个数据项。快速排序利用枢纽将数组划分成两部分,一部分(枢纽左侧)是所有小于该枢纽表示的数据项,另一部分(枢纽右侧)则都大于该枢纽表示的数据项(注意,此时左右两侧的数据项是无序的),枢纽放置在左右两侧的中间,此时该枢纽已经处在排序的正确位置上了(枢纽是快速排序所排序的对象,实现“排序”的关键点就是调整枢纽的位置)。通过递归的这样划分,最终出现左侧只有一个数据项(该数据项既是左侧子数组又是枢纽),则结束左侧递归,开始划分右侧数组。。。以此类推。这里又产生了一个问题,如何选择枢纽?

选择枢纽的算法:最简单的选择枢纽算法就是每次递归都选择数组的最后一个数据项做为枢纽(或者选择最左侧的数据项作枢纽)。

代码演示(枢纽始终使用子数组的最右侧数据项)

  private static int partitionIt(int left,int right,int pivot,int[] array){
    int leftPtr = left-1;
    int rightPtr = right;
    while(true){
      // 查找左侧子数组大于枢纽的位置
      while(compare(array[++leftPtr],pivot)<0){

      }
      // 查询右侧子数组小于枢纽的位置
      while(rightPtr>0&&compare(array[--rightPtr],pivot)>0){

      }

      if(leftPtr>=rightPtr){
        break;
      }
      // 交换需要调整位置的两个数据项
      swap(leftPtr,rightPtr,array);
    }
    // 将枢纽挪到两侧中间
    swap(leftPtr,right,array);
    return leftPtr;
  }

  private static void recQuickSort(int left,int right,int[] array){
    if(right-left<=0){
      return;
    }
    // 选择的枢纽
    int pivot = array[right];

    int partition = partitionIt(left,right,pivot,array);
    recQuickSort(left,partition-1,array);
    recQuickSort(partition+1,right,array);
  }

  public static void quickSort(int[] array){
    compareCount=0;
    swapCount=0;
    long start = new Date().getTime();
    recQuickSort(0,array.length-1,array);
    long end = new Date().getTime();
    printResult("快速排序","交换次数",start,end);
  }

运行结果

冒泡排序——比较次数:49995000,交换次数:25153125,耗时:173毫秒

选择排序——比较次数:49995000,交换次数:9987,耗时:65毫秒

插入排序——比较次数:25075792,复制次数:25075793,耗时:42毫秒

归并排序——比较次数:120459,复制次数:267232,耗时:3毫秒

希尔排序——比较次数:233896,复制次数:238266,耗时:5毫秒

对随机序列排序:快速排序——比较次数:165181,交换次数:31700,耗时:3毫秒

对逆序序列排序:快速排序——比较次数:49825881,交换次数:9976,耗时:54毫秒

从运行结果中,可以看到对于随机序列,快速排序算法执行速度是非常快的,和归并排序相同,但给逆序序列排序时,效果则非常差,几乎和选择排序一样的效率了。那这是为什么呢?

根本原因,就在于枢纽的选择上,快速排序期望枢纽能够将数组拆分成两个长度几乎相同的子数组,这样能最优的平衡比较次数和交换次数。对于随机序列,简单的选择最右侧数据项做为枢纽不至于频繁出现左右子数组极度不平衡的情况,而对于逆序序列,则几乎每次划分都是极度不平衡的两个子数组,最终导致较大侧的子数组要被划分更多次。

优化枢纽选择算法

快速排序的最优划分结果是划分的两个数组长度相等,为了达到这个目的,我们每次都要在划分前,先找到子数组的中间数据项吗?显然,不能这么做的,因为很可能你找中值的时间远远大于你排序的时间了。所以,我们只能选择一个实现既不是过于复杂,又比“选择最右侧数据项做为枢纽”的算法更具普适性。

上图所示取枢纽的方法叫“三数据项取中”,即从数组中选择第一个、最后一个以及中间的数据项,从这三个数据项中取出大小在中间的项做为枢纽,在选择过程中,我们实际上也对这三个数据项做了排序,那顺便把分别大于、小于枢纽的那两个数据项也放到正确的位置(即左侧小于枢纽,右侧大于枢纽)。

该方法每次划分都需要至少有三个数据项,所以当子数组项数不大于3个的时候,就可以结束递归划分了,此时的排序可以通过其他排序算法实现,如使用手动排序(待排序的数据项不大于3个,所以手动排序完全可以轻松搞定),也可以使用插入排序(如果使用插入排序,我们甚至可以当数据项不大于10(这个10没有具体意义,你也可以20、30)的时候就可以用插入排序来收尾了)。下面我们用该方法来选择枢纽(用插入排序来收尾),对代码进行修改。

代码演示

  private static int medianOf3(int left,int right,int[] array){
    int center = (left+right)/2;

    if(array[left]>array[center]){
      swap(left,center,array);
    }

    if(array[left]>array[right]){
      swap(left,right,array);
    }

    if(array[center]>array[right]){
      swap(center,right,array);
    }

    swap(center,right-1,array);
    return array[right-1];
  }

  private static void insertSort(int left,int right,int[] array){
    for (int i = left+1; i <= right; i++) {
      int temp = array[i];
      int cur = i;
      // 右移数字,实现cur下标的左侧都是有序的
      while (cur > 0 && compare(temp, array[cur - 1]) < 0) {
        copy(cur-1,cur,array);
        cur--;
      }
      // 如果cur==i则说明数据项已经在正确的位置了,不需要进行交换
      if (cur != i) {
        // 不满足temp<array[cur-1]时,则当前正在排的项temp已经找到正确位置了
        copyData(temp,cur,array);
      }
    }
  }

  private static int partitionIt(int left,int right,int pivot,int[] array){
    int leftPtr = left;
    int rightPtr = right-1;
    while(true){
      // 查找左侧子数组大于枢纽的位置
      while(compare(array[++leftPtr],pivot)<0){

      }
      // 查询右侧子数组小于枢纽的位置
      while(compare(array[--rightPtr],pivot)>0){

      }

      if(leftPtr>=rightPtr){
        break;
      }
      // 交换需要调整位置的两个数据项
      swap(leftPtr,rightPtr,array);
    }
    // 将枢纽挪到两侧中间
    swap(leftPtr,right-1,array);
    return leftPtr;
  }

  private static void recQuickSort(int left,int right,int[] array){

    int size = right-left+1;
    if(size <10){
      insertSort(left,right,array);
    }else{
      int median = medianOf3(left,right,array);
      int partition = partitionIt(left,right,median,array);
      recQuickSort(left,partition-1,array);
      recQuickSort(partition+1,right,array);
    }
  }

  public static void quickSort(int[] array){
    compareCount=0;
    swapCount=0;
    long start = new Date().getTime();
    recQuickSort(0,array.length-1,array);
    long end = new Date().getTime();
    printResult("快速排序","交换次数",start,end);
  }

运行结果

冒泡排序——比较次数:49995000,交换次数:25138570,耗时:170毫秒

选择排序——比较次数:49995000,交换次数:9990,耗时:65毫秒

插入排序——比较次数:25069178,复制次数:25069176,耗时:34毫秒

归并排序——比较次数:120483,复制次数:267232,耗时:2毫秒

希尔排序——比较次数:231598,复制次数:235991,耗时:6毫秒

对随机序列排序:快速排序——比较次数:154857,交换次数:44570,耗时:3毫秒

对逆序序列排序:快速排序——比较次数:188034,交换次数:20067,耗时:1毫秒

从执行结果可以看出,优化过枢纽选择算法后,无论是随机序列排序还是逆序序列排序,排序速度都非常快。

至此,本文结束

完整代码

package team.ngup.study;

import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author zww
 * @date 2022/8/4 10:35
 */
public class SortStudy {
  static final int ARRAY_SIZE = 10000;

  static int compareCount = 0;
  static int swapCount = 0;


  /**
   * 生成随机数数组
   *
   * @return
   */
  public static int[] buildRandomArray() {
    int[] array = new int[ARRAY_SIZE];
    for (int i = 0; i < ARRAY_SIZE; i++) {
      int randomWithThreadLocalRandom = ThreadLocalRandom.current().nextInt(0, 1000000);
      array[i] = randomWithThreadLocalRandom;
    }
    return array;
  }

  /**
   * a和b位置的数据交换
   *
   * @param a
   * @param b
   * @param array
   */
  public static void swap(int a, int b, int[] array) {
    swapCount++;
    int temp = array[a];
    array[a] = array[b];
    array[b] = temp;
  }

  /**
   * 复制 位置 a->b
   * @param a
   * @param b
   * @param array
   */
  private static void copy(int a,int b,int[] array){
    swapCount++;
    array[b] = array[a];
  }

  /**
   * 复制 数值a->位置b
   * @param a
   * @param b
   * @param array
   */
  private static void copyData(int a,int b,int[] array){
    swapCount++;
    array[b]=a;
  }


  /**
   * dataa大于datab返回1,否则返回-1
   *
   * @param dataa
   * @param datab
   * @return
   */
  public static int compare(int dataa, int datab) {
    compareCount++;
    if (dataa >= datab) {
      return 1;
    }
    return -1;
  }

  /**
   * 输出排序结果
   *
   * @param name 排序方法名
   * @param operName 交换/复制
   * @param start 开始时间
   * @param end 结束时间
   */
  private static void printResult(String name,String operName,long start,long end){
    System.out.print(
        name
            + "——比较次数:"
            + compareCount
            + ","+operName+":"
            + swapCount
            + ",耗时:"
            + (end - start)
            + "毫秒");
  }

  /** 冒泡排序 */
  public static void maopao(int[] array) {
    compareCount = 0;
    swapCount = 0;
    // 待排序序列长度,
    int length = array.length;
    long start = new Date().getTime();
    while (length > 0) {
      for (int a = 0; a < length - 1; a++) {
        if (compare(array[a], array[a + 1]) > 0) {
          // 交换位置
          swap(a, a + 1, array);
        }
      }
      // 一次从头到尾的遍历,找出了最右端的值(最大值),这将缩短第二次遍历的序列长度
      length--;
    }
    long end = new Date().getTime();
    // 输出排序结果
    printResult("冒泡排序","交换次数", start, end);
  }

  /** 选择排序 */
  public static void xuanze(int[] array) {
    compareCount = 0;
    swapCount = 0;
    int length = 0;
    long start = new Date().getTime();
    while (length != array.length - 1) {
      // 最小值位置
      int minPosition = -1;
      // 最小值
      int min = array[length];
      for (int i = length + 1; i < array.length; i++) {
        if (compare(array[i], min) < 0) {
          min = array[i];
          minPosition = i;
        }
      }
      // 存在比当前值还要小的值,则进行位置对换,否则无需对调位置
      if (minPosition != -1) {
        // 交换位置
        swap(length, minPosition, array);
      }
      length++;
    }
    long end = new Date().getTime();
    printResult("选择排序","交换次数", start, end);
  }

  /** 插入排序 */
  public static void charu(int[] array) {
    swapCount = 0;
    compareCount = 0;
    // 第一次排序无需比较,所以直接从1开始
    long start = new Date().getTime();
    for (int i = 1; i < array.length; i++) {
      int temp = array[i];
      int cur = i;
      // 右移数字,实现cur下标的左侧都是有序的
      while (cur > 0 && compare(temp, array[cur - 1]) < 0) {
        copy(cur-1,cur,array);
        cur--;
      }
      // 如果cur==i则说明数据项已经在正确的位置了,不需要进行交换
      if (cur != i) {
        // 不满足temp<array[cur-1]时,则当前正在排的项temp已经找到正确位置了
        copyData(temp,cur,array);
      }
    }
    long end = new Date().getTime();
    printResult("插入排序" ,"复制次数", start, end);
  }

  // ------------------高级排序------------------------
  private static void merge(int[] array, int[] workSpace, int lowPtr, int highPtr, int upperBound) {
    int j = 0;
    int lowerBound = lowPtr;
    int mid = highPtr - 1;
    int n = upperBound - lowerBound + 1;

    while (lowPtr <= mid && highPtr <= upperBound) {
      if (compare(array[lowPtr],array[highPtr])<0) {
        copyData(array[lowPtr++],j++,workSpace);
      } else {
        copyData(array[highPtr++],j++,workSpace);
      }
    }
    while (lowPtr <= mid) {
      copyData(array[lowPtr++],j++,workSpace);
    }
    while (highPtr <= upperBound) {
      copyData(array[highPtr++],j++,workSpace);
    }
    for (j = 0; j < n; j++) {
      copyData(workSpace[j],lowerBound+j,array);
    }
  }
  private static void recMergeSort(int[] array,int[] workSpace, int lowerBound, int upperBound) {
    if (lowerBound == upperBound) {
      return;
    }
    int mid = (lowerBound + upperBound) / 2;
    recMergeSort(array,workSpace, lowerBound, mid); // 分割左侧
    recMergeSort(array,workSpace, mid + 1, upperBound); // 分割右侧

    merge(array,workSpace,lowerBound,mid+1,upperBound); // 合并排序
  }

  /**
   * 合并排序
   * @param array
   */
  public static void mergeSort(int[] array){
    compareCount=0;
    swapCount=0;
    int[] workspace = new int[array.length];
    long start = new Date().getTime();
    recMergeSort(array,workspace,0,array.length-1);
    long end = new Date().getTime();
    printResult("归并排序","复制次数",start,end);
  }

  /**
   * 希尔排序
   * @param array
   */
  public static void xierSort(int[] array){
    compareCount=0;
    swapCount=0;

    int inner,outer;
    int temp;
    int h=1;
    // 计算跨度
    while(h<=array.length/3){
      h=h*3+1;
    }
    long start = new Date().getTime();
    while(h>0){
      for(outer=h;outer<array.length;outer++){
        temp = array[outer];
        inner = outer;
        while(inner>h-1 && compare(array[inner-h],temp)>0){
          copy(inner-h,inner,array);
          inner -= h;
        }
        copyData(temp,inner,array);
      }
      h=(h-1)/3;
    }
    long end = new Date().getTime();
    printResult("希尔排序","复制次数",start,end);
  }

  //-------------快速排序---------------------//

  private static int medianOf3(int left,int right,int[] array){
    int center = (left+right)/2;

    if(compare(array[left],array[center])>0){
      swap(left,center,array);
    }

    if(compare(array[left],array[right])>0){
      swap(left,right,array);
    }

    if(compare(array[center],array[right])>0){
      swap(center,right,array);
    }

    swap(center,right-1,array);
    return array[right-1];
  }

  private static void insertSort(int left,int right,int[] array){
    for (int i = left+1; i <= right; i++) {
      int temp = array[i];
      int cur = i;
      // 右移数字,实现cur下标的左侧都是有序的
      while (cur > 0 && compare(temp, array[cur - 1]) < 0) {
        copy(cur-1,cur,array);
        cur--;
      }
      // 如果cur==i则说明数据项已经在正确的位置了,不需要进行交换
      if (cur != i) {
        // 不满足temp<array[cur-1]时,则当前正在排的项temp已经找到正确位置了
        copyData(temp,cur,array);
      }
    }
  }

  private static int partitionIt(int left,int right,int pivot,int[] array){
    int leftPtr = left;
    int rightPtr = right-1;
    while(true){
      // 查找左侧子数组大于枢纽的位置
      while(compare(array[++leftPtr],pivot)<0){

      }
      // 查询右侧子数组小于枢纽的位置
      while(compare(array[--rightPtr],pivot)>0){

      }

      if(leftPtr>=rightPtr){
        break;
      }
      // 交换需要调整位置的两个数据项
      swap(leftPtr,rightPtr,array);
    }
    // 将枢纽挪到两侧中间
    swap(leftPtr,right-1,array);
    return leftPtr;
  }

  private static void recQuickSort(int left,int right,int[] array){

    int size = right-left+1;
    if(size <10){
      insertSort(left,right,array);
    }else{
      int median = medianOf3(left,right,array);
      int partition = partitionIt(left,right,median,array);
      recQuickSort(left,partition-1,array);
      recQuickSort(partition+1,right,array);
    }
  }

  public static void quickSort(int[] array){
    compareCount=0;
    swapCount=0;
    long start = new Date().getTime();
    recQuickSort(0,array.length-1,array);
    long end = new Date().getTime();
    printResult("快速排序","交换次数",start,end);
  }


  public static void main(String[] args) {
    int[] array = buildRandomArray();
    int[] array2 = Arrays.copyOf(array, ARRAY_SIZE);
    int[] array3 = Arrays.copyOf(array, ARRAY_SIZE);
    int[] array4 = Arrays.copyOf(array,ARRAY_SIZE);
    int[] array5 = Arrays.copyOf(array,ARRAY_SIZE);
    int[] array6 = Arrays.copyOf(array,ARRAY_SIZE);
    maopao(array);
    System.out.println();
    xuanze(array2);
    System.out.println();
    charu(array3);
    System.out.println();
    mergeSort(array4);
    System.out.println();
    xierSort(array5);
    System.out.println();
    // 随机数据项 进行快速排序
    System.out.print("对随机序列排序:");
    quickSort(array6);
    System.out.println();
    // 将array6逆序
    int[] array6a = new int[array6.length];
    for(int i=array6.length-1,j=0;i>=0;i--){
      array6a[j] = array6[i];
      j++;
    }
    // 对逆序的数组使用快速排序
    System.out.print("对逆序序列排序:");
    quickSort(array6a);

  }
}


原文格式更佳:高级排序算法之快速排序|NGUP的个人技术博客

相关推荐

30天学会Python编程:16. Python常用标准库使用教程

16.1collections模块16.1.1高级数据结构16.1.2示例...

强烈推荐!Python 这个宝藏库 re 正则匹配

Python的re模块(RegularExpression正则表达式)提供各种正则表达式的匹配操作。...

Python爬虫中正则表达式的用法,只讲如何应用,不讲原理

Python爬虫:正则的用法(非原理)。大家好,这节课给大家讲正则的实际用法,不讲原理,通俗易懂的讲如何用正则抓取内容。·导入re库,这里是需要从html这段字符串中提取出中间的那几个文字。实例一个对...

Python数据分析实战-正则提取文本的URL网址和邮箱(源码和效果)

实现功能:Python数据分析实战-利用正则表达式提取文本中的URL网址和邮箱...

python爬虫教程之爬取当当网 Top 500 本五星好评书籍

我们使用requests和re来写一个爬虫作为一个爱看书的你(说的跟真的似的)怎么能发现好书呢?所以我们爬取当当网的前500本好五星评书籍怎么样?ok接下来就是学习python的正确姿...

深入理解re模块:Python中的正则表达式神器解析

在Python中,"re"是一个强大的模块,用于处理正则表达式(regularexpressions)。正则表达式是一种强大的文本模式匹配工具,用于在字符串中查找、替换或提取特定模式...

如何使用正则表达式和 Python 匹配不以模式开头的字符串

需要在Python中使用正则表达式来匹配不以给定模式开头的字符串吗?如果是这样,你可以使用下面的语法来查找所有的字符串,除了那些不以https开始的字符串。r"^(?!https).*&...

先Mark后用!8分钟读懂 Python 性能优化

从本文总结了Python开发时,遇到的性能优化问题的定位和解决。概述:性能优化的原则——优化需要优化的部分。性能优化的一般步骤:首先,让你的程序跑起来结果一切正常。然后,运行这个结果正常的代码,看看它...

Python“三步”即可爬取,毋庸置疑

声明:本实例仅供学习,切忌遵守robots协议,请不要使用多线程等方式频繁访问网站。#第一步导入模块importreimportrequests#第二步获取你想爬取的网页地址,发送请求,获取网页内...

简单学Python——re库(正则表达式)2(split、findall、和sub)

1、split():分割字符串,返回列表语法:re.split('分隔符','目标字符串')例如:importrere.split(',','...

Lavazza拉瓦萨再度牵手上海大师赛

阅读此文前,麻烦您点击一下“关注”,方便您进行讨论和分享。Lavazza拉瓦萨再度牵手上海大师赛标题:2024上海大师赛:网球与咖啡的浪漫邂逅在2024年的上海劳力士大师赛上,拉瓦萨咖啡再次成为官...

ArkUI-X构建Android平台AAR及使用

本教程主要讲述如何利用ArkUI-XSDK完成AndroidAAR开发,实现基于ArkTS的声明式开发范式在android平台显示。包括:1.跨平台Library工程开发介绍...

Deepseek写歌详细教程(怎样用deepseek写歌功能)

以下为结合DeepSeek及相关工具实现AI写歌的详细教程,涵盖作词、作曲、演唱全流程:一、核心流程三步法1.AI生成歌词-打开DeepSeek(网页/APP/API),使用结构化提示词生成歌词:...

“AI说唱解说影视”走红,“零基础入行”靠谱吗?本报记者实测

“手里翻找冻鱼,精心的布局;老漠却不言语,脸上带笑意……”《狂飙》剧情被写成歌词,再配上“科目三”背景音乐的演唱,这段1分钟30秒的视频受到了无数网友的点赞。最近一段时间随着AI技术的发展,说唱解说影...

AI音乐制作神器揭秘!3款工具让你秒变高手

在音乐创作的领域里,每个人都有一颗想要成为大师的心。但是面对复杂的乐理知识和繁复的制作过程,许多人的热情被一点点消磨。...

取消回复欢迎 发表评论: