排序算法模板
初级排序
初级排序 - O(n2)
1. 选择排序:每次找最小值,然后放到待排序数组的起始位置
public int[] selectionSort(int[] arr) {
int len = arr.length;
int minIndex, temp;
for (int i = 0; i < len - 1; i++) {
minIndex = i;
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
2. 插入排序:从前到后逐步构建有序序列;对于未排序数据,在已排序序列中从后往前扫描,找到相应位置并插入
public int[] insertionSort(int[] arr) {
int len = arr.length;
int preIndex, current;
for (int i = 1; i < len; i++) {
preIndex = i - 1; // 当前有序序列的最后一个元素
current = arr[i];
// 当前面的元素比当前元素大,则把前面的元素往后移
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
3. 冒泡排序:嵌套循环,每次查看相邻的元素如果逆序,则交换
public int[] bubbleSort(int[] arr) {
int len = arr.length;
if (len < 2) return;
for(int i = 1; i < len; i++) {
for (int j = 0; j < len - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
思考一下:如果冒泡排序还可以优化吗?
答案是肯定的。如果在最理想的状态下,数列本身就是有序的,同样要O(n2)的处理时间,那太浪费性能了。
优化思路:冒泡排序在第一次遍历之后,是一定可以确定末尾就是数列中的最大值(针对升序排序的场景),如果在内层循环的遍历中,没有发生换位操作,岂不是可以说明数列本身就是有序的,可以终止外层操作了呢?这样一来就可以把冒泡排序在最佳的情况下是O(n)的时间复杂度。
public int[] bubbleSort(int[] arr) {
int len = arr.length;
if (len < 2) return;
// 增加换位标识,默认是未发生换位
boolean flag = false;
for(int i = 1; i < len; i++) {
for (int j = 0; j < len - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// 已发生换位处理
flag = true;
}
}
// 如果未发生换位,则说明序列本身有序
if (!flag) {
break;
}
}
return arr;
}
高级排序 - O(N*LogN)
1. 快速排序
数组取标杆 privot,将小元素放 privot 左边, 大元素放右侧,然后依次对右边和右边的子数组继续快排,以达到整个序列有序。
public static void quickSort(int[] array, int begin, int end) {
if (end <= begin) return;
int pivot = partition(array, begin, end);
quickSort(array, begin, pivot - 1);
quickSort(array, pivot + 1, end);
}
public int partition(int[] a, int begin, int end) {
int pivot = end, counter = begin;
for (int i = begin; i < end; i++) {
if (a[i] < a[pivot]) {
int temp = a[counter]; a[counter] = a[i]; a[i] = temp;
counter++;
}
}
int temp = a[pivot]; a[pivot] = a[counter]; a[counter] = temp;
return counter;
}
2. 归并排序 - 分治
1. 把长度为 n 的输入序列分成两个长度为 n/2的子序列;
2. 对这两个子序列分别采用归并排序;
3. 将两个排序好的子序列合并成一个最终的排序序列
public static void mergeSort(int[] array, int left, int right) {
if (right <= left) return;
int mid = (left + right) >> 1;
mergeSort(array, left, mid);
mergeSort(array, mid + 1, right);
merge(array, left, mid, right);
}
public static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1]; // 中间数组
// i 是左边数组的起始位置,j 是右边数组的起始位置,k 是temp数组已经填入的元素下标
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
// 将 temp copy 到了 arr 里面去
for (int p = 0; p < temp.lenght; p++) {
arr[left + p] = temp[p];
}
}
区分:
1. 归并 和 快排 具有相似性,但步骤顺序相反;
2. 归并:先排序左右子数组,然后合并两个有序子数组;
3. 快排:先调配出左右子数组,然后对于左右子数组进行排序
3. 堆排序 - 堆插入 O(logN),取最大/小值O(1)
1. 数组元素依次建立小顶堆
2. 依次取堆顶元素,并删除
public static void heapify(int[] array, int length, int i) {
int left = 2 * i + 1, right = 2 * i + 2;
int largest = i;
if (left < length && arrray[left] > array[largest]) {
largest = left;
}
if (left < length && arrray[right] > array[largest]) {
largest = right;
}
if (largest != i) {
int temp = array[i]; array[i] = array[largest]; array[largest] = temp;
heapify(array, length, largest);
}
}
public static void heapSort(int[] array) {
if (array.length == 0) return;
int length = array.length;
for (int i = length / 2 - 1; i >= 0; i--)
heapify(array, length, i);
for (int i = length - 1; i >= 0; i--) {
int temp = array[0]; array[0] = array[i]; array[i] = temp;
heapify(array, i, 0);
}
}
实战题目
有效字母异位词:https://leetcode-cn.com/problems/valid-anagram/
合并区间:https://leetcode-cn.com/problems/merge-intervals/