快速排序

快速排序的基本思想

快速排序也是利用分治的思想。
如果需要排序的数据的下标是从s到t,在下标s到t之间的任意一个数据作为分区点p,遍历s到t之间的数据,将小于分区点的数据放到分区点p的左边,将大于分区点的数据放到分区点的右边,分区点放在中间,经过这一操作后。数据被分为3个部分,s到p-1之间为小于分区点的数据,p+1到t之间为大于分区点的数据,p为分区点。根据分治递归的处理思想,可以递归排序s到p-1的区间和p+1到t的区间,直到区间缩小为1,说明排序完成,所有数据都已经为有序。
文字还是没有图直观:分区点的择为最末尾的一个元素
首先还是看看分区的操作,一个直观的对比
在这里插入图片描述

快速排序的代码实现

先还是从分区函数的实现开始吧
在这里插入图片描述
在这里插入图片描述
结合图再来看看分区函数的代码实现

/**
 * 分区函数,在快速排序中使用的是原地分区,实现非常的巧妙
 *
 * <p>函数需要返回分区点
 *
 * @param data 原始数据
 * @param s 开始位置
 * @param t 结束位置
 * @return 分区点的位置
 */
private int partition(T[] data, int s, int t) {
  // 1,指定分区点为当前的最后一个元素
  int point = t;
  int i = s;
  for (int j = s; j <= t - 1; j++) {
    // i < j ? -1
    // 如果数据小于分区点,执行互换操作
    if (((Comparable) data[j]).compareTo(data[point]) < 0) {
      // 交换数据
      swap(data, i, j);
      // i向后移动一位,指向分区点
      i += 1;
    }
  }

  // 当结束后,将分区点的位置与i互换,即可完成
  swap(data, i, point);
  // System.out.println(Arrays.toString(data));

  return i;
}

/**
 * 进行数据交换操作
 *
 * @param data 原始数据
 * @param src 原始位置
 * @param target 目标位置
 */
private void swap(T[] data, int src, int target) {
  T dataTmp = data[src];
  data[src] = data[target];
  data[target] = dataTmp;
}

在这里插入图片描述

完整的快速排序的实现:

/**
 * 快速排序的代码实现
 *
 * @author liujun
 * @version 0.0.1
 * @date 2019/10/28
 */
public class QuickSort<T> implements SortInf<T> {

  @Override
  public void sort(T[] data) {

    if (data == null || data.length <= 1) {
      return;
    }
    if (!(data[0] instanceof Comparable)) {
      throw new IllegalArgumentException("data not implement compator interface");
    }

    // 调用快速排序
    this.quickSort(data, 0, data.length - 1);
  }

  private void quickSort(T[] data, int s, int t) {

    // 当前需操作元素仅一个,则退出
    if (t - s < 1) {
      return;
    }

    int point = this.partition(data, s, t);
    quickSort(data, s, point - 1);
    quickSort(data, point + 1, t);
  }

  /**
   * 分区函数,在快速排序中使用的是原地分区,实现非常的巧妙
   *
   * <p>函数需要返回分区点
   *
   * @param data 原始数据
   * @param s 开始位置
   * @param t 结束位置
   * @return 分区点的位置
   */
  private int partition(T[] data, int s, int t) {
    // 1,指定分区点为当前的最后一个元素
    int point = t;
    int i = s;
    for (int j = s; j <= t - 1; j++) {
      // i < j ? -1
      // 如果数据小于分区点,执行互换操作
      if (((Comparable) data[j]).compareTo(data[point]) < 0) {
        // 交换数据
        swap(data, i, j);
        // i向后移动一位,指向分区点
        i += 1;
      }
    }

    // 当结束后,将分区点的位置与i互换,即可完成
    swap(data, i, point);
    // System.out.println(Arrays.toString(data));

    return i;
  }

  /**
   * 进行数据交换操作
   *
   * @param data 原始数据
   * @param src 原始位置
   * @param target 目标位置
   */
  private void swap(T[] data, int src, int target) {
    T dataTmp = data[src];
    data[src] = data[target];
    data[target] = dataTmp;
  }
}

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

