算法导论-第7章-快速排序

文章详细介绍了快速排序的分治过程,包括分解、解决和合并三个步骤,并提供了Java代码实现。此外,还讨论了快速排序的优化方法,如基准选择、插入排序的使用以及三数取中策略。最后,列出了不同排序算法的运行时间比较。
摘要由CSDN通过智能技术生成

7.1 快速排序的描述

对一个典型的子数组 A [ p . . r ] A[p..r] A[p..r]进行快速排序的三步分治过程:

  • 分解:数组 A [ p . . r ] A[p..r] A[p..r]被划分为两个(可能为空)的子数组 A [ p . . q − 1 ] A[p..q-1] A[p..q1] A [ q + 1.. r ] A[q+1..r] A[q+1..r],使得 A [ p . . q − 1 ] A[p..q-1] A[p..q1]中的每一个元素都小于等于 A [ q ] A[q] A[q],而 A [ q + 1.. r ] A[q+1..r] A[q+1..r]中的每个元素都大于等于 A [ q ] A[q] A[q]。返回下标 q q q
  • 解决:通过递归,对子数组 A [ p . . q − 1 ] A[p..q-1] A[p..q1] A [ q + 1.. r ] A[q+1..r] A[q+1..r]调用快速排序。
  • 合并:数组 A [ p . . r ] A[p..r] A[p..r]已经有序。

下面的程序实现快速排序:

下图显示了PARTITION(A, p, r)的操作过程:选择 x = A [ r ] x=A[r] x=A[r]作为枢轴(pivot)(不一定非要选择数组最后一个元素作为枢轴,也可以选择其他元素),并围绕它来划分子数组 A [ p . . r ] A[p..r] A[p..r]

PARTITION(A, p, r)的核心思想:取数组的最后一个元素为枢轴,使用指针 j j j从左向右遍历,遇到比枢轴小的元素,移动指针 i i i。这就形成了在从数组开头到指针 j j j的范围内,从数组开头到指针 i i i为比枢轴小的元素,从指针 i + 1 i+1 i+1到指针 j − 1 j-1 j1为比枢轴大的元素,直到遍历 A . l e n g t h − 1 A.length-1 A.length1个元素。最后,交换指针 i + 1 i+1 i+1指向的元素和数组最后一个元素即可。

PARTITION(A, p, r)在操作过程中将待排序的子数组划分为以下几个部分:

  • A [ p . . i ] A[p..i] A[p..i]:已经遍历的比枢轴元素小的元素
  • A [ i + 1.. j − 1 ] A[i+1..j-1] A[i+1..j1]:已经遍历的比枢轴元素大的元素
  • A [ j . . r − 1 ] A[j..r-1] A[j..r1]:将要遍历的元素
  • A [ r ] A[r] A[r]:枢轴元素


快速排序实验:读取data.txt文件,文件格式如下:第一行为数组长度,第二行为数组(int类型)的内容,将结果数组的数据写在一行内,每个数组中间以空格隔开,输出为sorted.txt。

代码实现:

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class QuickSort {

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

    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int pivotIndex = partition(arr, left, right);
            quickSort(arr, left, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, right);
        }
    }

    public static int partition(int[] arr, int left, int right) {
        int pivot = arr[right];
        int i = left - 1;
        for (int j = left; j < right; j++) {
            if (arr[j] < pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, right);
        return i + 1;
    }


    public static void main(String[] args) throws IOException {
        /*
          1、读取data.txt文件中的数据(第一行为数组长度, 第二行为数组(int类型)的内容)
         */
        String path = "D:/Projects/IDEAProjects/algorithms/src/main/java/ch07/data.txt";
        List<String> readAllLines = Files.readAllLines(Paths.get(path));
        int length = Integer.parseInt(readAllLines.get(0));
        String[] split = readAllLines.get(1).split("\\s+");
        int[] array = new int[length];
        for (int i = 0; i < length; i++) {
            array[i] = Integer.parseInt(split[i]);
        }

        /*
          2、计算算法的耗时
         */
        long start = System.currentTimeMillis();
        quickSort(array, 0, length - 1);
        long end = System.currentTimeMillis();

        System.out.println("算法耗时: " + (end - start) + " ms");


        /*
          3、将排序后的数据写入结果文件result.txt
         */
        String pathResult = "D:/Projects/IDEAProjects/algorithms/src/main/java/ch07/sorted.txt";
        File file = new File(pathResult);
        if (!file.exists()) {
            file.createNewFile();
        }

        FileWriter fileWriter = new FileWriter(file);
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            stringBuilder.append(array[i]).append(" ");
        }
        bufferedWriter.write(stringBuilder.toString());
        bufferedWriter.close();
        //System.out.println(Arrays.toString(array));
    }
}

7.2 快速排序的优化

