算法稳定性
什么是算法稳定性;假设Ki=Kj(1<=i<=n,1<=j<=n,i!=j),且在排序前的序列中Ri领先Rj(i<j)。
如果排序后Ri依然领先Rj,则称所用的排序方法是稳定的;反之不稳定;
如:排序前4,5,2, 1,2,3
排序后1,2,2,3,4,5其中第二个2 是之前第三个2那就是稳定的;
基础的三种排序
冒泡排序
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int e = arr.length - 1; e > 0; e--) {
for (int i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);//调换
}
}
}
}
选择排序
每一轮选择最小的
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = I;
for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);//调换
}
}
插入排序
像插入扑克牌一样,从左到右排序
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 不只1个数
for (int i = 1; i < arr.length; i++) { // 0 ~ i 做到有序
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
以上三种常用时间复杂度都是O()
改进的排序
希尔排序
在插入排序基础上进行优化,将相距某个增量的记录组成一个子序列,这样能保证在子序列内分别进行直接插入排序后得到的结果基本有序,时间复杂度在O()
public static void shellSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) { // 0 ~ i 做到有序
for (int j = i - 1; j >= 0 && arr[j] > arr[j + gap]; j-= gap) {
swap(arr, j, j + gap);
}
}
}
}
堆排序
堆的数据结构
堆通常是一个可以被看做一棵完全二叉树的数组对象。
堆满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值。
- 堆总是一棵完全二叉树
假设当前元素的索引位置为 i,可以得到规律:
parent(i) = i/2-1 left child(i) = 2*i+1 right child(i) = 2*i +2
堆中取出一个元素
private void heapify(int[] arr, int index, int heapSize) {
int left = index * 2 + 1;
while (left < heapSize) { // 如果有左孩子,有没有右孩子,可能有可能没有!
// 把较大孩子的下标,给largest
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//孩子和当前结点比较
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
// index和较大孩子,要互换
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
堆排序的实现
// 堆排序额外空间复杂度O(1)
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// O(N)
for (int i = arr.length - 1; i >= 0; i--) {
heapify(arr, i, arr.length);
}
int heapSize = arr.length;
swap(arr, 0, --heapSize);
// O(N*logN)
while (heapSize > 0) { // O(N)
heapify(arr, 0, heapSize); // O(logN)
swap(arr, 0, --heapSize); // O(1)
}
}
合并排序
归并排序(Merge sort)是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
// 递归方法实现
public static void mergeSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
// 请把arr[L..R]排有序
// l...r N
// T(N) = 2 * T(N / 2) + O(N)
// O(N * logN)
public static void process(int[] arr, int L, int R) {
if (L == R) { // base case
return;
}
int mid = L + ((R - L) >> 1);
process(arr, L, mid);
process(arr, mid + 1, R);
merge(arr, L, mid, R);
}
public static void merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
while (p1 <= M && p2 <= R) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 要么p1越界了,要么p2越界了
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
}
快速排序
随机化快速排序基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
// 快排递归版本
public static void quickSort1(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
process(arr, 0, arr.length - 1);
}
public static void process(int[] arr, int L, int R) {
if (L >= R) {
return;
}
swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
int[] equalArea = netherlandsFlag(arr, L, R);
process(arr, L, equalArea[0] - 1);
process(arr, equalArea[1] + 1, R);
}
汇总对比
排序方法 | 平均情况 | 稳定性 |
冒泡排序 | O() | 稳定 |
简单选择排序 | O() | 稳定 |
直接插入排序 | O() | 稳定 |
希尔排序 | O() | 不稳定 |
堆排序 | O(nlogn) | 不稳定 |
归并排序 | O(nlogn) | 稳定 |
快速排序 | O(nlogn) | 不稳定 |