最好情况时间复杂度

快速排序也是使用递归来实现的,在分析归并排序时,总结的公式同样适用,在最好情况下,每次都能将数据均匀的分成两个相同的区间,那快速排序与归并排序的时间复杂度是相同的
所以快排的最好情况时间复杂度就是O(nlogn)
t(1)=C 只需要常量级的执行时间,使用C表示
t(n)=2*t(n/2)+n n>1

平均情况下的时间复杂度

假设每次分区都将数据分成9:1的两个区间,继续套用公式来求解时间复杂度:
就会变成
t(1)=C t=1时,只需常量级的时间,表示为C
t(n)=t(9n/10)+t(n/10)+n n>1求解时间复杂度
t(n)=t(9n/10)+t(n/10)+n
(过程我还不能推导出来,目前空余)
只知道结果是n*logn

针对快速排序时间退化成O(n^2)的优化方法

优化方式1:三数取中
从待排序的数组中的头部、中间、还有尾部三个位置各取一个数,然后在这三个数中求出中位数,这种方法肯定比单纯的取固定值的方法要好,当要排序的数据很大时,三数取中可能就不够了,可能就会采用5数取中,或者更大的10数取中。

先还是来说三数取中法吧
首先对前两个数A和B进行比较大小,如果A大于B,则将A与换。
这时开始处理数C,数C处理在这里存在三种情况:
情况1:C小于A,那么A就是中位数
情况2:C大于A,小于B,那么C就是中位数
情况3:C大于B,那么B就是中位数。

在这里插入图片描述

代码实现:


/**
 * 快速排序的代码实现,使用三数取中法进行分区点的选择操作
 *
 * @author liujun
 * @version 0.0.1
 * @date 2019/10/28
 */
public class QuickSortThirdMod<T> implements SortInf<T> {

  @Override
  public void sort(T[] data) {

    if (data == null || data.length <= 1) {
      return;
    }
    if (!(data[0] instanceof Comparable)) {
      throw new IllegalArgumentException("data not implement compator interface");
    }

    // 调用快速排序
    this.quickSort(data, 0, data.length - 1);
  }

  private void quickSort(T[] data, int s, int t) {

    // 当前需操作元素仅一个,则退出
    if (t - s < 1) {
      return;
    }

    int point = this.partition(data, s, t);
    quickSort(data, s, point - 1);
    quickSort(data, point + 1, t);
  }

  /**
   * 分区函数,在快速排序中使用的是原地分区,实现非常的巧妙
   *
   * <p>函数需要返回分区点
   *
   * @param data 原始数据
   * @param s 开始位置
   * @param t 结束位置
   * @return 分区点的位置
   */
  private int partition(T[] data, int s, int t) {
    // 1,分区函数采用三数取中法
    int countPoint = this.dataMid(data, s, t);
    // 求得中位数后将,最后一个位置与中位数互换即可
    swap(data, countPoint, t);

    // 当前的分区点
    int midPoint = t;

    int i = s;
    for (int j = s; j <= t - 1; j++) {
      // i < j ? -1
      // 如果数据小于分区点,执行互换操作
      if (((Comparable) data[j]).compareTo(data[midPoint]) < 0) {
        // 交换数据
        swap(data, i, j);
        // i向后移动一位,指向分区点
        i += 1;
      }
      /// System.out.println("分区数据:" + Arrays.toString(data));
    }

    // 当结束后,将分区点的位置与i互换,即可完成
    swap(data, i, midPoint);

    //    System.out.println("分区点:" + midPoint);
    //    System.out.println("分区完成后" + Arrays.toString(data));

    return i;
  }

