美文网首页
【算法】快速排序及优化

【算法】快速排序及优化

作者: mapleYe | 来源:发表于2018-06-12 17:47 被阅读0次

    一、荷兰国旗问题

    在讲快速排序前,我们先来看看荷兰国旗问题。题目如下:

    给定一个数组arr,和一个数num,请把
    小于num的数放在数组的左边,
    等于num的数放在数组的中间,
    大于num的数放在数组的 右边。
    要求额外空间复杂度O(1),时间复杂度O(N),
    例如: 
    对于数组3,5,4,5,8,1,9,以5进行分割
    输出:3 4 1 5 5 9 8 
    

    其实,这就是快排的partition过程,通过三个指针,index,less,more进行的,初始less=左边界-1,more=右边界+1,说明一开始不存在less域和more域:
    1)若arr[index] < num,less域增加,即less++,然后index和less位置的数交换后,index++继续指向下一个数
    2)若arr[index] > num,more域增加,即more++,然后index和more的数交换后,继续判断交换后arr[index]和num的关系
    3)若arr[index] == num,index++继续指向下一个数,不坐任何处理
    4)重复以上过程,直至index==more

    大致过程图:


    代码实现

    public static int[] partition(int[] arr, int l, int r, int num) {
        int less = l - 1;
        int more = r + 1;
        int index = l;
        // 循环至index到more相等
        while (index < more) {
          if (arr[index] < num) {
            // 扩大less域
            swap(arr, ++less, index++);
          }else if (arr[index] > num){
            // 扩大more域,且交换后,继续判断当前index的数和num的关系
            swap(arr, --more, index);
          }else {
            index++;
          }
        }
        int[] result = {less + 1, more - 1};
        // 返回相等区间开始和结束的索引值
        return result;
      }
      public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
      }
    

    二、经典快速排序

    快排其实是冒泡排序的改进,其算法思路如下:
    1)在数组中选取一个数作为基准元素
    2)分区,把小于基准的数放左边,大于基准的数放右边
    3)递归,对左右两个分区重复以上步骤

    在网上找了一张图,如下所示(侵删):


    实现代码

      public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
          return;
        }
        quickSort(arr, 0, arr.length - 1);
      }
    
      public static void quickSort(int[] arr, int l, int r) {
        if (l >= r) {
          return;
        }
        int p = partition(arr, l, r);
        quickSort(arr, l, p - 1);
        quickSort(arr, p + 1, r);
      }
    
      public static int partition(int[] arr, int l, int r) {
        // 取左边第一个数为基准
        int pivot = arr[l];
        int i = l;
        int j = r;
        while(i != j) {
          // 从右边找第一个小于基准的数
          while(arr[j] >= pivot && i < j) {
            j--;
          }
          // 从左边找第一个大于基准的数
          while(arr[i] <= pivot && i < j) {
            i++;
          }
          if (i < j) {
            swap(arr, i, j);
          }
        }
        // 将基准归位
        swap(arr, l, j);
        // 返回基准的位置
        return j;
      }
    

    三、改进的快速排序

    回顾经典的快速排序,可以优化的地方有两个:

    1)基准的选取,若每次取数组的第一个作为基准,则可能出现基准是数组中最小的数,造成了多余的计算。因此,基准的选取可以进行随机选择
    2)分区返回的边界,回顾荷兰国旗问题,分区时,可以发现基准可以是中间一片区域,而不是一个数,因此,分区时,返回的不在是基准的位置,而是基准的左边界和右边界,那么下次划分子问题时,就可以减少交换的次数了。

    代码实现

    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
          return;
        }
        // quickSort2(arr, 0, arr.length - 1);
        quickSort(arr, 0, arr.length - 1);
      }
    
    public static void quickSort(int[] arr, int l, int r) {
        if (l < r) {
          // 从l~r中选一个数,放在r作为基准
          swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
          int[] p = partition(arr, l, r);
          quickSort(arr, l, p[0] - 1);
          quickSort(arr, p[1] + 1, r);
        }
      }
    
      // 分割
      public static int[] partition(int[] arr, int l, int r) {
        int less = l - 1;
        int more = r;
        int index = l;
        // 循环至index到more相等
        while (index < more) {
          if (arr[index] < arr[r]) {
            // 扩大less域
            swap(arr, ++less, index++);
          }else if (arr[index] > arr[r]){
            // 扩大more域,且交换后,继续判断当前index的数和num的关系
            swap(arr, --more, index);
          }else {
            index++;
          }
        }
        // 将基准归位,基准取的是最右边的数
        swap(arr, more, r);
        int[] result = {less + 1, more};
        // 返回相等区间开始和结束的索引值
        return result;
      }
    

    相关文章

      网友评论

          本文标题:【算法】快速排序及优化

          本文链接:https://www.haomeiwen.com/subject/kilreftx.html