各类算法时空复杂度、稳定性对比
归并和快排是重点
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 是否稳定 |
冒泡排序 | O(n2) | O(n2) | O(1) | 是 |
选择排序 | O(n2) | O(n2) | O(1) | 不是 |
直接插入排序 | O(n2) | O(n2) | O(1) | 是 |
希尔排序 | O(nlogn) | O(ns) | O(1) | 不是 |
归并排序 | O(nlogn) | O(nlogn) | O(n) | 是 |
快速排序 | O(nlogn) | O(n2) | O(logn) | 不是 |
一、冒泡排序
- 时间复杂度最好的情况是已经排好序
- 时间复杂度最好O(n),平均O(n2),最坏O(n2)
- 空间复杂度O(1)
// 升序冒泡
public int [] bubbleSort(int [] arr){
for(int i = 0; i < arr.length-1; i++){
//定义一个标记,没有发生交换,则说明已经有序
boolean flag = true;
for(int j = 0; j < arr.length-i-1; j++){
// 不断把大的数字往后交换
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = false;
}
}
if(flag){
break;
}
}
return arr;
}
二、选择排序
- 时间复杂度最好的情况是已经排好序
- 时间复杂度最好O(n),平均O(n2),最坏O(n2)
- 空间复杂度O(1)
// 升序简单选择排序
public int[] selectSort(arr){
for(int i = 0; i < arr.length-1; i++){
// 每趟找最小的数
min = i;
for(int j = i+1; j < arr.length; j++){
if(arr[j] < arr[min]){
min = j;
}
}
// 交换最小的数和下标为i的数
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
return arr;
}
三、插入排序
- 时间复杂度最好的情况是已经排好序
- 时间复杂度最好O(n),平均O(n2),最坏O(n2)
- 空间复杂度O(1)
public int [] insertSort(int [] nums){
// i后面的数组表示待插入的数组
for(int i = 1; i < nums.length; i++){
// 记录要插入的数据
int tmp = nums[i];
// 边比较边移动
int j = i;
while(j > 0 && tmp < nums[j-1]){
nums[j] = nums[j-1];
j--;
}
// 插入
if(j < i){
nums[j] = tmp;
}
}
}
四、希尔排序
间隔步长step的数字归为一组,组内进行插入排序,step由大变小
- 当增量序列为K = 2x时,时间复杂度为O(n2)
- 当增量序列为K = (3 * x + 1)时,时间复杂度为O(n^3/2)
- 空间复杂度为O(1)
public int [] shellSort(int [] nums) {
// 每次增量都除以2
for (int step = nums.length/2; step>0; step/=2) {
// 每次都从step开始进行插入,直到数组末尾
for (int i = step; i<nums.length; i++) {
//内部和插入排序一样
int j = i;
int tmp = nums[i];
while (j-step >= 0 && tmp < nums[j-step]) {
nums[j] = nums[j-step];
j = j-step;
}
if (j < i) {
nums[j] = tmp;
}
}
}
}
五、归并排序
原理如下:
对于给定的一组记录,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序,最后再用递归方法将排好序的半子表合并成为越来越大的有序序列
- 时间复杂度最优、平均和最差都是O(nlogn)
- 空间复杂度为:O(n)
public static void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low;// 左指针
int j = mid + 1;// 右指针
int k = 0;
// 把较小的数先移到新数组中
while (i <= mid && j <= high) {
if (a[i] < a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
// 把左边剩余的数移入数组
while (i <= mid) {
temp[k++] = a[i++];
}
// 把右边边剩余的数移入数组
while (j <= high) {
temp[k++] = a[j++];
}
// 把新数组中的数覆盖nums数组
for (int k2 = 0; k2 < temp.length; k2++) {
a[k2 + low] = temp[k2];
}
}
public static void mergeSort(int[] a, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
// 左边
mergeSort(a, low, mid);
// 右边
mergeSort(a, mid + 1, high);
// 左右归并
merge(a, low, mid, high);
System.out.println(Arrays.toString(a));
}
}
六、快速排序
- 最优情况是每次划分正好平分数组
- 最差情况是每次划分落到最边缘,退化为冒泡排序
- 时间复杂度最优O(nlogn),平均O(nlogn),最坏O(n^2)
- 空间复杂度最优O(logn),平均O(logn),最差O(n)
排序实例
public static void sort(int a[], int low, int hight) {
int i, j, index;
if (low > hight) {
return;
}
i = low;
j = hight;
index = a[i]; // 用子表的第一个记录做基准
while (i < j) { // 从表的两端交替向中间扫描
while (i < j && a[j] >= index)
j--;
if (i < j)
a[i++] = a[j]; // 用比基准小的记录替换低位记录
while (i < j && a[i] < index)
i++;
if (i < j) // 用比基准大的记录替换高位记录
a[j--] = a[i];
}
a[i] = index; // 将基准数值替换回 a[i]
sort(a, low, i - 1); // 对低子表进行递归排序
sort(a, i + 1, hight); // 对高子表进行递归排序
}
public static void quickSort(int a[]) {
sort(a, 0, a.length - 1);
}