一,前言
排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减地排列起来的操作。而对于一个排序算法 而言,我们通常会从以下三个方面分析其性能:
- 稳定性:假设待排序序列中存在多个重复值,若是经过排序后,重复值之间的相对位置不变,则称这个排序算法是稳定的,否则就是不稳定。
- 时间复杂度:代码执行次数随n的增长趋势,通常使用大O的渐进法表示
- 空间复杂度:额外存储随n的增长趋势,其中递归算法空间=递归栈的最大深度,通常使用大O渐进表示法
常见的基于比较的排序算法 有七种,对于这七种排序算法,又因其实现方式不同划分为以下四类 :
- 插入排序:直接插入排序、希尔排序
- 选择排序:选择排序、堆排序
- 交换排序:冒泡排序、快速排序
- 归并排序:归并排序
本节内容主要讲解交换排序,并且都是以排升序为例进行讲解。
二,冒泡排序
1,算法思想
反复比较相邻两个元素,如果顺序不对则交换,让较大的元素像泡泡一样冒到待排序序列末尾。每一趟结束后,当前未排序区间里的最大值会被放到最终位置,下一趟的待排序范围就缩小一个元素。
2,算法特性
适合教学与小规模的数据排序,实际工程中一般不会使用。
时间复杂度:最坏/平均情况下为O(N^2),最好情况下为O(N)
空间复杂度:O(1)
稳定性:稳定
3,排序过程演示

4,算法实现
java
/**
* 冒泡排序
* @param array 待排序序列
*/
public static void bubbleSort(int[] array) {
if(array == null) return;
int n = array.length;
// 外层循环表示排序趟数,n个元素最多排序n-1趟
for(int i = 0; i < n - 1; i ++) {
boolean sorted = true;
// 内部循环表示待排序范围
for(int j = 0; j < n - 1 - i; j ++) {
if(array[j] > array[j + 1]) {
swap(array, j, j + 1);
sorted = false;
}
}
// 小优化:若是本轮排序没有交换则已经有序,无需再排序
if(sorted) break;
}
}
/**
* 交换数组中两个下标的元素
* @param array 待操作序列
* @param i 下标1
* @param j 下标2
*/
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
三,快速排序
1,算法思想
快速排序采用分治思想。任取待排序序列中的一个元素作为基准值,按照关键字将当前区间划分为两个子序列:左子序列中的元素均不大于基准值,右子序列中的元素均不小于基准值,并将基准值放到其最终有序位置。然后对左右两个子序列分别重复上述划分过程,直到子序列长度为 0 或 1 时自然有序。
其分治过程可概括为:
-
选择基准值:从当前区间中选取一个元素作为基准值;
-
划分区间:将当前区间划分为左、右两个子序列,使左子序列元素不大于基准值,右子序列元素不小于基准值,基准值到达最终位置;
-
递归处理:对基准值左右两个子序列分别递归执行快速排序,直到子序列长度不超过 1。
2,算法特性
平均性能好、常数小、缓存友好、实践中通常很快。
时间复杂度:最好/平均情况下为O(N * logN),最坏情况下为O(N^2)
空间复杂度:平均情况下为O(logN),最坏情况下为O(N)
稳定性:不稳定
3,排序过程演示
快速排序有多种划分方式和优化策略。常见划分方式有二路划分和三路划分:二路划分将区间分为"小于基准"和"不小于基准"两部分;三路划分进一步将"等于基准"的元素集中处理,适合大量重复元素的场景。
常见优化包括:
- 基准值优化:如随机基准、三数取中,主要目的为降低最坏情况概率
- 小区间优化:子区间长度较小时改用直接插入排序,减少递归调用
- 递归优化:尾递归优化或较小区间递归、较大区间循环处理,控制递归栈深度
为了便于表达最基本的排序逻辑,以下实现采用基础二路划分,固定选择端点作为基准,未做上述优化,递归划分过程如下图所示:

注:
上图仅展示递推与划分的过程,而具体每一次递归时划分数组的逻辑略,下文有详解。
划分区间也有多种方式,如Hoare法、挖坑法、前后指针法等等,在基础教学示例中可任选一种,但不同划分对常数和重复元素性能有影响。此处我使用的是前后指针法,划分区间过程如下图所示:

4,算法实现
java
/**
* 快速排序-递归实现
* @param array 待排序数组
* @param left 左边界
* @param right 右边界
*/
public static void quickSort(int[] array, int left, int right) {
// 递归出口
if(left >= right) {
return;
}
// 划分数组
int pivot = partition(array, left, right);
// 递归左右区间
quickSort(array, left, pivot - 1);
quickSort(array, pivot + 1, right);
}
/**
* 按照基准值划分数组
* -使用当前区间最左侧元素作为基准值
* -本实现未优化,实际使用建议三数取中/随机基准/小区间插入排序
* @param array 待操作数组
* @param left 左边界
* @param right 右边界
* @return int
*/
private static int partition(int[] array, int left, int right) {
// 记录基准值
int pivotValue = array[left];
int pivotIndex = left;
// 划分数组
while(left < right) {
// 基准值在最左侧,必须先移动右指针
while(left < right && array[right] >= pivotValue) {
right --;
}
while(left < right && array[left] <= pivotValue) {
left ++;
}
swap(array, left, right);
}
// 相遇点为基准值有序位置
swap(array, pivotIndex, left);
return left;
}
/**
* 交换数组中两下标元素
* @param array 待操作数组
* @param i 下标1
* @param j 下标2
*/
private static void swap(int[] array ,int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}