Java 主要排序方法为 java.util.Arrays.sort()
- 对基本数据类型使用三向切分的快速排序
- 对引用类型使用归并排序
对实体类排序,需要实现 Comparable 接口
排序定义
public class Sort<T extends Comparable<T>> {}
实体类定义
public class Pojo implements Comparable<Pojo>, Serializable {
private int id;
...
@Override
public int compareTo(Pojo p) {
return Integer.compare(this.id, p.id);
}
}
渐进符号
递归
- 递归总有一个最简单的情况,方法的第一条语句总是一个包含 return 的条件语句
- 递归调用总是去尝试解决一个规模更小的子问题
- 递归调用的父问题和尝试解决的子问题之间不应该有交集
1. 冒泡排序
- 从上到下不断比较、交换相邻的两个元素
- 经过一轮的循环后,最大元素沉到最底下
- 进行下一轮循环比较,把第二大元素沉到倒数第二位置
- 依次类推
public class BubbleSort {
public void bst(int[] array){
int i, j;
for(i = 0; i < array.length - 1; i++) {
for(j = 0; j < array.length - 1 - i; j++) {
if(array[j] > array[j+1]) {
int tmp;
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
}
2. 快速排序
最好情况:O(nlogn)
最坏情况:O(n2) 发生在已经排好序或者逆序,即 pivot 一侧是空的
- 随机选择一个元素作为基准,建立分离函数使左边元素都小于基准,右边元素都大于基准,在没有优化的情况下,一般直接选第一个元素作为基准即可
- 分离函数从最低端开始依次与基准元素对比,遇到大于基准的元素则交换位置;再从最高端开始依次与基准元素对比,小于基准元素则交换位置
- 经过重复多次3过程,最低端和最高端下标最终相等,此时的下标即基准位置
- 分别对基准左边和基准右边两个序列递归调用快速排序
- 最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分等等,即数组最开始就是有序的,在进行快速排序前可以先随机打乱数组,可以使用shuffle函数打乱数组
public class QuickSort {
public void qs(int[] array, int low, int high) {
int pivot; //pivot必须定义为局部变量,随当前方法销毁
if (low >= high)
return -1;
pivot = partition(array, low, high);
qs(array, low, pivot - 1);
qs(array, pivot + 1, high);
}
public int partition(int[] array, int low, int high) {
int pivotKey = array[low];
int tmp;
while (low < high) {
while (low < high && array[high] >= pivotKey)
high--;
tmp = array[high];
array[high] = array[low];
array[low] = tmp;
while (low < high && array[low] <= pivotKey)
low++;
tmp = array[low];
array[low] = array[high];
array[high] = tmp;
}
return low;
}
}
3. 插入排序
最坏情况:O(n2)
- 把要插入的元素存放到一个临时位置作为哨兵,可被作为哨兵的元素用 r[i] 表示
- 哨兵从第二个位置,即 i=2 开始选择,当比前一个位置的元素小时,就作为哨兵,否则后移一位,即 i++ 后继续判断
- 把选出的元素临时存到r[0],此时r[0]就是哨兵
- 从哨兵元素位置开始,依次比较前一个元素和哨兵元素的大小,大于哨兵的元素均后移一位,直到不大于哨兵元素时停止移动
- 完成移动后,把临时位置存的哨兵元素存入正确的位置 r[j + 1]
- 每完成一次哨兵插入,就 i++ 选取新的哨兵重复前边的操作
public class InsertSort {
public void ist(int[] array) {
int i, j;
for (i = 1; i <= array.length - 1; i++) {
if (array[i] < array[i-1]) {
int tmp = array[i];
for(j = i - 1; j >= 0 && array[j] > tmp ; j--){
array[j+1] = array[j];
}
array[j+1] = tmp;
}
}
}
}
4. 希尔排序
- 希尔排序是插入排序的改进,插入排序选出哨兵后,每次前移一位与哨兵比较,希尔排序选出哨兵后,每次前移increment位与哨兵比较
- 设置合理的increment增量计算公式是关键,一般用increment=increment/大于2的整数,保证increment可以随循环逐渐递减到1
public class ShellSort {
public void sst(int[] array) {
int i, j;
int increment = array.length;
do {
increment = increment / 3;
for (i = increment; i <= array.length - 1; i++) {
if (array[i] < array[i - increment]) {
int tmp = array[i];
for (j = i - increment; j >= 0 && array[j] > tmp; j -= increment)
array[j + increment] = array[j];
array[j + increment] = tmp;
}
}
}
while (increment > 0) ;
}
}
5. 归并排序
最好、平均、最坏都是:nlogn
Merge:比较顶部元素大小,依次放入较小的元素,最后剩余的元素,逐个拼接到合并数组的后面
C
void MergeSort(SqList *L)
{
MSort(L->r, L->r, 1, L->length);
}
void MSort(int SR[], int TR1[], int s, int t)
{
int m;
int TR2[MAXSIZE + 1]; //TR2作为辅助空间存放分裂后的新数组
if (s == t)
TR1[s] = SR[s];
else
{
m = (s + t) / 2;
MSort(SR, TR2, s, m); //分裂后的左部分递归再次分裂
MSort(SR, TR2, m + 1, t); //分裂后的右部分递归再次分裂
Merge(TR2, TR1, s, m, t); //归并:排序后合并
}
}
void Merge(int SR[], int TR[], int i, int m, int n)
{
int j, k, l; //j表示右部分的当前下标,范围[m+1,n];左部分范围[i,m];k表示归并后数组的当前下标
for (j = m + 1, k = i; i <= m && j <= n; k++)
{
if (SR[i] < SR[j]) //比较当前左和右的顶部元素大小,较小的放入合并后数组
TR[k] = SR[i++]; //如果左小于右,则左部分i位置元素存入归并数组
else
TR[k] = SR[j++]; //否则,如果右小于左,则有部分j位置元素存入归并数组
}
//当左部分或右部分中某部分完全遍历完,另一个可能还未遍历完,此时再遍历剩余的部分续接到合并的数组
if (i <= m) //如果左侧没遍历完
{
for (l = 0; l <= m - i; l++)
TR[k + l] = SR[i + l]; //剩余的部分必定是已排好序的,直接拼接到后面就行
}
if (j <= n) //如果右侧没遍历完
{
for (l = 0; l <= n - j; l++)
TR[k + l] = SR[j + l];
}
}
Java
public class MergeSort {
public void mst(int[] array, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
mst(array, low, mid);
mst(array, mid + 1, high);
merge(array, low, mid, high);
}
}
public void merge(int[] array, int low, int mid, int high) {
int[] tmp = new int[array.lengt
int i = low;
int j = mid + 1;
int k = low;
int x = low;
while (i <= mid && j <= high) {
if (array[i] < array[j]) {
tmp[k++] = array[i++];
} else {
tmp[k++] = array[j++];
}
}
while (i <= mid) {
tmp[k++] = array[i++];
}
while (j <= high) {
tmp[k++] = array[j++];
}
while (x <= high) {
array[x] = tmp[x++];
}
}
}
可以看到归并排序应用的是分治策略
分治法
- 将原问题分解为几个规模较小但类似于原问题的子问题
- 递归求解这些子问题
- 最后合并子问题的解来建立原问题的解