快排原理讲解

源代码

public class MainDemo {
    public static void main(String[] args) {
        Integer a[] = {3, 2, 1, 4, 5, 6, 7, 1};
        //递归调用
        QuickSort(a, 0, a.length-1);
        System.out.println(Arrays.toString(a));

    }

    private static void QuickSort(Integer[] a, int l, int r) {

        if (l >= r) {
            return;//l和r代表左右两边的的萝卜下标
        }

        int key = a[l];//挖出第一个萝卜备用      
        int left = l, right = r;//工人A和B分别站在最左边和最右边位置开始寻找需要交换位置的萝卜


        while (left < right) {
            while (right > left && a[right] >= key) {
            //B往左走寻找比挖出来的第一个萝卜轻,且位置在A右边的萝卜
                right--;
            }
            a[left] = a[right];
            while (left < right && a[left] <= key) {
            //A要往右寻找比一个萝卜重,且位置在B左边的萝卜
                left++;
            }
            a[right] = a[left];
        }

        a[left] = key;

        QuickSort(a, l, left);
        QuickSort(a, left + 1, r);

    }

}

原理讲解

款速排序是很经典的排序算法,递归最难理解的就是临界情况,大家可以自行对{1,2}、{2,1}、{2,2}等特殊情况进行模拟调用,理解了这些临界情况的处理方式,快排就很好懂了,如果认为笔者有讲述不清晰的地方,或者描述不清楚或有误的地方,请不吝指正,感激不尽。下面我分两步进行讲解说明。

第一步,理解核心思路

假设你有一堆横排的体重不一的萝卜,你想把它们按照重量从小到大进行排序。
用快排来完成的话:

准备工作:
0、如果最左边萝卜的下标>=最右右边萝卜下标,结束。(这就是我们的递归出口)
1、把第一个萝卜挖出来备用。
2、找两个工人一个叫A一个叫B,分别站在第一个萝卜(注意第一个萝卜的坑已经空了)和最后一个萝卜的位置。
3、如果A和B相遇了,则执行6,否则继续执行4和5
4、B往左走寻找比挖出来的第一个萝卜轻,且位置在A右边的萝卜,找到了就挖出来放到左所在的空坑里(这时B守着空坑)
5、由于B把A的坑用掉了,所以A要往右寻找比一个萝卜重,且位置在B左边的萝卜,找到了就挖出来放到B守着的空坑里(这时A又守空坑了),执行3
6、这时候两人已经相遇了,我们曾经把第一个萝卜挖出来,所以这时候必定有一个位置是空着的,这个位置也必然是他们相遇的这个位置,为什么呢?因为A和B相遇有两种情况,A往右走,与遇到B和B往左走遇到A,A和B的工作其实就是找到合适的萝卜去填对方的坑,当有一方在走,另一方所在位置必然是空的,所以A遇到B时候B是空的,B遇到A的时候A时是空的,所以最后的空坑肯定是他们相遇的位置。这时候只要把我们挖出的第一个萝卜放入这个空坑,就可以达到一个效果那就是相遇位置放上第一个萝卜之后,它左边的萝卜质量都小于它,它右边位置的萝卜质量都大于它。这时候就执行步骤7。
7、把所有萝卜分成两堆,[第一个萝卜,相遇位置]为第一堆,[相遇位置+1,第最后一个萝卜]为第二堆。对这两堆萝卜从步骤0开始执同样的操作。

注意:

这里所谓的挖出萝卜产生空坑是逻辑上的不是物理上的,我们执行a=b之后,逻辑意义是将b存入了a,但是b中的值还是存在的。同理我们把A位置萝卜存入B位置之后A位置的值还是存在的,但是已经没有意义了,可以被别的值覆盖掉,也就是说我在第二步中的X的含义不是这里没有值了,而是这里的值已经拷贝到另一个位置,而这个位置可以被别的值直接覆盖

第二步,数据模拟

注意:加红的表示A和B当前所在位置
初始萝卜:
5 6 7 3 4
0、左边位置0,右边4,继续执行
1、挖掉5,变成 X 6 7 3 4
2、A站在0,B站在4
3、A和B没有相遇,执行4、5
4、B从所在位置开始往左找发现4比挖掉的5小,且位置在A右边,所以把4放到A的位置,变成4 6 7 3 X
5、A从所在位置往右找,找比第一个萝卜大的萝卜,找到6,将6放到B所在坑位,变成4 X 7 3 6
3、A和B没有相遇继续4、5
4、B从所在位置开始往左找发现3比挖掉的5小,且位置在A右边,所以把3放到A的位置,变成4 3 7 X 6
5、A从所在位置往右找,找到7,将6放到B所在坑位,变成4 3 X 7 6
3、A和B没有相遇,执行4、5
4、B从所在位置开始往左找,发现A的位置和它相等了,而且是个空坑,所以B虚空将该位置并不存在的萝卜放在该位置,这步属于累赘执行。变成4 3 X 7 6
5、A从所在位置开始往右找,发现B的位置和它祥光了,而且是个空坑,所以A也虚空将该位置并不存在的萝卜放在该位置,这步属于累赘执行。变成4 3 X 7 6
3、A已经遇到B执行6
6、将第一次挖出的5放到相遇位置,变成了4 3 5 7 6
7、分成两堆,一堆[0,2],另一堆[3,4]
8、对[0, 2]这堆的萝卜进行步骤0开始的一系列操作,对[3, 4]这堆的萝卜进行步骤0开始的一系列操作。

