【Java & JS】排序算法

565 阅读3分钟

注:

  • 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个元素,i1变化到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);