优化思路:

  1. 基准的选择:快速排序的运行时间与划分是否对称有关。最坏情况下,每次划分过程产生两个区域分别包含 n − 1 n-1 n1个元素和 1 1 1个元素,其时间复杂度会达到 O ( n 2 ) O(n^2) O(n2)。在最好的情况下,每次划分所取的基准都恰好是中值,即每次划分都产生两个大小为 n / 2 n/2 n/2的区域。此时,快排的时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn)

    所以基准的选择对快排而言至关重要。快排中基准的选择方式主要有以下三种:

    1. 固定基准
    2. 随机基准
    3. 三数取中
  2. 当输入数据已经“几乎有序”时,使用插入排序速度很快。我们可以利用这一特点来提高快速排序的速度。当对一个长度小于k的子数组调用快速排序时,让她不做任何排序就返回。上层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。

  3. (可选)聚集元素

    思想:在一次分割结束后,将与本次基准相等的元素聚集在一起,再分割时,不再对聚集过的元素进行分割。

    1. 在划分过程中将与基准值相等的元素放入数组两端,
    2. 划分结束后,再将两端的元素移到基准值周围。
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class QuickSortPro {
    private final static int THRESHOLD = 8; // 插入排序阈值

    /**
     * 交换数组中两个索引位置的元素
     * @param arr
     * @param i
     * @param j
     */
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    /**
     * 插入排序
     * @param A
     * @param left
     * @param right
     */
    public static void insertionSort(int[] A, int left, int right) {
        for (int j = left + 1; j <= right; j++) {
            int key = A[j];
            int i = j - 1;
            while (i >= left && A[i] > key) {
                A[i + 1] = A[i];
                i--;
            }
            A[i + 1] = key;
        }
    }

    /**
     * 快速排序优化
     * @param arr
     * @param left
     * @param right
     */
    public static void quickSortPro(int[] arr, int left, int right) {
        if (right - left + 1 <= THRESHOLD) {
            insertionSort(arr, left, right);
        }
        if (left < right) {
            int pivotIndex = randomPartition(arr, left, right);
            //int pivotIndex = partition(arr, left, right);

            quickSortPro(arr, left, pivotIndex - 1);
            quickSortPro(arr, pivotIndex + 1, right);
        }
    }

    /**
     * 三数取中,将中间大的元素交换到枢轴的位置
     * @param arr
     * @param left
     * @param right
     * @return
     */
    public static int randomPartition(int[] arr, int left, int right) {
        int mid = left + (right - left) / 2;
        if (arr[left] > arr[mid]) {
            swap(arr, left, mid);
        }
        if (arr[mid] > arr[right]) {
            swap(arr, mid, right);
        }
        if (arr[mid] < arr[left]) {
            swap(arr, left, mid);
        }

        swap(arr, mid, right);
        return partition(arr, left, right);
    }

    /**
     * 选取的枢轴为数组中最后一个元素
     * @param arr
     * @param left
     * @param right
     * @return
     */
    public static int partition(int[] arr, int left, int right) {
        int pivot = arr[right];
        int i = left - 1;
        for (int j = left; j < right; j++) {
            if (arr[j] < pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, right);
        return i + 1;
    }


    public static void main(String[] args) throws IOException {
        /*
          1、读取data.txt文件中的数据(第一行为数组长度, 第二行为数组(int类型)的内容)
         */
        String path = "D:/Projects/IDEAProjects/algorithms/src/main/java/ch07/data.txt";
        List<String> readAllLines = Files.readAllLines(Paths.get(path));
        int length = Integer.parseInt(readAllLines.get(0));
        String[] split = readAllLines.get(1).split("\\s+");
        int[] array = new int[length];
        for (int i = 0; i < length; i++) {
            array[i] = Integer.parseInt(split[i]);
        }

        /*
          2、计算算法的耗时
         */
        long start = System.currentTimeMillis();
        quickSortPro(array, 0, length - 1);
        long end = System.currentTimeMillis();

        System.out.println("算法耗时: " + (end - start) + " ms");


        /*
          3、将排序后的数据写入结果文件result.txt
         */
        String pathResult = "D:/Projects/IDEAProjects/algorithms/src/main/java/ch07/sorted-pro.txt";
        File file = new File(pathResult);
        if (!file.exists()) {
            file.createNewFile();
        }

        FileWriter fileWriter = new FileWriter(file);
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            stringBuilder.append(array[i]).append(" ");
        }
        bufferedWriter.write(stringBuilder.toString());
        bufferedWriter.close();
        //System.out.println(Arrays.toString(array));
    }
}

7.3 常见排序算法的运行时间

算法最坏运行时间平均情况/期望运行时间
插入排序 Θ ( n 2 ) \Theta(n^2) Θ(n2) Θ ( n 2 ) \Theta(n^2) Θ(n2)
归并排序 Θ ( n log ⁡ n ) \Theta(n \log n) Θ(nlogn) Θ ( n log ⁡ n ) \Theta(n \log n) Θ(nlogn)
堆排序 O ( n log ⁡ n ) \Omicron(n \log n) O(nlogn)--------
快速排序 Θ ( n 2 ) \Theta(n^2) Θ(n2) Θ ( n log ⁡ n ) \Theta(n \log n) Θ(nlogn)(期望)
计数排序 Θ ( k + n ) \Theta(k+n) Θ(k+n) Θ ( k + n ) \Theta(k+n) Θ(k+n)
基数排序 Θ ( d ( n + k ) ) \Theta(d(n+k)) Θ(d(n+k)) Θ ( d ( n + k ) ) \Theta(d(n+k)) Θ(d(n+k))
桶排序 Θ ( n 2 ) \Theta(n^2) Θ(n2) Θ ( n ) \Theta(n) Θ(n)(平均情况)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值