排序方法 | 最优时间 | 平均时间 | 最差时间 | 辅助空间 | 稳定性 |
冒泡 | O(n) | O(n^2) | O(n^2) | 稳定 | |
插入 | O(n) | O(n^2) | O(n^2) | 稳定 | |
选择 | O(n^2) | O(n^2) | O(n^2) | 不稳定 | |
希尔 | O(n^1.25) | 不稳定 | |||
堆 | O(nlgn) | O(nlgn) | O(nlgn) | 不稳定 | |
快速 | O(nlgn) | O(nlgn) | O(nlgn) | 不稳定 | |
归并 | O(nlgn) | O(nlgn) | O(nlgn) | O(n) | 稳定 |
认为排序到从小到大有序,推荐一个网站,排序可视化:https://visualgo.net/en/sorting
交换:
public void exch(Comparable[] a, int i, int j)
{
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
冒泡排序:从第一个开始,比较相邻元素,比右边大则交换位置,知道最大元素排到最后一个位置,反复此过程
public void bubbleSort(Comparable[] a)
{
if (a == null)
return;
for (int i = 0; i < a.length; i++)
{
for (int j = 1; j < a.length - i; j++) // 控制冒泡范围
{
if (a[j].compareTo(a[j-1]) < 0) // 如果a[j] < a[j-1],交换两者位置
{
}
}
}
}
插入排序:从第一个元素开始,认为第一个元素有序,然后第二元素插入前面的有序子数组,插入过程为前面的子数组的最后一个开始与之比较,知道插入合适的位置,再第三个,第四个,。。。,直到结束
public void insertSort(Comparable[] a)
{
if (a == null)
return;
for (int i = 1; i < a.length; i++)
{
for (int j = i; j >= 0 && a[j].compareTo(a[j-1]) < 0; j--)
{
exch(a, j, j-1);
}
}
}
选择排序:从第一个元素开始,与后面每个元素比较,找到最小元素,然后交换第一个元素与最小元素位置,在从第二个开始。反复这个过程
public void selectSort(Comparable[] a)
{
if (a == null)
return;
for (int i = 0; i < a.length; i++)
{
int min = i;
for (int j = i + 1; j < a.length; j++)
{
if (a[j].compareTo(a[min]) < 0)
min = j;
}
exch(a, min, i);
}
}
希尔排序:使得数组中任意间隔为h的元素都是有序的,确定初始h的值,再逐步缩小h的值
public void hillSort(Comparable[] a)
{
if (a == null)
return;
int N = a.length;
int h = 1;
while (h < N/3)
h = 3 * h + 1;
while (h >= 1)
{
for (int i = h; i < N; i++)
for (int j = i; j >= h; j -= h)
if (a[j].compareTo(a[j-h]) < 0)
exch(a, j, j-h);
h /= 3;
}
}
堆排序:堆的构造(堆有序)+ 下沉排序
堆的定义:1)堆有序:当一颗二叉树的每个节点均大于等于它的两个子节点
2)根节点是堆有序的二叉树中的最大节点
3)堆的特点,假设存储在数组中,数组长度为N,则使用数组a[N+1],a[0]不放数值。父节点位置为k,则子节点位置为2k和2k+1,子节点位置为k,父节点位置为k/2
下沉操作:
private void sink(Comparable[] a, int k, int N)
{ // N表示数组长度
while (2 * k <= N)
{
int j = 2 * k;
if (j < N && a[j].compareTo(a[j+1]) < 0) // 找到较大的那个子节点
j++;
if (a[k].compareTo(a[j]) >= 0) // 直到父节点不再小于子节点的值,终止循环
break;
exch(a, k, j); // 交换子节点与父节点的位置
k = j;
}
}
堆的构造: 令k=N/2(找到最后一点的父节点),从此节点开始,直到k=1,执行下沉操作,达到堆有序的状态
for (int k = N / 2; k >= 1; k--)
sink(a, k, N);
下沉排序:堆有序中,最大元素必定在根节点
while (N > 1)
{
exch(a, 1, N--);
sink(a, 1, N);
}
堆排序:
public void heapSort(Comparable[] a)
{
if (a == null)
return;
int N = a.length;
Comparable[] aCopy = new Comparable[N+1];
for (int i = 1; i < N+1; i++)
aCopy[i] = a[i-1]; // 使得a[0]~a[N-1] 变成 a[1]~a[N]
// 构造有序堆
for (int k = N / 2; k >= 1; k--)
sink(aCopy, k, N);
// 下沉排序
while (N > 1)
{
exch(aCopy, 1, N--);
sink(aCopy, 1, N);
}
for (int i = 0; i < N; i++)
a[i] = aCopy[i+1]; // 使得a[1]~a[N] 变成 a[0]~a[N-1]
}
快速排序:一种“分治”的排序算法,递归发生在处理数组(切分)之后
标准快速排序:取a[lo](数组第一个元素)作为切分匀速,从数组左边开始扫描找到一个大于等于a[lo]的元素,从数组右端开始扫描找到一个小于等于a[lo]的元素,两则交换位置。反复执行得到a[lo]...a[j]...a[],交换a[lo]与a[j]的位置,使得a[lo](现在在第j位置)左边元素全部小于等于它,右边元素全部大于等于它,再对左右两个子数组(递归)用相同方式处理。
性能特点:简洁,比较次数少,切分不平衡时低效
public void quickSort(Comparable[] a)
{
quickSort(a, 0, a.length-1);
}
public void quickSort(Comparable[] a, int lo, int hi)
{
if (lo >= hi)
return;
int j = partition(a, lo, hi);
quickSort(a, lo, j-1);
quickSort(a, j+1, hi);
}
public int partition(Comparable[]a, int lo, int hi)
{
int i = lo, j = hi + 1; // 左右扫描指针
Comparable v = a[lo]; // 切分元素
while (true)
{
while (a[++i].compareTo(v) < 0)
if (i == hi)
break;
while (v.compareTo(a[--j]) < 0)
if (j == lo)
break;
if (i >= j)
break;
exch(a, i, j);
}
exch(a, lo, j); // 将v=a[j]放入正确的位置
return j;
}
算法改进:可以先随机打乱数组,再运用快速排序,当相等的元素比较多时,考虑三向切分快速排序
归并排序:将两个有序的数组归并到一个有序的数组,递归发生在处理数组之前,需要借助辅助数组,两个有序数组各自有个从头开始的指针,将较小者依此放入新的大数组中。
自顶向下的归并排序:一般情况,数组并不是能分成左右两个子数组有序,因此采用“分治”的思想,将数组分为两半,再分为两半,。。。,直到每个子数组只有一个元素,开始归并。
Comparable[] aCopy;
public void mergeSort(Comparable[] a)
{
aCopy = new Comparable[a.length];
mergeSort(a, 0, a.length-1);
}
public void mergeSort(Comparable[] a, int lo, int hi)
{
if (hi <= lo)
return;
int mid = (lo + hi) / 2;
mergeSort(a, lo, mid);
mergeSort(a, mid+1, hi);
mergeSort(a, lo, mid, hi);
}
public void mergeSort(Comparable[] a, int lo, int mid, int hi)
{
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) // 复制
aCopy[k] = a[k];
for (int k = lo; k <= hi; k++) // 归并
{
if (i > mid)
a[k] = aCopy[j++];
else if (j > hi)
a[k] = aCopy[i++];
else if (aCopy[j].compareTo(aCopy[i]))
a[k] = aCopy[j++];
else
a[k] = aCopy[i++];
}
}