注:
swap(nums, i, j)为提前定义好的用于交换i, j位置元素的函数- 稳定性:如果在数组中有两个元素是相等的,在经过某个排序算法之后,原来在前面的的那个元素仍然在另一个元素的前面,那么我们就说这个排序算法是稳定的。
1. 冒泡排序
从左到右依次比较相邻元素,通过交换使较大数在后方,每轮可使最大数“冒泡”到最后端。
时间复杂度:O(n^2),空间复杂度:O(1),稳定
最好时间复杂度:O(n),最坏时间复杂度O(n^2)
Java:
public void bubbleSort(int[] nums) {
for (int i = nums.length - 1; i >= 1 ; i--) {
for (int j = 1; j <= i; j++) {
if (nums[j] < nums[j - 1])
swap(nums, j, j - 1);
}
}
}
JavaScript:
function bubbleSort(arr) {
const len = arr.length;
for(let i = 0; i < len - 1; i++) { // 每轮都使得未排序数组的最后一个有序
for(let j = 0; j < len - 1 - i; j++) {
if(arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
return arr;
}
2. 选择排序
类似冒泡,每轮都找到未排序部分最大(最小)的值通过swap放到最后(最前)
时间复杂度:O(n^2),空间复杂度:O(1),不稳定(可通过讲swap改为插入来使算法稳定)
时间复杂度最好最坏都是O(n^2)
Java:找最大
private void selectionSort(int[] nums) {
for (int i = nums.length - 1; i > 0; i--) {
int maxIndex = 0;
for (int j = 1; j <= i; j++) {
if (nums[maxIndex] < nums[j])
maxIndex = j;
}
swap(nums, maxIndex, i);
}
}
JavaScript:找最小
function selectSort(arr) {
const len = arr.length;
for(let i = 0; i < len; i++) { // 每轮找最小的一个数放在首位
let minIndex = i;
for(let j = i + 1; j < len; j++) {
if(arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr, i, minIndex);
}
return arr;
}
3. 插入排序
数组的前i - 1个元素已经排好序,插入第i个元素,i从1变化到nums.length - 1
时间复杂度:O(n^2),空间复杂度:O(1),稳定
最好时间复杂度:O(n),最坏时间复杂度:O(n^2)
Java:
private void insertionSort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int j = i;
while (j > 0 && nums[j] < nums[j - 1]) {
swap(nums, j, j - 1);
j--;
}
}
}
JavaScript:
function insertionSort(arr) {
const len = arr.length;
for(let i = 1; i < len; i++) { // 每轮将第i个数插入到前半部分的有序数组中
const curr = arr[i];
let preIndex = i - 1;
while(preIndex >= 0 && arr[preIndex] > curr) {
arr[preIndex + 1] = arr[preIndex]; // 将比要插入数大的往后移动,空出插入位置
preIndex--;
}
arr[preIndex + 1] = curr;
}
return arr;
}
4. 希尔排序
每轮都跳着有序,变形的插入(冒泡),通过固定间隔gap将数组分为gap个小组,在小组内部进行插入(冒泡)排序,gap每次减小一半直到为1
时间复杂度:O(n^4/3) ~ O(n^2),空间复杂度:O(1),不稳定
最好时间复杂度:O(n^1.3),最坏时间复杂度:O(n^2)
Java:
private void shellSort(int[] nums) {
int gap = nums.length >> 1;
while (gap > 0) {
for (int i = 0; i < gap; i++) {
for (int j = i + gap; j < nums.length; j += gap) {
int temp = j;
while (temp > i && nums[temp] < nums[temp - gap]) {
swap(nums, temp, temp - gap);
temp -= gap;
}
}
}
gap >>= 1;
}
}
JavaScript:
function shellSort(arr) {
const len = arr.length;
for(let gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) { // 每轮结束后按gap跳着有序
for(let i = gap; i < len; i++) { // gap个插入同时进行
let j = i;
const curr = arr[i];
while(j - gap >= 0 && arr[j - gap] > curr) { // 相当于插入,大的往后移动,空出插入位置
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = curr;
}
}
return arr;
}
5. 归并排序
先拆再合,分治思想,递归进行,将数组分为左右两部分分别排序,不断二分直至只剩1个或0个元素
时间复杂度:O(nlogn),空间复杂度:O(n),稳定
最好时间复杂度:O(nlogn),最坏时间复杂度:O(nlogn),不管是什么数组都要拆完再合起来
Java:
private void mergeSort(int[] nums, int left, int right) { // 需要左右边界确定排序范围
if (left >= right) return;
int mid = (left + right) / 2;
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
int[] temp = new int[right - left + 1];
int curr = 0, i = left, j = mid + 1;
while (i <= mid && j <= right) {
if (nums[i] < nums[j])
temp[curr++] = nums[i++];
else
temp[curr++] = nums[j++];
}
while (i <= mid) temp[curr++] = nums[i++];
while (j <= right) temp[curr++] = nums[j++];
for (int k = 0; k < temp.length; k++) {
nums[k + left] = temp[k];
}
}
JavaScript:
function mergeSort(arr) { // 拆到只剩一个数(有序)
const len = arr.length;
if(len < 2) {
return arr;
}
const mid = Math.floor(len / 2);
return merge(mergeSort(arr.slice(0, mid)), mergeSort(arr.slice(mid, len)));
}
function merge(left, right) { // 合并两个有序数组
const res = [];
while(left.length && right.length) {
if(left[0] > right[0]) {
res.push(right.shift());
} else {
res.push(left.shift());
}
}
while(left.length) {
res.push(left.shift());
}
while(right.length) {
res.push(right.shift());
}
return res;
}
6. 快速排序
取第一个元素(或最后一个元素)作为分界点,把整个数组分成左右两侧,左边的元素小于等于分界点,右边的元素大于分界点,然后把分界点移到中间位置,对左右子数组分别进行递归,最后就能得到一个排序完成的数组。当子数组只有一个或者没有元素的时候就结束这个递归过程。
时间复杂度:平均O(nlogn),空间复杂度:平均O(logn) 最坏O(n),不稳定
最好时间复杂度:O(nlogn),最坏时间复杂度:O(n^2);最好的时候每个基准都能正好均分数组,递归树深度O(logn);最坏的时候每个基准都只能将数组分为一个元素和其他元素,递归树深度O(n)。
Java:
private void quickSort(int[] nums, int left, int right) {
if (left >= right) return;
int lo = left + 1;
int hi = right;
while (lo <= hi) {
if (nums[left] < nums[lo]) {
swap(nums, lo, hi);
hi--;
} else {
lo++;
}
}
lo--;
swap(nums, left, lo);
quickSort(nums, left, lo - 1);
quickSort(nums, lo + 1, right);
}
JavaScript:
function quickSort(arr, left, right) {
// 处理后可以直接使用,第一次不需要传 left right
// left = typeof left === 'number' ? left : 0;
// right = typeod right === 'number' ? right : arr.length - 1;
if(left >= right) return; // 递归到一个数,一个数一定有序
let lo = left + 1;
let hi = right;
while(lo <= hi) { // 找到基准arr[left]在arr中的位置
if(arr[lo] > arr[left]) {
swap(arr, lo, hi);
hi--;
} else {
lo++;
}
}
lo--; // 将lo指向小于基准数列的最后一个
swap(arr, lo, left);
quickSort(arr, left, lo - 1); // 以基准的位置切分两边做递归
quickSort(arr, lo + 1, right);
}
// 使用
quickSort(arr, 0, arr.length - 1);