一、插入排序
1. 核心思想
共进行 N - 1 趟排序,对于 P = 1 到 P = N - 1 趟,保证从位置0到位置P上的元素为排序状态。在第P趟,将位置P上的元素移动到前P + 1个元素的正确位置上,比其更大的元素都向右移动一个位置。
2. 分析
如果输入数据已预先排序,则时间复杂度为O(N);平均时间复杂度和最坏时间复杂度均为O(N^2),空间复杂度为O(1)。插入排序只用在小的或是非常接近排好序的输入数据上。
3. 代码实现
public void InsertSort(int[] arr) {
int tmp, p, i;
for(p = 1; p < arr.length; p++) {
tmp = arr[p];
for(i = p; i > 0 && arr[i - 1] > tmp; i--) {
arr[i] = arr[i - 1];
}
arr[i] = tmp;
}
}
二、选择算法
1. 核心思想
每趟均在数组中找到第k小的元素文章,将其放置k - 1的位置,共进行N - 1趟排序。
2. 分析
无论什么样本数据,时间复杂度均为O(N^2),空间复杂度为O(1),不适合大量数据的排序。
3. 代码实现
public void selectionSort(int[] arr) {
for(int i = 0; i < arr.length - 1; i++) {
int min = i;
for(int j = i + 1; j < arr.length; j++) {
if(arr[min] > arr[j]) {
min = j;
}
}
swap(arr, min, i);
}
}
三、冒泡算法
1. 核心思想
每趟排序都对数组中未排序数据进行遍历,相邻元素两两比较,如果顺序与预先规定的顺序不一致,则交换,每次遍历的结果都会使最大元素上浮到顶端。之后再重复操作直至数组有序,共进行N - 1趟排序。
2. 分析
当输入数据为正序时,时间复杂度最小,为O(N),当输入数据为反序时,时间复杂度最大,为O(N^2),平均时间复杂度为O(N^2)。当数据量很大时,冒泡排序效率不高。
3. 代码实现
public void bubbleSort(int[] arr) {
for(int i = 0; i < arr.length - 1; i++) {
for(int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
四、希尔排序(缩小增量排序)
1. 核心思想
希尔排序使用一个增量序列h1,h2,...,ht,在使用增量hk的一趟排序后,对于每个i都有arr[i] <= A[i + hk],所有相隔hk的元素都被排序。
2. 分析
希尔排序的时间复杂度与增量序列的选取有关。使用希尔增量时希尔排序的时间复杂度的最坏时间复杂度为O(N^2),最好时间复杂度为O(NlogN)。
3. 代码实现
public void shellSort(int[] arr) {
int n = arr.length;
for(int increment = n / 2; increment > 0; increment /= 2) {
for(int i = increment; i < n; i++) {
int tmp = arr[i], j;
for(j = i; j >= increment && arr[j] < arr[j - increment]; j -= increment) {
arr[j] = arr[j - increment];
}
arr[j] = tmp;
}
}
五、堆排序
1. 核心思想
将无序数组构造成一个大顶堆(排序后可获得升序数组),完成后将堆顶元素与末尾元素交换,此时末尾为最大值。然后继续调整堆,再次将堆顶与末尾元素互换,此时可得到第二大元素,如此反复进行交换、重建。
2. 分析
堆排序的最好、最坏、平均时间复杂度均为O(NlogN)。
3. 代码实现
public void heapSort(int[] arr) {
int n = arr.length;
for(int i = n / 2; i >= 0; i--) {
percDown(arr, i, n); // 初始化堆
}
for(int i = n - 1; i > 0; i--) {
swap(arr, 0, i);
percDown(arr, 0, i);
}
}
public void percDown(int[] arr, int i, int n) {
int tmp = arr[i], child;
for(; 2 * i + 1 < n; i = child) {
child = 2 * i + 1;
if(child != n - 1 && arr[child] < arr[child + 1]) child++;
if(tmp < arr[child])
arr[i] = arr[child];
else
break;
}
arr[i] = tmp;
}
六、归并排序
1. 核心思想
该算法是经典的分治策略,它将无序数组分成两个序列,分别对它们进行排序,最后合并。而拆分的子序列继续拆分成两个序列再分别进行排序,直至序列只有一个元素。
2. 分析:
归并算法的最好、最坏、平均时间复杂度均为O(NlogN),空间复杂度为O(N),因此很难用于主存排序,因为合并两个排序的表时需要线性附加内存。
3. 代码实现
public void mergeSort(int[] arr) {
int n = arr.length;
int[] tmp = new int[n];
mergeSort(0, n - 1, arr, tmp);
}
public void mergeSort(int left, int right, int[] arr, int[] tmp) {
if(left < right) {
int mid = (left + right) / 2;
mergeSort(left, mid, arr, tmp);
mergeSort(mid + 1, right, arr, tmp);
merge(left, mid, right, arr, tmp);
}
}
public void merge(int left, int mid, int right, int[] arr, int[] tmp) {
int k = 0, i = left, j = mid + 1;
while(i <= mid && j <= right) {
if(arr[i] <= arr[j])
tmp[k++] = arr[i++];
else
tmp[k++] = arr[j++];
}
while(i <= mid) {
tmp[k++] = arr[i++];
}
while(j <= right) {
tmp[k++] = arr[j++];
}
for(int t = 0; t < k; t++) {
arr[left + t] = tmp[t];
}
}
七、快速排序
1. 核心思想
也是一种分治算法,在无序数组中选取一个元素作为枢纽元,使得枢纽元左边的元素均小于它,而右边的元素均大于它,随后继续对两边的子序列进行快速排序。
2. 分析
平均时间复杂度为O(NlogN),最坏时间复杂度为O(N^2),但这种情况并不多见,空间复杂度为O(logN)。
3. 代码实现
public void quickSort(int[] arr) {
int n = arr.length;
quickSort(arr, 0, n - 1);
}
public void quickSort(int[] arr, int left, int right) {
if(left >= right) {
return;
} else {
int pivot = arr[left];
int partition = partition(arr, left, right, pivot);
quickSort(arr, left, partition);
quickSort(arr, partition + 1, right);
}
}
public int partition(int[] arr, int left, int right, int pivot) {
while(left < right) {
while (left < right && arr[right] > pivot) right--;
swap(arr, left, right);
while (left < right && arr[left] < pivot) left++;
swap(arr, left, right);
}
return left;
}
八、总结
排序算法 | 平均时间复杂度 | 最好时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
插入算法 | O(N^2) | O(N) | O(N^2) | O(1) | 稳定 |
选择算法 | O(N^2) | O(N^2) | O(N^2) | O(1) | 不稳定 |
冒泡算法 | O(N^2) | O(N) | O(N^2) | O(1) | 稳定 |
希尔排序 | O(NlogN) | O(Nlog^2N) | O(Nlog^2N) | O(1) | 不稳定 |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N) | 稳定 |
快速排序 | O(NlogN) | O(NlogN) | O(N^2) | O(logN) | 不稳定 |
1. 稳定性即排序前后两个相等的数其位置不变。其中插入、冒泡、归并排序均为稳定排序,选择、希尔、堆、快速排序均为不稳定排序。
2. 除归并、快速排序外,其他排序空间复杂度均为O(1),其中归并排序为O(N),快速排序为O(logN)。
3. 选择、堆、归并排序的时间复杂度在三种情况下均相同,其中选择排序的均为O(N^2),堆、归并排序的均为O(NlogN)。