快排与双轴快排

2023-05-08 21:40:56 424

传统快排

该方法的基本思想是:

  1. 先从数列中取出一个数作为基准数
  2. 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边
  3. 再对左右区间重复第二步,直到各区间只有一个数

快速排序还有很多改进版本,如随机选择基准数,区间内数据较少时直接用另的方法排序以减小递归深度

qs.png

示例代码如下, 没有进行任何优化操作, 便于更直观的理解

public class ArraysTest {

    public static void main(String[] args) {
        int[] arr = new int[]{5, 6, 1, 3, 9, 8, 0};
        System.out.println(Arrays.toString(sort(arr)));
    }


    public static void sort(int[] arr, int i, int j) {
        if (i < j) {
            int start = i;
            int end = j;
            int pivot = arr[i];

            while (true) {
                while (true) {
                    if (i == j) {
                        break;
                    }
                    if (arr[j] <= pivot) {
                        arr[i] = arr[j];
                        break;
                    }
                    j--;
                }

                while (true) {
                    if (i == j) {
                        break;
                    }
                    if (arr[i] > pivot) {
                        arr[j] = arr[i];
                        break;
                    }
                    i++;
                }

                if (i == j) {
                    break;
                }
            }
            arr[i] = pivot;
            
            sort(arr, start, i - 1);
            sort(arr, i + 1, end);
        }
    }
}

双轴快速排序(Dual Pivot Quicksort)

经典快排进行了更多的元素扫描动作,因此也就比较慢。

Dual-Pivot快排比经典快排节省了12%的元素扫描,从实验来看节省了10%的时间

双轴快排使用了两个pivot, 即双轴, 双轴隔断(partition)后

区间如下

[n < p1, p1 <= n <= p2, n > p2 ]

dpqs_p.png

left为0, right为最后一位下标

Part I [left, i - 1]为 < P1 部分

Part II [i, k - 1]为 >= P1 且 <= P2 部分

Part III [j + 1, right - 1]为 > P2 部分

Part IV [k, j] 为还没有确定的部分
排序中, 通过k的遍历, 逐一确定每一个元素所在区间, 并进行对应交换

如下数组, 初始化i=0, j=length-1, k=i+1, 若p1>p2, 需要交换两个轴的位置, 即swap(arr[i], arr[j]), p1=arr[i], p2=arr[j]

dpqs.png

因为当第一次寻找到第一区间的数时, k是与++i进行交换的, 即轴1的位置是暂时先固定, 保证i左边的数符合第一区间的条件, 最后将arr[0]这个轴放到第二区间的第一位上以符合轴左右的数的顺序.

同理, 当第一次寻找到第三区间的数时, k是与--j进行交换的. 轴2的位置固定, 保证j右边这个范围都是第三区间的数, 最后将arr[length-1]这个轴放到第二区间的最后一位上以符合轴左右的数的顺序.

第一次排序结果4, 3, 5, 6, 7, 8 | p1 = 5, p2 = 7

完成后, 可以发现p1左边都是小于等于p1的, p1/p2中间的都是大于p1, 小于等于p2的, p2右边都是大于p2的

最后将三个区间再进行递归排序即可

代码如下

public static void main(String[] args) {
    int[] arr = {5, 5, 8, 6, 4, 3, 7};
//        int[] arr = {4, 981, 10, -17, 0, -20, 29, 50, 8, 43, -5};
//        int[] arr = {3, 5, 67, 54, 2, 56, 0, 7, 134, 23};
//        int[] arr = {3, 5, 1, 6, 4, 7, 2, 8};
    dualPivotQuickSort(arr, 0, arr.length - 1);
    System.out.println(Arrays.toString(arr));
}

