1.快速排序是基于轴的排序,正常来说我应该把快速排序和归并排序还有堆排序写在一起,但是这套快速排序涉及到了插入排序,因为当快速排序待排序序列的长度变得很小时,使用插入排序代替快速排序可以提高性能和效率。这是因为插入排序在小规模数据上的排序效率较高。通过降级成插入排序,可以避免快速排序在小规模数据上的额外开销,例如递归调用和划分子序列。插入排序在小规模数据上的性能更好,因为它的时间复杂度是O(n^2),其中n是待排序序列的长度。与之相比,快速排序的时间复杂度为O(nlogn)。当n是无穷大的时候自然是快速排序最好。n无穷大时,n的多少次方都会大于logn。
public static void insertSort(int[]array,int start,int end){
for(int i=start+1;i<=end;i++){
int tmp=array[i];
int j=i-1;
for (;j>=start;j--){
if(array[j]>tmp){
array[j+1]=array[j];
}else {
break;
}
}
array[j+1]=tmp;
}
}
上图简单讲了下插入排序的前两轮循环,内循环j<0结束一轮。
2.快速排序算法中的 partition
方法。它的作用是将数组划分为两个部分,使得左边的元素都小于等于轴元素(tmp
),右边的元素都大于轴元素。
private static int partition(int[] array, int left, int right) {
int tmp = array[left]; // 选择起始位置的元素作为轴元素
while (left < right) {
// 从右边开始找到第一个小于轴元素的元素
while (left < right && array[right] >= tmp) {
right--;
}
if (left >= right) {
break;
}
array[left] = array[right]; // 将右边的元素移到左边
// 从左边开始找到第一个大于轴元素的元素
//如果没有进入上面的while循环,
// 比如说最右面的值是4,tmp的值是7,进不去,
// 那么就在if判断之后把array[right]的值放到array[left]中。
// 反之,如果最右面的值得大于tmp中的值的话,
// 就直接将right指针前移好了
while (left < right && array[left] < tmp) {
left++;
}
if (left >= right) {
break;
}
array[right] = array[left]; // 将左边的元素移到右边
}
array[left] = tmp; // 将轴元素放置到最终位置
return left; // 返回轴元素的索引
}
3.取中间元素
分为几种情况,一种是左边元素小于右边,另一种是左边元素大于右边,依次比较array[mid],array[left],array[mid]的值。
private static int middleNum(int []array,int left,int right){
int mid=left+((right-left)>>1);
if(array[left]<array[right]){
if(array[mid]<array[left]){
return left;
}else if(array[mid]>array[right]){
return right;
}else {
return mid;
}
} else {
if(array[right]>array[mid]){
return right;
}else if (array[mid]>array[left]){
return left;
}else {
return mid;
}
}
}
4.显示数组元素
public static void display(int[] array){
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
System.out.println();
}
5 这段代码是快速排序算法中的 quick
方法,用于对数组进行快速排序。
private static void quick(int[] array, int start, int end) {
if (start >= end) {
return;
}
if (end - start <= 15) {
insertSort(array, start, end);
return;
}
int index = middleNum(array, start, end);
swap(array, index, start);
int pivot = partition(array, start, end);
quick(array, start, pivot - 1);
quick(array, pivot + 1, end);
}
首先,它会选择一个轴元素(pivot),并将其放在起始位置。这里使用 middleNum
方法选择轴元素的索引,然后将轴元素与起始位置的元素进行交换。
接下来,它调用 partition
方法,通过比较轴元素和数组中其他元素的大小,将数组划分为两个部分。左侧的子数组包含小于等于轴元素的元素,右侧的子数组包含大于轴元素的元素。partition
方法返回轴元素的索引。
然后,它对轴元素的左侧子数组和右侧子数组分别调用 quick
方法进行递归排序。这样就可以逐步将整个数组划分并排序,运用到了分而治之的思想,所以快速排序也是基于轴的排序。
6.下面是整体的代码
public class Sort {
public static void quickSort(int []array){
quick(array,0,array.length-1);
}
private static void quick(int[]array,int start,int end){
if(start>=end){
return;
}
if(end-start<=15){
insertSort(array,start,end);
return;
}
int index=middleNum(array,start,end);
swap(array,index,start);
int pivot=partition(array,start,end);
quick(array,start,pivot-1);
quick(array,pivot+1,end);
}
private static int partition(int[]array,int left,int right){
int tmp=array[left];
while (left<right){
while (left<right && array[right]>=tmp){
right--;
}
if(left>=right){
break;
}
array[left]=array[right];//如果没有进入上面的while循环,
// 比如说最右面的值是4,tmp的值是7,进不去,
// 那么就在if判断之后把array[right]的值放到array[left]中。
// 反之,如果最右面的值得大于tmp中的值的话,
// 就直接将right指针前移好了
while (left<right&&array[left]<tmp){
left++;
}
if(left>=right){
break;
}
array[right]=array[left];
}
array[left]=tmp;
return left;
}
public static void insertSort(int[]array,int start,int end){
for(int i=start+1;i<=end;i++){
int tmp=array[i];
int j=i-1;
for (;j>=start;j--){
if(array[j]>tmp){
array[j+1]=array[j];
}else {
break;
}
}
array[j+1]=tmp;
}
}
private static void swap(int []array,int i,int j){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
private static int middleNum(int []array,int left,int right){
int mid=left+((right-left)>>1);
if(array[left]<array[right]){
if(array[mid]<array[left]){
return left;
}else if(array[mid]>array[right]){
return right;
}else {
return mid;
}
} else {
if(array[right]>array[mid]){
return right;
}else if (array[mid]>array[left]){
return left;
}else {
return mid;
}
}
}
public static void display(int[] array){
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
System.out.println();
}
}
7.主函数中的代码
public class Test {
public static void main(String[] args) {
int[] array={4,9,6,3,7};
Sort.display(array);
Sort.quickSort(array);
Sort.display(array);
}
}
排序得到的结果
8.时间复杂度和空间复杂度:
快速排序的平均时间复杂度为 O(n log n),最坏情况下的时间复杂度为 O(n^2),其中 n 是待排序数组的长度。快速排序的空间复杂度为 O(log n)。
时间复杂度分析:
- 平均情况下,快速排序的时间复杂度为 O(n log n)。这是因为在每次划分操作中,平均情况下能将数组划分为接近相等大小的两个子数组。每次划分操作的时间复杂度为 O(n),而递归的深度为 O(log n),因此总体时间复杂度为 O(n log n)。
- 最坏情况下,快速排序的时间复杂度为 O(n^2)。最坏情况发生在每次划分操作中,选择的轴元素都是当前子数组中的最大或最小元素。这种情况下,每次划分只能将数组划分为一个较小的子数组和一个空的子数组,导致递归的深度为 n,因此总体时间复杂度为 O(n^2)。为了避免最坏情况,通常会采用随机选择轴元素的策略或者使用三数取中法来选择轴元素,以提高算法的性能。
空间复杂度分析:
- 快速排序的空间复杂度为 O(log n)。这是因为快速排序使用递归进行排序,每次递归调用都会产生一层栈空间用于保存函数调用的上下文信息。在最坏情况下,递归的深度为 n,因此空间复杂度为 O(log n)。需要注意的是,这里的空间复杂度仅考虑了额外使用的栈空间,而不包括原始数组的空间。