上一篇:数据结构与算法系列十四(归并排序) 中,归并排序利用分治的思想,实现了高效率的排序,时间复杂度是: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为止。排序结束(整个序列排好序)
快速排序代码实现
排序入口函数
/**
* 快速排序入口
* @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.【快速排序】不是稳定的排序算法,存在元素的交换操作,如果有相同值的元素,排序后顺序会发生改变