public static void dualPivotQuickSort(int[] arr, int i, int j) {
    if (i < j) {
        if (arr[i] > arr[j]) {
            swap(arr, i, j);
        }
        int k = i + 1;
        int p1 = arr[i];
        int p2 = arr[j];
        int start = i;
        int end = j;

        while (k < j) {
            if (arr[k] < p1) {
                // 第一区间
                // 先自增再交换
                // 如果不自增直接交换, 可能会将轴放到错误的位置上
                i++;
                swap(arr, i, k);
                // 指针k移动到下一位
                k++;
            } else if (arr[k] >= p1 && arr[k] <= p2) {
                k++;
            } else {
                while (true) {
                    // 如果不自增直接交换, 可能会将轴放到错误的位置上或跳过元素
                    j--;
                    if (arr[j] <= p2 || k >= j) {
                        break;
                    }
                }
                // 指针重合 退出循环
                if (k >= j) {
                    break;
                }
                swap(arr, k, j);
            }
        }
        // 将两个轴放在隔断(partition)处
        swap(arr, start, i);
        swap(arr, end, j);
        // 将两轴隔断形成的三区域再排序 两个轴不参与再排序, 因为本身就是有序的
        dualPivotQuickSort(arr, start, i - 1);
        dualPivotQuickSort(arr, i + 1, j - 1);
        dualPivotQuickSort(arr, j + 1, end);
    }
}

private static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}


快排与双轴快排

快排与双轴快排

传统快排该方法的基本思想是:先从数列中取出一个数作为基准数分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边再对左右区间重复第二步,直到各区间只有一个数快速排序还有很多改进版本,如随机选择基准数,区间内数据较少时直接用另的方法排序以减小递归深度示例代码如下, 没有进行任何优化操
2023-05-08
Trie实现词句匹配

Trie实现词句匹配

Trie 字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高树结构如图代码过于
2021-07-26

freemarker 时间显示不正常 设置时区

项目在本地开发的时候显示正常,部署上服务器就一直差8个小时,最后发现freemarker官方文档有这样的说明time_zone:时区的名称来显示并格式化时间。 默认情况下,使用JVM的时区。 也可以是 Java 时区 API 接受的值,或者 "JVM default" (从 FreeMarker 2
2020-03-28
IDEA 2019.1 xml 不高亮

IDEA 2019.1 xml 不高亮

前几天更新了idea后,发现xml里的代码都没有了高亮,变得跟记事本一个德性了打开setting ,搜索 File Types,找到xml项, 查看下方的匹配格式,果然没有xml,(idea真是厉害)点击右方的+,输入*.xml,点击ok,解决问题
2020-03-28

npm install 淘宝镜像

npm install --registry=https://registry.npm.taobao.org
2020-03-28
Java中方法的参数传递机制

Java中方法的参数传递机制

来看一段代码 public class Man { private String name; private Integer age; public String getName() { return name; } publi
2020-03-28
基于自定义注解手写权限控制

基于自定义注解手写权限控制

方法一: AOP 方法二: 拦截器项目结构项目依赖<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-w
2020-03-28

Docker 部署 详细全过程 附代码

Docker 部署本站 全过程环境:CentOS7.61. 安装Docker其他版本CentOS可以参考这个https://help.aliyun.com/document_detail/187598.html查看本机内核版本,内核版本需高于 3.10uname -r 确保 yum 包最新yum u
2020-03-28

SpringBoot 启动普通java工程

引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.0.9</version> </dependency>
2020-03-28

Vue.js DOM操作

<template> <input type="button" @click="reply($event)" value="回复"> </template> export default { methods: { replyFun(e) {
2020-03-29
CentOS7编译调试OpenJDK12

CentOS7编译调试OpenJDK12

1. 下载源码https://hg.openjdk.java.net/jdk/jdk12点击左侧的browse,再点击zip,就可以下载zip格式的源码压缩包。unzip xxx.zip 解压文件2. 安装jdkyum install java-11-openjdk-devel -y3. 运行con
2020-04-23
编写自己的Spring Boot Starter

编写自己的Spring Boot Starter

1.新建一个maven项目命名规则统一是xxx-spring-boot-starter完整pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"
2020-06-29