数组的Partition与快速排序

本文详细介绍了数组的Partition操作,包括荷兰国旗问题,并探讨了快速排序的不同版本,从无改进到工程上常用的改进方法。文章还分析了快速排序的时间复杂度,指出其在最坏情况下的空间复杂度为O(N),而最佳情况下为O(logN),实际应用中通常收敛于O(logN)。最后,阐述了快速排序在工程上被广泛使用的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数组的Partition

在数组中取一个数p,把<=p的数都放在左边,>p的数放在右边,数的排序可以是无序的。
流程:
在a[0]之前建一个<=区,i指向a[0]
若a[i] <= p, a[i]与<=区的右边一个数交换,<=区向右扩一个单位,i++
若a[i] > p, i++
升级:荷兰国旗问题
在数组中取一个数p,把<p的数都放在左边,=p的数放中间,>p的数放在右边,数的排序可以是无序的。
流程:
在a[0]之前建一个<=区,i指向a[0],a[a.length-1]之后建立一个>=区
若a[i] < p, a[i] < p, a[i]与<区后一个数交换,<区向右扩,i++
若a[i] > p,a[i]和>区前一个数交换,>区向左扩,i不变
若a[i] == p,i++

快速排序

快速排序的核心思想便是数组的partition,通过一轮partition之后,可以把一个数插到中间,这样每个数都会找到自己的位置,然后把左边和右边的数组分别递归进行partition,每个数字都可以找到自己的位置,最后数组便是有序的了。

没有任何改进的快排

用数组的最后一个数p作为划分,<=p的放数组的左边,>p的放数组的右边,一轮partition之后,p就存在与数组中间的某个位置,然后p不动,p左边的数组和右边的数组重复以上流程。

稍微改进的快排

用数组的最后一个数p作为划分,把<p的数都放在左边,=p的数放中间,>p的数放在右边,然后=p的区域不动,区域左边的数组和区域右边的数组继续重复以上流程。

进阶改进的快排

每次都随机把l到r上的任意一个数交换到最后,然后用这个数进行partition,最后p落在任意一个位置上的概率都是相等的,即1/n
累加的公式得出来,时间复杂度为O(n*logN),最坏情况和最好情况都已成概率事件了

工程上改进的快排

在quickSort这一步,当l < r - 60 时,使用插入排序。虽然插入排序时间复杂度为O(n^2),但是样本量少的时候速度飞快,没有时间瓶颈。

快速排序的时间复杂度分析

1.最坏O(n^2)
例子 1 2 3 4 5 6 7 8 9
以9作为划分,每次只搞定了最后一个数,每次都要承载O(n)的时间复杂度,所以总的时间复杂度为O(n^2)
2.最好O(n*logN)
每次都把p打到中点位置,左边和右边的长度都为n / 2
得出来的公式:T(N) = 2T(N / 2) + O(N)
其中O(N)为partition的时间复杂度
根据master公式得出时间复杂度为O(n)
由于按最差情况来估计时间复杂度,所以完全不改进的快排时间复杂度为O(n^2)。

空间复杂度

1.最坏情况 O(N)
2.最好情况 O(logN)
结论:经过概率的统计,最后收敛于O(logN)。

快速排序的Code

public class QuickSort_lijunru {

    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 1) {
            return;
        }
        quickSort(arr, 0, arr.length - 1);
    }
	// 这是一个处理arr[l..r]的函数
	// 返回等于区域的左边界和右边界,所以返回一个长度为2的数组
    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            // 改进算法的重点,随机取一个数跟最右边的数交换,把这个数作为partition的基准
            swap(arr, left + (int) (Math.random() * (right - left + 1)), right);

            int[] p = partition(arr, left, right);
            quickSort(arr, left, p[0] - 1);
            quickSort(arr, p[1] + 1, right);
        }
    }
	
    public static int[] partition(int[] arr, int left, int right) {
        int less = left - 1;
        int more = right;
        /**
         * 始终都是arr[left]与arr[right]进行比较
         */
        while (left < more) {
            if (arr[left] < arr[right]) {
                swap(arr, ++less, left++);
            } else if (arr[left] > arr[right]) {
                swap(arr, --more, left);
            } else {
                left++;
            }
        }
       /**
         * 注意经过partition之后要把>区的第一个数和最后一个数进行交换
         * 不能把less+1进行交换,交换之后>区就被污染了,只有more才能无缝切换
         */
//        swap(arr, less + 1, right);
        swap(arr, more, right);
        return new int[] { less + 1, more };
    }

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

    public static void printArr(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[] arr = {3, 4, 1, 8, 9, 6, 2};
        printArr(arr);
        quickSort(arr);
        printArr(arr);
    }
}

为什么工程上最常用快速排序

因为代码比起其他排序比较简洁,说明常数项低,所以跑得很快,而且时间复杂度为O(n*logN)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值