伪代码


    QuickSort(萝卜数组a, 左边位置, 右边位置) {

        if (左边位置 >= 右边位置) {
            return;//步骤0
        }


        key = 第一个萝卜重量;//步骤1
        将左右两边位置分别存入A、B之中;//步骤2

        while (A位置 < B位置) {//步骤3
            while (B位置 > A位置 && B位置萝卜质量 >= key)
                right--;//步骤4.1
            将B位置萝卜放入A所在空位;//步骤4.2
            while (left < right && a[left] <= key)
                left++;//步骤5.1
            a[right] = a[left];//步骤5.2
        }

        a[left] = key;//步骤6

        QuickSort(萝卜数组a, 左边位置, A位置);//步骤7.1
        QuickSort(萝卜数组a, A位置+1, 右边位置);//步骤7.2

    }

难点

临界情况:
5 3
这组数据的运行情况是这样的:
记录5
B找到3比5小移到A位置变成3 X
然后A右移遇到B,将B位置数放入A位置(其实就是同意位置放入同一位置)。
A和B已经相遇,跳出while循环,将5放入当前空坑变成3 5
对[0,1],[2,1]对两段进行递归调用。
第一段:
[0,1],数据为3 5
这组数据运行情况是这样的:
记录3
B找到3位置遇到A,将B位置数放入A位置(同一位置放入同一位置)。
循环结束,分成两堆[0,0]和[1,1],并继续进行递归调用,满足递归出口条件l >= r直接结束。
第二段:
[2,1],这段满足递归出口条件l >= r,直接结束。

### 快速排序算法的工作原理 快速排序是一种基于分治法的高效排序算法,其核心思想是通过选取一个基准值(pivot),将数组划分为两个子数组,使得左侧子数组中的所有元素都小于或等于基准值,右侧子数组中的所有元素都大于基准值。随后递归地对这两个子数组应用相同的策略,直至整个数组有序。 #### 原理概述 1. **选择基准值**:从待排序数组中挑选一个元素作为基准值[^1]。 2. **分区操作**:重新排列数组,使所有小于基准值的元素位于其左侧,所有大于基准值的元素位于其右侧[^4]。 3. **递归处理**:分别对左、右两部分子数组重复上述过程,直到每个子数组只剩下一个元素为止[^4]。 --- ### 快速排序算法的实现方式 以下是快速排序的具体实现步骤: #### Java 实现代码 ```java public class QuickSort { public static void main(String[] args) { int[] array = {78, 47, 99, 24, 19, 71}; quickSort(array, 0, array.length - 1); System.out.println("Sorted Array:"); for (int num : array) { System.out.print(num + " "); } } public static void quickSort(int[] arr, int low, int high) { if (low < high) { // Partitioning index int pi = partition(arr, low, high); // Recursively sort elements before and after partition quickSort(arr, low, pi - 1); // Left subarray quickSort(arr, pi + 1, high); // Right subarray } } private static int partition(int[] arr, int low, int high) { int pivot = arr[high]; // Pivot element int i = (low - 1); // Index of smaller element for (int j = low; j < high; j++) { // If current element is less than or equal to pivot if (arr[j] <= pivot) { i++; // Swap arr[i] and arr[j] int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // Swap the pivot element with the greater element specified by i int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; // Return partitioning index } } ``` #### Python 实现代码 ```python def quick_sort(arr): if len(arr) <= 1: return arr else: # Select a pivot value pivot = arr[len(arr) // 2] # Divide into three lists based on comparison with pivot left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] # Recursively apply quicksort and concatenate results return quick_sort(left) + middle + quick_sort(right) # Example usage if __name__ == "__main__": unsorted_array = [78, 47, 99, 24, 19, 71] sorted_array = quick_sort(unsorted_array) print("Sorted Array:", sorted_array) ``` --- ### 时间复杂度与空间复杂度分析 1. **最佳时间复杂度**:当每次分割都能均匀分配时,时间复杂度为 \( O(n \log n) \)[^3]。 2. **最差时间复杂度**:如果每次都选到极端值(如最小值或最大值)作为基准,则退化为冒泡排序,时间复杂度为 \( O(n^2) \)[^3]。 3. **平均时间复杂度**:通常情况下,快速排序的时间复杂度为 \( O(n \log n) \)[^3]。 4. **空间复杂度**:由于递归调用栈的存在,空间复杂度为 \( O(\log n) \) 到 \( O(n) \),取决于递归深度[^1]。 --- ### 不稳定性的原因 尽管快速排序效率高,但它并非一种稳定的排序方法。这是因为分区过程中可能会改变相同值元素之间的原始相对位置[^2]。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值