快排

前提介绍

荷兰国旗问题:

  • 按照某个条件,把数据分成三段;例如【正数、负数、零】或者【小于2、等于2、大于2】;
  • 进一步来讲,也就是快排的一次partition找基准的过程;以中间的作为基准,例如负数、等于2作为基准

例如:75. 颜色分类

class Solution {
    public void sortColors(int[] nums) {
        
        int left = 0, i = left, right = nums.length - 1;
        while(right >= i){
            if(nums[i] == 0){
                swap(nums, i++, left++);
            }else if(nums[i] == 2){
                swap(nums, i, right--);
            }else{
                i++;
            }
        }
    }

    public void swap(int[] nums, int left, int right) {
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

美团一面算法:

荷兰国旗问题(正数、负数、零)或者直接暴力两次双指针
package com.hshuo;

import java.util.Arrays;

import static com.hshuo.QuickSort.swap;

/**
 * @author SHshuo
 * @data 2022/4/4--8:56
 */
public class Partition {
    public static void main(String[] args) {
//        荷兰国旗问题、根据某个数进行分三段

        int[] res = new int[]{0, 0, -1, -4, -2, 3, 6, 4, 7};

        int left = 0, i = left, right = res.length - 1;
        while(i <= right){
            if(res[i] > 0){
                swap(i++, left++, res);
            }else if(res[i] == 0){
                swap(i, right--, res);
            }else{
                i++;
            }
        }
        System.out.println(Arrays.toString(res));

    }
}
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
class Solution {
    public int[] exchange(int[] nums) {
        int left = 0, i = left, right = nums.length - 1;
        while(i <= right){
            if(nums[i] % 2 == 0){
                swap(i, right--, nums);
            }else{
                swap(i++, left++, nums);
            }
        }

        return nums;
    }

    public void swap(int left, int right, int[] nums){
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

解决TopK问题:

快排思路

  • 先找基准、之后分治递归
例如:
  • 剑指offer40,最小的k个数
  • 思路:partition的思路,在过程中return;而不是所有数据都排序完毕
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        if(arr.length <= k){
            return arr;
        }
        int[] res = new int[k];
        partition(arr, 0, arr.length - 1, k);

        System.arraycopy(arr, 0, res, 0, k);
        return res;
    }

    public void partition(int[] arr, int left, int right, int k){
        int index = getIndex(arr, left, right);
        if(index == k){
            return;
        }else if(index < k){
            partition(arr, index + 1, right, k);
        } else{
            partition(arr, left, index - 1, k);
        }
    }

    public int getIndex(int[] arr, int left, int right){
        int pivot = left;
        int index = pivot + 1;
        for(int i = index; i <= right; i++){
            if(arr[i] < arr[pivot]){
                swap(arr, i, index);
                index++;
            }
        }

        swap(arr, pivot, index - 1);
        return index - 1;
    }

    public void swap(int[] arr, int left, int right){
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
}

剑指offer45:把数组排成最小的数
class Solution {
    public String minNumber(int[] nums) {
        int len = nums.length, index = 0;
        String[] res = new String[len];
        for(int i : nums){
            res[index++] = String.valueOf(i);
        }
        quickSort(res, 0, len - 1);

        StringBuffer buffer = new StringBuffer();
        for(String i : res){
            buffer.append(i);
        }
        return buffer.toString();
    }

    public void quickSort(String[] res, int left, int right){
        if(right >= left){
            int index = getIndex(res, left, right);
            quickSort(res, left, index - 1);
            quickSort(res, index + 1, right);
        }
    }

    public int getIndex(String[] res, int left, int right){
        int pivot = left;
        int index = pivot + 1;
        for(int i = index; i <= right; i++){
            if((res[i] + res[pivot]).compareTo(res[pivot] + res[i]) < 0){
                swap(res, i, index);
                index++;
            }
        }

        swap(res, index - 1, pivot);
        return index - 1;
    }

    public void swap(String[] res, int left, int right){
        String temp = res[left];
        res[left] = res[right];
        res[right] = temp;
    }
}


快排的最坏时间复杂度的情况?

  • 数据本来就已经升序,按照pivot的方法,每次分区之后分区点都还在右边界;
  • 这就产生了分区极度不均匀的情况,最后需要分n次区,每次分区平均要扫描n / 2个元素。所以时间复杂度就退化成了O(n ^ 2)
  • 也就是递归的过程原本是树的形式,退化到了链表
  • 参考:https://www.cnblogs.com/FengZeng666/p/13944309.html


如何实现快排的优化?

  • 当待排序序列长度分割到一定大小后,使用插入排序;对于很小和部分有序的数据,快排不如插排(有序的时候插入时间复杂度会变为O(n))好。所以可以使用插排替换快排;
  • 在一次分割结束后,可以把与基准pivot相等的元素聚在一起,继续下次分割时,不用再对与pivot相等元素分割(处理);
  • 随机选取数组中的元素作为基准,使算法成为等概率事件,也就是快排3.0的优化。


如何实现稳定的快排?

  • 将找基准的过程不在原数组进行修改,而是另外申请一段空间B,在原来的数据空间A中,选取第一个元素pivot作为基准;
  • 首先遍历空间A,将所有小于pivot基准的元素依次放到空间B中,然后将pivot放入B中;
  • 再次遍历空间B,将所有大于等于pivot基准的元素依次放入空间B中;
  • 最后重新将空间B的数据全部拷贝到空间A中。