目录
排序算法概述
一、基于比较的排序
1.选择排序
2.冒泡排序
3.插入排序
4.希尔排序
5.快速排序
6.归并排序
7.堆排序
二、线性时间的排序
8.计数排序
9.桶排序
10.基数排序
常见排序算法主要分为两种(本文log皆以2为底):
基于比较的排序:被排序对象属于Compareable类型,使用CompareTo()进行比较排序。除了赋值、引用运算外,这是仅有的对输入数据的操作。由决策树可证,对于含n个元素的一个输入序列,任何比较排序算法在最坏情况下,都需要做Ω(NlogN)次比较。比较排序算法的运行速度不会快于NlogN,这就是基于比较的排序算法的时间下界。
线性时间的排序:在某些特殊情况下以线性时间进行排序依然是可能的,不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
算法复杂度:
表格引自:https://www.cnblogs.com/onepixel/articles/7674659.html
算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
选择排序 | O(N²) | O(N²) | O(N²) | O(1) | 不稳定 |
冒泡排序 | O(N²) | O(N²) | O(N) | O(1) | 稳定 |
插入排序 | O(N²) | O(N²) | O(N) | O(1) | 稳定 |
希尔排序 | O(N^1.3) (shell序列) | O(N²) | O(N^1.3) | O(1) | 不稳定 |
快速排序 | O(N logN) | O(N²) | O(N logN) | O(N logN) | 不稳定 |
归并排序 | O(N logN) | O(N logN) | O(N logN) | O(N) | 稳定 |
堆排序 | O(N logN) | O(N²) | O(N logN) | O(1) | 不稳定 |
计数排序 | O(N+M) | O(N+M) | O(N+M) | O(N+M) | 稳定 |
桶排序 | O(N+M) (M个桶) | O(N²) | O(N) | O(N+M) | 稳定 |
基数排序 | O(p(N+M)) (p是趟数) | O(p(N+M)) | O(p(N+M)) | O(N+M) | 稳定 |
选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
/**
* 选择排序(O(N²)) 不稳定
*
* 选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,
* 然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
*
* @param a an array of Comparable items
* @param item type
*/
public static > void selectionSort(T[] a) {
int minIndex;
for (int i = 0; i < a.length - 1; i++) {
minIndex = i;
for (int j = i; j < a.length; j++) {
if (a[j].compareTo(a[minIndex]) < 0) {
minIndex = j;
}
}
swapReference(a, i, minIndex);
}
}
/**
* 交换数组两个位置的元素
*
* @param a an array of Comparable items
* @param pos1 position1
* @param pos2 position2
* @param item type
*/
private static > void swapReference(T[] a, int pos1, int pos2) {
T temp = a[pos1];
a[pos1] = a[pos2];
a[pos2] = temp;
}
冒泡排序:把较小的元素往前调或者把较大的元素往后调,通过对相邻两个元素进行大小的比较,根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换。
/**
* 冒泡排序(O(N²)) 稳定
*
* 冒泡排序:把较小的元素往前调或者把较大的元素往后调,通过对相邻两个元素进行大小的比较,
* 根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换。
*
* @param a an array of Comparable items
* @param item type
*/
public static > void bubbleSort(T[] a) {
for (int i = 0; i < a.length - 1; i++) {
for (int j = 0; j < a.length - 1 - i; j++) {
if (a[j].compareTo(a[j + 1]) > 0) {
swapReference(a, j, j + 1);
}
}
}
}
插入排序:由N-1趟排序组成,对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。
/**
* 插入排序(O(N²)) 稳定
*
* 插入排序:由N-1趟排序组成,对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。
*
* @param a an array of Comparable items
* @param item type
*/
public static > void insertionSort(T[] a) {
int j;
//p=1到N-1趟排序(只有一个元素时是已排序的状态,既p=0已排序)
for (int p = 1; p < a.length; p++) {
T tmp = a[p];
for (j = p; j > 0 && tmp.compareTo(a[j - 1]) < 0; j--) {
a[j] = a[j - 1];
}
a[j] = tmp;
}
}
希尔排序:使用一个序列h1,h2,...,ht,叫做增量序列。只要 h1=1,任何增量序列都是可行的。不过,有些增量序列比另外一些增量序列要好。Shell序列:ht=N/2和h(k)=h(k+1)/2。
/**
* 希尔排序(O(N)~O(N³)) 不稳定
*
* 希尔排序:使用一个序列h1,h2,...,ht,叫做增量序列。
* 只要 h1=1,任何增量序列都是可行的。不过,有些增量序列比另外一些增量序列要好。Shell建议的序列:ht=N/2和h(k)=h(k+1)/2。
*
* @param a an array of Comparable items
* @param item type
*/
public static > void shellSort(T[] a) {
int j;
for (int gap = a.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < a.length; i++) {
T tmp = a[i];
for (j = i; j >= gap && tmp.compareTo(a[j - gap]) < 0; j -= gap) {
a[j] = a[j - gap];
}
a[j] = tmp;
}
}
}
快速排序:分治的递归算法,在一趟排序中选定一个枢纽元,将要排序的数据分割成两部分,其中一部分的所有数据都比枢纽元大,另外一部分的所有数据都比枢纽元小,对这两部分数据分别进行快速排序。
public class QuickSort {
/**
* 递归终止条件
*/
private static final int CUTOFF = 3;
/**
* 快速排序(O(N logN)) 不稳定
*
* 快速排序:分治的递归算法,在一趟排序中选定一个枢纽元,将要排序的数据分割成两部分,
* 其中一部分的所有数据都比枢纽元大,另外一部分的所有数据都比枢纽元小,对这两部分数据分别进行快速排序。
*
* @param a an array of Comparable items
* @param item type
*/
public static > void quickSort(T[] a) {
quickSort(a, 0, a.length - 1);
}
/**
* 快排主例程
*
* @param a an array of Comparable items
* @param left start point
* @param right end point
* @param item type
*/
public static > void quickSort(T[] a, int left, int right) {
T pivot = median3(a, left, right);
if (right - left + 1 > CUTOFF) {
int i = left;
int j = right - 1;
while (i < j) {
while (a[++i].compareTo(pivot) <= 0) { }
while (a[--j].compareTo(pivot) >= 0) { }
if (i < j) {
swapReference(a, i, j);
}
}
swapReference(a, i, right - 1);
quickSort(a, left, i - 1);
quickSort(a, i + 1, right);
}
}
/**
* 三数中值分割法
*
* @param a an array of Comparable items
* @param left start point
* @param right end point
* @param item type
* @return median
*/
private static > T median3(T[] a, int left, int right) {
if (right - left + 1 >= CUTOFF) {
int center = (left + right) / 2;
if (a[center].compareTo(a[left]) < 0) {
swapReference(a, center, left);
}
if (a[right].compareTo(a[left]) < 0) {
swapReference(a, right, left);
}
if (a[right].compareTo(a[center]) < 0) {
swapReference(a, right, center);
}
swapReference(a, center, right - 1);
return a[right - 1];
} else {
if (a[left].compareTo(a[right]) > 0) {
swapReference(a, left, right);
}
return a[left];
}
}
/**
* 交换数组两个位置的元素
*
* @param a an array of Comparable items
* @param pos1 position1
* @param pos2 position2
* @param item type
*/
private static > void swapReference(T[] a, int pos1, int pos2) {
T temp = a[pos1];
a[pos1] = a[pos2];
a[pos2] = temp;
}
}
归并排序:分治的递归算法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。
public class MergeSort {
/**
* 归并排序(O(N logN)) 稳定
*
* 归并排序:分治的递归算法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
* 若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。
*
* @param a an array of Comparable items
* @param item type
*/
public static > void mergeSort(T[] a) {
T[] tmpArr = (T[]) new Comparable[a.length];
mergeSort(a, tmpArr, 0, a.length - 1);
}
/**
* 归并排序主例程
*
* @param a an array of Comparable items
* @param left start point
* @param right end point
* @param item type
*/
public static > void mergeSort(T[] a, T[] tmpArr, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
mergeSort(a, tmpArr, left, center);
mergeSort(a, tmpArr, center + 1, right);
merge(a, tmpArr, left, center + 1, right);
}
}
/**
* 合并已排序的两部分
*
* @param a an array of Comparable items
* @param tmpArr temp array
* @param leftPos start point
* @param rightPos center point
* @param rightEnd end point
* @param item type
*/
public static > void merge(T[] a, T[] tmpArr, int leftPos, int rightPos, int rightEnd) {
int leftEnd = rightPos - 1;
int tmpPos = leftPos;
int numElements = rightEnd - leftPos + 1;
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if (a[leftPos].compareTo(a[rightPos]) <= 0) {
tmpArr[tmpPos++] = a[leftPos++];
} else {
tmpArr[tmpPos++] = a[rightPos++];
}
}
while (leftPos <= leftEnd) {
tmpArr[tmpPos] = a[leftPos++];
}
while (rightPos <= rightEnd) {
tmpArr[tmpPos++] = a[rightPos++];
}
for (int i = 0; i < numElements; i++, rightEnd--) {
a[rightEnd] = tmpArr[rightEnd];
}
}
}
堆排序:先建立堆,这个阶段花费O(N)时间。然后我们执行N次deleteMin操作,这个阶段花费O(N logN)时间。O(N + N logN) => O(N logN)执行deleteMin操作后,按照顺序最小的元素先离开堆,通过将这些元素记录到第二个数组然后再将数组拷贝回来,得到N个元素的顺序。
public class HeadSort {
/**
* 堆排序(O(N logN)) 不稳定
*
* 堆排序:堆排序先建立堆,这个阶段花费O(N)时间。然后我们执行N次deleteMin操作,这个阶段花费O(N logN)时间。O(N + N logN) => O(N logN)
* 执行deleteMin操作后,按照顺序最小的元素先离开堆,通过将这些元素记录到第二个数组然后再将数组拷贝回来,得到N个元素的顺序。
*
* @param a an array of Comparable items
* @param item type
*/
public static > void heapSort(T[] a) {
for (int i = a.length / 2 - 1; i >= 0; i--) {
perDown(a, i, a.length);
}
for (int i = a.length - 1; i > 0; i--) {
swapReference(a, 0, i);
perDown(a, 0, i);
}
}
/**
* 获取左儿子节点位置
*
* @param i 当前位置
* @return 左儿子节点位置
*/
private static int leftChild(int i) {
return 2 * i + 1;
}
/**
* 堆下滤
*
* @param a an array of Comparable items
* @param i the position from which to percolate down
* @param n the logical size of the binary heap
* @param item type
*/
private static > void perDown(T[] a, int i, int n) {
int child;
T tmp;
for (tmp = a[i]; leftChild(i) < n; i = child) {
child = leftChild(i);
if (child != n - 1 && a[child].compareTo(a[child + 1]) < 0) {
child++;
}
if (tmp.compareTo(a[child]) < 0) {
a[i] = a[child];
} else {
break;
}
}
a[i] = tmp;
}
/**
* 交换数组两个位置的元素
*
* @param a an array of Comparable items
* @param pos1 position1
* @param pos2 position2
* @param item type
*/
private static > void swapReference(T[] a, int pos1, int pos2) {
T temp = a[pos1];
a[pos1] = a[pos2];
a[pos2] = temp;
}
}
计数排序:数组由小于 M 的正整数组成,使用一个大小为 M 的 count 数组,初始化为全0。循环数组,每读入一个 item,count[item] + 1,循环完毕后扫描 count 数组。
/**
* 计数排序(O(N+M)) 稳定
*
* 计数排序:数组由小于 M 的正整数组成,使用一个大小为 M 的 count 数组,初始化为全0。
* 循环数组,每读入一个 item,count[item] + 1,循环完毕后扫描 count 数组。
*
* @param arr an array
* @param top 桶子数目 M,元素上界
*/
public static void countingSort(int[] arr, int top) {
int[] count = new int[top + 1];
for (int e : arr) {
count[e]++;
}
int index = 0;
for (int i = 0; i < top + 1; i++) {
if (count[i] > 0) {
for (int j = count[i]; j > 0; j--) {
arr[index++] = i;
}
}
}
}
桶排序:将数组元素分到数量为 M 的桶子中,每个桶子再进行排序(此段代码用的插入排序)。
/**
* 桶排序(O(N+M)) 稳定
*
* 桶排序:将数组元素分到数量为 M 的桶子中,每个桶子再进行排序(此段代码用的插入排序)。
*
* @param arr an array
* @param bucketSize 桶子数目 M,元素上界
*/
public static void bucketSort(int[] arr, int bucketSize) {
ArrayList> buckets = new ArrayList<>(bucketSize);
for (int i = 0; i < bucketSize; i++) {
buckets.add(new LinkedList<>());
}
for (int e : arr) {
insertionSort(buckets.get(e / bucketSize), e);
}
int index = 0;
for (LinkedList bucket : buckets) {
for (Integer e : bucket) {
arr[index++] = e;
}
}
}
/**
* 插入排序
*
* @param bucket 待排序的桶子
* @param value 待放入桶子的值
*/
public static void insertionSort(List bucket, int value) {
ListIterator iterator = bucket.listIterator();
boolean insertFlag = true;
while (iterator.hasNext()) {
Integer e = iterator.next();
if (e >= value) {
iterator.add(value);
insertFlag = false;
}
}
if (insertFlag) {
bucket.add(value);
}
}
基数排序:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序(例如String)。
private static final int SIZE = 10;
/**
* 基数排序(O(p(N+M))) 稳定
*
* 基数排序:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
* 有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序(例如String)。
*
* @param arr an array
* @param digit 位数
*/
public static void radixSort(int[] arr, int digit) {
int mod = 10;
int pos = 1;
ArrayList> buckets = new ArrayList<>(SIZE);
for (int i = 0; i < SIZE; i++) {
buckets.add(new LinkedList<>());
}
for (int i = 0; i < digit; i++, mod *= 10, pos *= 10) {
for (int e : arr) {
int index = e % mod / pos;
insertionSort(buckets.get(index), e);
}
int index = 0;
for (LinkedList bucket : buckets) {
for (Integer e : bucket) {
arr[index++] = e;
}
}
for (LinkedList bucket : buckets) {
bucket.clear();
}
}
}
/**
* 插入排序
*
* @param bucket 待排序的桶子
* @param value 待放入桶子的值
*/
public static void insertionSort(List bucket, int value) {
ListIterator iterator = bucket.listIterator();
boolean insertFlag = true;
while (iterator.hasNext()) {
Integer e = iterator.next();
if (e >= value) {
iterator.add(value);
insertFlag = false;
}
}
if (insertFlag) {
bucket.add(value);
}
}
计数基数排序:基数排序另一种实现,它避免使用ArrayList,取而代之一个计数器(类比计数排序)。
private static final int BUCKETS = 256;
/**
* 计数基数排序(O(p(N+M))) 稳定
*
* 计数基数排序:基数排序另一种实现,它避免使用ArrayList,取而代之一个计数器(类比计数排序)。
*
* @param arr an array
* @param strLen 字符串长度(全体元素定长)
*/
public static void radixSort(String[] arr, int strLen) {
String[] buffer = new String[arr.length];
String[] in = arr;
String[] out = buffer;
for (int pos = strLen - 1; pos >= 0; pos--) {
//初始化桶子BUCKETS + 1个
int[] count = new int[BUCKETS + 1];
//计数
for (int i = 0; i < arr.length; i++) {
count[in[i].charAt(pos) + 1]++;
}
//计算起始位置
for (int b = 1; b <= BUCKETS; b++) {
count[b] += count[b - 1];
}
for (int i = 0; i < arr.length; i++) {
out[count[in[i].charAt(pos)]++] = in[i];
}
String[] tmp = in;
in = out;
out = tmp;
}
if (strLen % 2 == 1) {
System.arraycopy(in, 0, out, 0, arr.length);
}
}