数据结构与算法系列十五(快速排序)

140 阅读7分钟

上一篇:数据结构与算法系列十四(归并排序) 中,归并排序利用分治的思想,实现了高效率的排序,时间复杂度是:O(nlogn) 。不知道你有没有发现,归并排序有一个不完美的地方?如果你还没有发现,让我们一起来分析一下。

归并排序的核心有两个过程:分解、合并。通过分解函数递归分解子问题;通过合并函数实现排序。那么在合并函数中,我们需要两个临时数组来协助排序:

 public static void merge(Integer[] array,int left,int mid,int right){
        // 左边数组大小
        int[] leftA = new int[mid-left];
        // 右边数组大小
        int[] rightA = new int[right-mid+1];
     
     .................省略其它代码....................
 }

到这里,我想你应该知道归并排序不完美的地方了,归并排序不是原地排序算法,它的空间复杂度是:O(n)

那有没有时间复杂度满足:O(nlogn),又同时空间复杂度是:O(1)的排序算法呢?答案是:有。它就是我们今天的主角:快速排序

#考考你:
1.你知道快速排序的核心思想吗?
2.你能用java实现快速排序吗?
3.你知道快速排序与归并排序的区别与联系吗?

案例

快速排序核心思想

假设有一个待排序序列:[4, 5, 6, 3, 2, 1]。我们需要按照升序进行排序,排序后的序列是这 样的:[1, 2, 3, 4, 5, 6]。

如何通过快速排序实现呢?

快速排序核心思想:

快速排序,与归并排序算法类似,也利用了分而治之的思想,它的排序过程:

1.每一次从待排序序列中,选择一个元素,作为分区元素:pivot

2.比如选择第1个元素,或者最后1个元素

3.将大于pivot的元素放在一边,将小于pivot的元素放在另一边

4.一次分区结束,把待排序序列分成3个部分

#4.1.小于分区元素子序列:<pivot
#4.2.分区元素:=pivot
#4.3.大于分区元素子序列:>pivot

5.将小于分区元素子序列,继续递归分区排序

6.将大于分区元素子序列,继续递归分区排序

7.一直到子分区序列剩下一个元素,即low>=high为止。排序结束(整个序列排好序)

image.png

快速排序代码实现

排序入口函数

/**
* 快速排序入口
* @param array  待排序数组
* @param n     数据规模
*/
public static void sort(Integer[] array,int n){
  // 如果数据规模小于等于1,不需要排序
  if(n <= 1){
     return;
  }

   // 快速排序(递归函数)
   quickSort(array,0,n-1);
}

递归函数

/**
* 快速排序(递归函数)
* @param array
* @param low
* @param high
*/
public static void quickSort(Integer[] array,int low,int high){
   // 终止条件
   if(low >= high){
      return;
   }

   // 分区函数寻找:pivot
   int mid = partition(array,low,high);

   // 低位递归排序
   quickSort(array,low,mid-1);

   // 高位递归排序
   quickSort(array,mid+1,high);

}

分区函数

/**
* 分区函数
* @param array
* @param low
* @param high
* @return
*/
public static int partition(Integer[] array,int low,int high){

   // 定义两个临时变量,方便输出数组(它们与排序无关)
   int tmpLow = low;
   int tmpHigh = high;

   // 选取第一个元素作为pivot(分区点)
   int pivot = array[low];

   System.out.println("2.1.本次分区:low="+low+"&high="+high+",待排序序列:"+printArray(array,tmpLow,tmpHigh)+",选择分区元素pivot:array["+low+"]="+pivot);

   // 循环处理low<high
   System.out.println("2.2.循环操作low<high:从右往左,将小于pivot的数据,放入左边.pivot="+pivot);
   System.out.println("2.3.循环操作low<high:从左往右,将大于pivot的数据,放入右边.pivot="+pivot);
  while (low<high){

     // 从右往左,将小于pivot的数据,放入左边
     while(low<high && array[high]>=pivot){
         high -=1;
     }
     array[low] = array[high];
     System.out.println("2.2.1.本次循环结束,low="+low+"&high="+high+",待排序序列:"+printArray(array,tmpLow,tmpHigh));

     // 从左往右,将大于pivot的数据,放入右边
     while(low<high && array[low]<=pivot){
          low +=1;
     }
     array[high]=array[low];
     System.out.println("2.3.1.本次循环结束,low="+low+"&high="+high+",待排序序列:"+printArray(array,tmpLow,tmpHigh));

    }

   // 找到分区点low,并返回
   array[low] = pivot;
   System.out.println("2.4.本次分区结束pivot:array["+low+"]="+pivot+"后,待排序序列:"+printArray(array,tmpLow,tmpHigh));
   System.out.println("2.5.-------------------------华丽丽分割线-------------------------");
      return low;
 }

分区函数中数组输出辅助函数:

/**
* 根据数组起始位置,结束位置输出数组内容
* @param array
* @param start
* @param end
* @return
*/
public static String printArray(Integer [] array,int start,int end){
     StringBuilder builder = new StringBuilder();
     builder.append("[");
     for(int i = start; i <= end;i++){
         builder.append(array[i]).append(",");
     }

     // 去掉最后一个:,
     String substring = builder.substring(0, builder.length() - 1);
     substring += "]";

      return substring;
}

