1. 选择排序 O(N^2)
依次找到最小值的位置,然后与当前位置交换。比较完一圈之后再交换
public void selectSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
int min = i;
for (int j = i + 1; j < arr.length; j ++) {
min = arr[min] > arr[j] ? j : min;
}
swrap(arr, i, min);
}
}
private void swrap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
2. 冒泡排序 O(N^2)
不断比较两个位置,小的放前面。每次比较都可能发生交换
public void popSort(int[] arr) {
for (int i = 0; i < arr.length; i ++) {
for (int j = i + 1; j < arr.length; j ++) {
if (arr[i] > arr[j]) {
swrap(arr, i, j);
}
}
}
}
3. 插入排序 O(N^2)
左侧的数相邻比较,直到左侧的数都有序为止。时间复杂度在数据情况交好时,需要的时间短
public void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i ++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swrap(arr, j, j + 1);
}
}
}
4. 归并排序 O(N * log N)
分治思想。
- 分割:递归地把当前序列平均分割成两半。
- 集成:将上一步得到的子序列集成到一起(归并)。
public void sort2(int[] arr) {
int step = 1, L, mid;
while (step < arr.length) {
L = 0;
mid = L + step - 1;
while (mid < arr.length) {
merge(arr, L, mid, Math.min(mid + step, arr.length - 1));
L = mid + step + 1;
mid = L + step - 1;
}
if (step > arr.length / 2) {
break;
}
step = step << 1;
}
}
5. 随机快速排序 O(N * log N)
public void sort(int[] arr) {
process(arr, 0, arr.length - 1);
}
private void process(int[] arr, int L, int R) {
if (L > R) {
return;
}
if (L == R) {
return;
}
swrap(arr, (int)(Math.random() * (R - L + 1)) + L, R);
int[] re = partition(arr, L, R);
process(arr, L, re[0] - 1);
process(arr, re[1] + 1, R);
}
public int[] partition(int[] arr, int L, int R) {
int less = L, great = R - 1, i = L, X = arr[R];
while (i <= great) {
if (arr[i] < X) {
swrap(arr, less++, i++);
} else if (arr[i] > X) {
swrap(arr, great--, i);
} else {
i++;
}
}
swrap(arr, great + 1, R);
return new int[]{less, great + 1};
}
6. 堆排序 O(N * log N)
堆是一棵顺序存储的完全二叉树。
其中每个结点的value都不大于其孩子结点的value,这样的堆称为小根堆。
其中每个结点的value都不小于其孩子结点的value,这样的堆称为大根堆。
// 插入堆后,向上进行比较,使其满足规则
public void heapInsert(int[] arr, int i) {
while (arr[(i - 1) / 2] < arr[i]) {
swap(arr, (i - 1) / 2, i);
i = (i - 1) / 2;
}
}
// pop 堆后,向下比较,使其满足大根堆的规则
public void heapify(int[] arr, int i, int size) {
int left = 2 * i + 1;
while (left < size) {
int maxIndex = left + 1 < size && arr[left] < arr[left + 1] ? left + 1 : left;
if (arr[maxIndex] > arr[i]) {
swap(arr, i, maxIndex);
i = maxIndex;
left = 2 * i + 1;
} else {
break;
}
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public void sort(int[] arr) {
if (arr.length <= 1) {
return;
}
/* O(N * log N)
int i = 0;
while (i < arr.length) {
heapInsert(arr, i++);
}*/
// O(N)
int i = arr.length - 1;
for (; i >= 0; i--) {
heapify(arr, i, arr.length);
}
i = arr.length - 1;
do {
swap(arr, 0, i);
heapify(arr, 0, i--);
} while (i >= 0);
}
基于非比较的排序。 桶排序
将数据放在容器中,然后排序
7. 计数排序 O(N)
如年龄排序,将所有的年龄存放到一个数组中,然后将数组取出
private void sort(int[] ages) {
if (ages.length <= 1) {
return;
}
int[] help = new int[200];
for (int i = 0; i < ages.length; i++) {
help[ages[i]]++;
}
int i = 0;
for (int j = 0; j < help.length; j++) {
for (int k = 0; k < help[j]; k++) {
ages[i++] = j;
}
}
}
8. 基数排序
按照进制进行排序。 桶排序是按照基数进行排序,先个位再十位,再百位。。。,入桶的顺序和出桶的顺序需要保持一致;
// 基数排序
public void sort2(int[] arr) {
if (arr.length <= 1) {
return;
}
sortRadix(arr, 0, arr.length - 1, maxDigit(arr));
}
private void sortRadix(int[] arr, int L, int R, int d) {
int radix = 10;
int[] help = new int[radix];
int[] helpSum = new int[radix];
int[] copy = new int[R - L + 1];
for (int i = 0; i < d; i++) {
for (int j = L; j <= R; j++) {
int currNum = getCurrNum(arr[j], i);
help[currNum]++;
}
helpSum[0] = help[0];
for (int j = 1; j < helpSum.length; j++) {
helpSum[j] = helpSum[j - 1] + help[j];
}
for (int j = R; j >= L; j--) {
int currNum = getCurrNum(arr[j], i);
copy[helpSum[currNum] - 1] = arr[j];
helpSum[currNum]--;
}
Arrays.fill(help, 0);
System.arraycopy(copy, L, arr, L, R + 1 - L);
}
}
private int getCurrNum(int num, int position) {
int a = num % (int) Math.pow(10, position + 1);
return a / (int) Math.pow(10, position);
}
private int maxDigit(int[] arr) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(arr[i], max);
}
int i = 1;
while (max != 0) {
max = max / (int) Math.pow(10, i++);
}
return i;
}
总结
排序算法的稳定性
- 稳定性是指同样大小的样本再排序之后不会改变相对次序
- 对基础类型来说,稳定性毫无意义
- 对非基础类型来说,稳定性有重要意义
- 有些排序算法可以实现成稳定的,而有些排序算法无论如何都实现不成稳定的
1)不基于比较的排序,对样本数据有严格要求,不易改写
2)基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用3)基于比较的排序,时间复杂度的极限是O(NlogN)
4)时间复杂度O(NlogN)、额外空间复杂度低于O(N)、且稳定的基于比较的排序是不存在的。
5)为了绝对的速度选 快排(常数时间小)、为了省空间选 堆排、为了稳定性选 归并