排序算法
八大基本排序算法
- 冒泡排序:双层循环,相邻元素交换,每次内层把最大的数冒泡到最后面,时间复杂度最坏O(n^2)
for (int i = 0; i < n - 1; i++) {
// 内层循环:两两比较,把大的数“冒”到后面
for (int j = 0; j < n - 1 - i; j++) {
// 如果前一个数 > 后一个数,交换位置
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
-
选择排序:分为未排序区间和排序区间,每一次在未排序区间中找到最小的值,和未排序区间第一个值进行交换,逐步扩大已经交换的区域,O(n^2)
-
插入排序:同样分为未排序区间和排序区间,每一次把未排序区间第一个在已经排序的区间中进行比较找到合适的位置插入,在O(n^2)时间复杂度中最优,优化点在因排序区间中找合适位置的时候可以使用二分查找提高效率
-
希尔排序:插入排序的升级版本,因为插入排序对于无序数组的效率非常低,所以先进行分组排序,最后一次当gap = 1的时候就是一次插入排序,但是此时数组已经相对有序了,插入排序的速率更高,最优的情况下能达到O(nlogn)时间复杂度。因为gap每一次都除以2,所以外层的轮数是log2n轮,内层分组的插入排序约等于O(n),所以最终最好的情况下能达到O(nlogn)的时间复杂度
public static void shellSort(int[] arr) {
int n = arr.length;
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n ; i++) {
int temp = arr[i];
int j = i - gap;
while (j >=0 && arr[j] > temp){
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = temp;
}
}
}
- 归并排序: 分治思想,把数组递归拆成最小的分组,从下向上归并,最终形成一个整体有序的数组,时间复杂度O(nlogn),因为多使用了一个temp临时数组,所以空间复杂度O(n)
public static void mergeSort(int[] arr) {
int[] temp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, temp);
}
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left >= right) {
return ;
}
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);
}
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int l = left; l <= right; l++) {
arr[l] = temp[l];
}
}
- 堆排序
public static void heapSort(int[] arr) {
int n = arr.length;
//先选取中间的这个值进行第一次整体的堆化
for (int i = n/2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
for (int i = n - 1; i > 0; i--) {
//依次取出堆顶的这个最大根和未排序的最后一位交换
swap(arr, 0, i);
//这个i值限制了堆的大小
heapify(arr, i, 0);
}
}
public static void heapify(int[] arr, int heapSize, int i) {
//根据这个二叉树的性质,找到左右子树的下标,并且根要大于左右子树
int largest = i;
int left = 2*i + 1;
int right = 2*i + 2;
if (left < heapSize && arr[left] > arr[largest]) {
largest = left;
}
if (right < heapSize && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, heapSize, largest);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
- 计数排序
// 计数排序 - 稳定排序、非原地排序
public static void countingSort(int[] arr) {
if (arr.length <= 1) return;
// 1. 找数组的最小值和最大值,确定取值范围
int min = arr[0], max = arr[0];
for (int num : arr) {
if (num < min) min = num;
if (num > max) max = num;
}
int size = max - min + 1;
int[] count = new int[size]; // 计数数组
// 2. 统计每个元素出现的次数
for (int num : arr) {
count[num - min]++;
}
// 3. 前缀和处理:count[i]表示<= min+i的元素个数
for (int i = 1; i < size; i++) {
count[i] += count[i - 1];
}
// 4. 逆序遍历原数组,填充结果数组(保证稳定性)
int[] res = new int[arr.length];
for (int i = arr.length - 1; i >= 0; i--) {
int val = arr[i];
int index = count[val - min] - 1; // 确定元素的有序位置
res[index] = val;
count[val - min]--; // 该值的计数减1
}
// 5. 拷贝结果数组到原数组
System.arraycopy(res, 0, arr, 0, arr.length);
}
sort排序底层算法
- JDK7以双轴排序为主,JDK9+以蒂姆排序(TimSort)为主,所有版本都有数组长度小于阈值,使用插入排序
- 双周快排主要是选择了两个基准值,把数组分成三部分,虽然时间复杂度仍然是O(nlogn),但是更加稳定
- TimSort排序本质是归并排序和插入排序的极致混合优化版本,第一步:分割数组,生成所有的Run也就是分割后的数组,有一个长度阈值(32)如果数组长度小于这个阈值,就使用插入排序,不断的扩展,变成长的Run, 第二步:采用归并排序,从底到上,从小到大的合并相邻的Run 第三部兜底策略:依旧是插入排序兜底

浙公网安备 33010602011771号