测试代码

public static void main(String[] args) {
   // 初始化测试数组
   Integer[] array = {4,5,6,3,2,1};
   // 1.排序前
   System.out.println("1.排序前数组:" + Arrays.deepToString(array));

   // 排序
   System.out.println("2.开始排序-------------------------------");
   sort(array,array.length);

   // 3排序后
   System.out.println("3.排序后数组:" + Arrays.deepToString(array));
}
D:\02teach\01soft\jdk8\bin\java 
    com.anan.algorithm.sort.QuickSort
1.排序前数组:[4, 5, 6, 3, 2, 1]
2.开始排序-------------------------------
2.1.本次分区:low=0&high=5,待排序序列:[4,5,6,3,2,1],选择分区元素pivot:array[0]=4
2.2.循环操作low<high:从右往左,将小于pivot的数据,放入左边.pivot=4
2.3.循环操作low<high:从左往右,将大于pivot的数据,放入右边.pivot=4
2.2.1.本次循环结束,low=0&high=5,待排序序列:[1,5,6,3,2,1]
2.3.1.本次循环结束,low=1&high=5,待排序序列:[1,5,6,3,2,5]
2.2.1.本次循环结束,low=1&high=4,待排序序列:[1,2,6,3,2,5]
2.3.1.本次循环结束,low=2&high=4,待排序序列:[1,2,6,3,6,5]
2.2.1.本次循环结束,low=2&high=3,待排序序列:[1,2,3,3,6,5]
2.3.1.本次循环结束,low=3&high=3,待排序序列:[1,2,3,3,6,5]
2.4.本次分区结束pivot:array[3]=4后,待排序序列:[1,2,3,4,6,5]
2.5.-------------------------华丽丽分割线-------------------------
2.1.本次分区:low=0&high=2,待排序序列:[1,2,3],选择分区元素pivot:array[0]=1
2.2.循环操作low<high:从右往左,将小于pivot的数据,放入左边.pivot=1
2.3.循环操作low<high:从左往右,将大于pivot的数据,放入右边.pivot=1
2.2.1.本次循环结束,low=0&high=0,待排序序列:[1,2,3]
2.3.1.本次循环结束,low=0&high=0,待排序序列:[1,2,3]
2.4.本次分区结束pivot:array[0]=1后,待排序序列:[1,2,3]
2.5.-------------------------华丽丽分割线-------------------------
2.1.本次分区:low=1&high=2,待排序序列:[2,3],选择分区元素pivot:array[1]=2
2.2.循环操作low<high:从右往左,将小于pivot的数据,放入左边.pivot=2
2.3.循环操作low<high:从左往右,将大于pivot的数据,放入右边.pivot=2
2.2.1.本次循环结束,low=1&high=1,待排序序列:[2,3]
2.3.1.本次循环结束,low=1&high=1,待排序序列:[2,3]
2.4.本次分区结束pivot:array[1]=2后,待排序序列:[2,3]
2.5.-------------------------华丽丽分割线-------------------------
2.1.本次分区:low=4&high=5,待排序序列:[6,5],选择分区元素pivot:array[4]=6
2.2.循环操作low<high:从右往左,将小于pivot的数据,放入左边.pivot=6
2.3.循环操作low<high:从左往右,将大于pivot的数据,放入右边.pivot=6
2.2.1.本次循环结束,low=4&high=5,待排序序列:[5,5]
2.3.1.本次循环结束,low=5&high=5,待排序序列:[5,5]
2.4.本次分区结束pivot:array[5]=6后,待排序序列:[5,6]
2.5.-------------------------华丽丽分割线-------------------------
3.排序后数组:[1, 2, 3, 4, 5, 6]

Process finished with exit code 0

讨论分享

#考考你答案:
1.你知道快速排序的核心思想吗?
  1.1.快速排序,应用了分而治之的思想
  1.2.每一次从待排序序列中,选择一个分区元素:pivot
  1.3.将小于pivot的元素放在一边,将大于pivot的元素放在另一边
  1.4.每一次分区结束,待排序序列分为三个部分:
    a.小于分区元素pivot的子序列
    b.分区元素
    c.大于分区元素pivot的子序列
  1.5.递归分区排序,小于分区元素子序列
  1.6.递归分区排序,大于分区元素子序列
  1.7.直到待排序分区元素子序列,剩下一个元素,即满足数组下标:low>=high
  1.8.那么整个排序结束

2.你能用java实现快速排序吗?
  2.1.参考【3.2】节代码实现
  
3.你知道快速排序与归并排序的联系与区别吗?
  3.1.联系
    a.都应用了分而治之的思想
    b.都是高效的排序算法,时间复杂度都是:O(nlogn)
    
  3.2.区别
    a.【归并排序】不是原地排序算法,空间复杂度:O(n)
    b.【快速排序】通过设计分区函数:partition,可以在原地排序,空间复杂度:O(1)
    c.【归并排序】是稳定的排序算法,如果有相同值的元素,排序后顺序保持不变
    d.【快速排序】不是稳定的排序算法,存在元素的交换操作,如果有相同值的元素,排序后顺序会发生改变