  /**
   * 三数取中法
   *
   * <p>对三个数进行比较,然后交换次序
   *
   * @param data 数据
   * @param s 开始索引
   * @param t 结束索引
   * @return 当前的中位数
   */
  public int dataMid(T[] data, int s, int t) {
    // 求两个数的中位数与(s+t)/2效果一致,但在接近最大数时,将不能运算,所以使用t+((s-t)>>1)
    int midIndex = t + ((s - t) >> 1);

    // 首先保证前两个数是有序的
    if (((Comparable) data[s]).compareTo(data[midIndex]) > 0) {
      swap(data, s, midIndex);
    }

    // 分为多种情况,情况1,比最小的数字小
    if (((Comparable) data[t]).compareTo(data[s]) <= 0) {
      return s;
    }
    // 情况2,大于最小,小于最大
    else if (((Comparable) data[t]).compareTo(data[s]) > 0
        && ((Comparable) data[t]).compareTo(data[midIndex]) <= 0) {
      return t;
    }
    // 情况3:大于最大的数,返回最大数
    else {
      return midIndex;
    }
  }

  /**
   * 进行数据交换操作
   *
   * @param data 原始数据
   * @param src 原始位置
   * @param target 目标位置
   */
  private void swap(T[] data, int src, int target) {
    T dataTmp = data[src];
    data[src] = data[target];
    data[target] = dataTmp;
  }
}

优化方式2:随机

就是每次在要排序的数据区间中,随机选择一个点作为分区点,因为是随机挑选的,并不能保证每次挑选的分区点都比较合适,但从概率上来说,也不会每次挑选的分区点都很差。从而,退化到O(n^2)的情况非常的少
代码实现:


/**
 * 快速排序的代码实现
 *
 * <p>使用随机分区点的方法
 *
 * @author liujun
 * @version 0.0.1
 * @date 2019/10/28
 */
public class QuickSortRandomPoint<T> implements SortInf<T> {

  @Override
  public void sort(T[] data) {

    if (data == null || data.length <= 1) {
      return;
    }
    if (!(data[0] instanceof Comparable)) {
      throw new IllegalArgumentException("data not implement compator interface");
    }

    // 调用快速排序
    this.quickSort(data, 0, data.length - 1);
  }

  private void quickSort(T[] data, int s, int t) {

    // 当前需操作元素仅一个,则退出
    if (t - s < 1) {
      return;
    }

    int point = this.partition(data, s, t);
    quickSort(data, s, point - 1);
    quickSort(data, point + 1, t);
  }

  /**
   * 分区函数,在快速排序中使用的是原地分区,实现非常的巧妙
   *
   * <p>函数需要返回分区点
   *
   * @param data 原始数据
   * @param s 开始位置
   * @param t 结束位置
   * @return 分区点的位置
   */
  private int partition(T[] data, int s, int t) {
    // 1,采用随机的方法采用分区点,进行分区
    int countPoint = ThreadLocalRandom.current().nextInt(s, t);
    // 求得中位数后将,最后一个位置与中位数互换即可
    swap(data, countPoint, t);

    // 当前的分区点
    int midPoint = t;

    int i = s;
    for (int j = s; j <= t; j++) {
      // i < j ? -1
      // 如果数据小于分区点,执行互换操作
      if (((Comparable) data[j]).compareTo(data[midPoint]) < 0) {
        // 交换数据
        swap(data, i, j);
        // i向后移动一位,指向分区点
        i += 1;
      }
    }

    // 当结束后,将分区点的位置与i互换,即可完成
    swap(data, i, midPoint);

    return i;
  }

  /**
   * 进行数据交换操作
   *
   * @param data 原始数据
   * @param src 原始位置
   * @param target 目标位置
   */
  private void swap(T[] data, int src, int target) {
    T dataTmp = data[src];
    data[src] = data[target];
    data[target] = dataTmp;
  }
}

快速排序的总结

算法名称最好情况时间复杂度最好情况的原始数据最坏情况时间复杂度最坏情况的原始数据平均情况时间复杂度是否基于比较空间复杂度是否稳定排序算法
快速排序O(nlogn)每次分区都分成大小相同的两个区间O(n^2)原始数据已经有序O(nlogn)O(n)原地排序算法非移定的原地排序算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值