常用的排序方法

183 阅读10分钟

常用的排序方法

冒泡排序,选择排序,插入排序,希尔排序,归并排序,快速排序,堆排序,计数排序,桶排序,基数排序。

推荐

  • 大数据量且对性能要求较高:如果需要处理大量数据并且对性能有较高要求,可以考虑使用 快速排序归并排序。快速排序通常在平均情况下性能较好,但在最坏情况下可能会退化到 O(n^2),而归并排序的性能比较稳定。
  • 数组已经部分有序:如果数组已经部分有序,插入排序可能会比其他排序方法更快,因为它在最好情况下的时间复杂度是 O(n)。

详解

  1. 冒泡排序(Bubble Sort)
  • 原理:比较相邻的元素,如果第一个比第二个大,就交换它们两个;对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;针对所有的元素重复以上的步骤,除了最后一个;重复步骤1~3,直到排序完成。
  • 时间复杂度:平均时间复杂度为 O(n2)O(n^2),最坏情况下为 O(n2)O(n^2),最好情况下为 O(n)O(n)
  • 空间复杂度O(1)O(1)
  • 稳定性:稳定。
function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len; i++) {
        for (var j = 0; j < len - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换元素
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return arr;
}

// 示例用法
var unsortedArray = [64, 34, 25, 12, 22, 11, 90];
var sortedArray = bubbleSort(unsortedArray);
console.log(sortedArray); // 输出: [11, 12, 22, 25, 34, 64, 90]
  1. 选择排序(Selection Sort)
  • 原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
  • 时间复杂度:平均时间复杂度为 O(n2)O(n^2),最坏情况下为 O(n2)O(n^2),最好情况下为 O(n2)O(n^2)
  • 空间复杂度O(1)O(1)
  • 稳定性:不稳定。
function selectionSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len - 1; i++) {
        var minIndex = i;
        for (var j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex !== i) {
            // 交换元素
            var temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
    return arr;
}
// 示例用法
var unsortedArray = [64, 34, 25, 12, 22, 11, 90];
var sortedArray = selectionSort(unsortedArray);
console.log(sortedArray); // 输出: [11, 12, 22, 25, 34, 64, 90]
  1. 插入排序(Insertion Sort)
  • 原理:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
  • 时间复杂度:平均时间复杂度为 O(n2)O(n^2),最坏情况下为 O(n2)O(n^2),最好情况下为 O(n)O(n)
  • 空间复杂度O(1)O(1)
  • 稳定性:稳定。
function insertionSort(arr) {
    var len = arr.length;
    for (var i = 1; i < len; i++) {
        var key = arr[i];
        var j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j = j - 1;
        }
        arr[j + 1] = key;
    }
    return arr;
}
// 示例用法
var unsortedArray = [64, 34, 25, 12, 22, 11, 90];
var sortedArray = insertionSort(unsortedArray);
console.log(sortedArray); // 输出: [11, 12, 22, 25, 34, 64, 90]
  1. 希尔排序(Shell Sort)
  • 原理:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
  • 时间复杂度:平均时间复杂度为 O(nlogn)O(nlogn),最坏情况下为 O(n2)O(n^2),最好情况下为 O(n)O(n)
  • 空间复杂度O(1)O(1)
  • 稳定性:不稳定。
function shellSort(arr) {
    var len = arr.length;
    for (var gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
        for (var i = gap; i < len; i++) {
            var temp = arr[i];
            var j;
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
    return arr;
}
// 示例用法
var unsortedArray = [64, 34, 25, 12, 22, 11, 90];
var sortedArray = shellSort(unsortedArray);
console.log(sortedArray); // 输出: [11, 12, 22, 25, 34, 64, 90]

  1. 归并排序(Merge Sort)
  • 原理:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
  • 时间复杂度:平均时间复杂度为 O(nlogn)O(nlogn),最坏情况下为 O(nlogn)O(nlogn),最好情况下为 O(nlogn)O(nlogn)
  • 空间复杂度O(n)O(n)
  • 稳定性:稳定。
// 交换数组中的两个元素
function swap(arr, i, j) {
    let temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
// 维护最大堆性质
function maxHeapify(arr, n, i) {
    let largest = i; // 初始化最大值为根节点
    let left = 2 * i + 1; // 左子节点索引
    let right = 2 * i + 2; // 右子节点索引

    // 如果左子节点大于根节点
    if (left < n && arr[left] > arr[largest]) {
        largest = left;
    }
    // 如果右子节点大于最大值
    if (right < n && arr[right] > arr[largest]) {
        largest = right;
    }
    // 如果最大值不是根节点
    if (largest !== i) {
        swap(arr, i, largest);
        // 递归地维护子树的最大堆性质
        maxHeapify(arr, n, largest);
    }
}
// 构建最大堆
function buildMaxHeap(arr) {
    let n = arr.length;
    // 从最后一个非叶子节点开始,向上构建最大堆
    for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
        maxHeapify(arr, n, i);
    }
}
// 堆排序主函数
function heapSort(arr) {
    let n = arr.length;
    // 构建最大堆
    buildMaxHeap(arr);
    // 一个个从堆顶取出元素,放到数组末尾
    for (let i = n - 1; i > 0; i--) {
        // 将当前堆顶元素(最大值)与末尾元素交换
        swap(arr, 0, i);
        // 重新调整堆,使其满足最大堆性质
        maxHeapify(arr, i, 0);
    }
    return arr;
}
// 示例用法
let arr = [12, 11, 13, 5, 6, 7];
console.log(heapSort(arr)); // 输出: [5, 6, 7, 11, 12, 13]
  1. 快速排序(Quick Sort)
  • 原理:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
  • 时间复杂度:平均时间复杂度为 O(nlogn)O(nlogn),最坏情况下为 O(n2)O(n^2),最好情况下为 O(nlogn)O(nlogn)
  • 空间复杂度O(logn)O(logn)
  • 稳定性:不稳定。
function quickSort(arr) {
    if (arr.length <= 1) {
        return arr;
    }
    const pivot = arr[0];
    const left = [];
    const right = [];
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    return [...quickSort(left), pivot, ...quickSort(right)];
}
// 示例用法
const unsortedArray = [3, 6, 8, 10, 1, 2, 1];
const sortedArray = quickSort(unsortedArray);
console.log(sortedArray); // 输出: [1, 1, 2, 3, 6, 8, 10]
  1. 堆排序(Heap Sort)
  • 原理:利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
  • 时间复杂度:平均时间复杂度为 O(nlogn)O(nlogn),最坏情况下为 O(nlogn)O(nlogn),最好情况下为 O(nlogn)O(nlogn)
  • 空间复杂度O(1)O(1)
  • 稳定性:不稳定。
// 交换数组中的两个元素
function swap(arr, i, j) {
    let temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
// 维护最大堆性质
function maxHeapify(arr, n, i) {
    let largest = i; // 初始化最大值为根节点
    let left = 2 * i + 1; // 左子节点索引
    let right = 2 * i + 2; // 右子节点索引

    // 如果左子节点大于根节点
    if (left < n && arr[left] > arr[largest]) {
        largest = left;
    }
    // 如果右子节点大于最大值
    if (right < n && arr[right] > arr[largest]) {
        largest = right;
    }
    // 如果最大值不是根节点
    if (largest !== i) {
        swap(arr, i, largest);
        // 递归地维护子树的最大堆性质
        maxHeapify(arr, n, largest);
    }
}
// 构建最大堆
function buildMaxHeap(arr) {
    let n = arr.length;
    // 从最后一个非叶子节点开始,向上构建最大堆
    for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
        maxHeapify(arr, n, i);
    }
}
// 堆排序主函数
function heapSort(arr) {
    let n = arr.length;
    // 构建最大堆
    buildMaxHeap(arr);
    // 一个个从堆顶取出元素,放到数组末尾
    for (let i = n - 1; i > 0; i--) {
        // 将当前堆顶元素(最大值)与末尾元素交换
        swap(arr, 0, i);
        // 重新调整堆,使其满足最大堆性质
        maxHeapify(arr, i, 0);
    }
    return arr;
}
// 示例用法
let arr = [12, 11, 13, 5, 6, 7];
console.log(heapSort(arr)); // 输出: [5, 6, 7, 11, 12, 13]
  1. 计数排序(Counting Sort)
  • 原理:计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
  • 时间复杂度:平均时间复杂度为 O(n+k)O(n + k),最坏情况下为 O(n+k)O(n + k),最好情况下为 O(n+k)O(n + k),其中 kk 是整数的范围。
  • 空间复杂度O(k)O(k)
  • 稳定性:稳定。
function countingSort(arr) {
    // 找出数组中的最大值
    let max = Math.max(...arr);
    // 创建一个计数数组,长度为最大值加1,并初始化为0
    let countArray = new Array(max + 1).fill(0);
    // 统计每个元素出现的次数
    for (let i = 0; i < arr.length; i++) {
        countArray[arr[i]]++;
    }
    // 重建排序后的数组
    let sortedArray = [];
    for (let i = 0; i < countArray.length; i++) {
        while (countArray[i] > 0) {
            sortedArray.push(i);
            countArray[i]--;
        }
    }
    return sortedArray;
}

// 示例用法
let unsortedArray = [4, 2, 2, 8, 3, 3, 1];
let sortedArray = countingSort(unsortedArray);
console.log(sortedArray); // 输出: [1, 2, 2, 3, 3, 4, 8]

  1. 桶排序(Bucket Sort)
  • 原理:将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。
  • 时间复杂度:平均时间复杂度为 O(n+k)O(n + k),最坏情况下为 O(n2)O(n^2),最好情况下为 O(n)O(n),其中 kk 是桶的数量。
  • 空间复杂度O(n+k)O(n + k)
  • 稳定性:稳定。
function bucketSort(arr, bucketSize = 5) {
    if (arr.length === 0) {
        return arr;
    }

    // 确定最小值和最大值
    let minValue = arr[0];
    let maxValue = arr[0];
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] < minValue) {
            minValue = arr[i];
        } else if (arr[i] > maxValue) {
            maxValue = arr[i];
        }
    }

    // 计算桶的数量
    const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
    const buckets = new Array(bucketCount);

    // 初始化桶
    for (let i = 0; i < buckets.length; i++) {
        buckets[i] = [];
    }

    // 将元素分配到桶中
    for (let i = 0; i < arr.length; i++) {
        const bucketIndex = Math.floor((arr[i] - minValue) / bucketSize);
        buckets[bucketIndex].push(arr[i]);
    }

    // 对每个桶进行排序(可以使用插入排序或其他排序算法)
    const sortedArray = [];
    for (let i = 0; i < buckets.length; i++) {
        if (buckets[i].length > 0) {
            insertionSort(buckets[i]);
            sortedArray.push(...buckets[i]);
        }
    }

    return sortedArray;
}

// 插入排序(用于对桶内元素排序)
function insertionSort(bucket) {
    for (let i = 1; i < bucket.length; i++) {
        let current = bucket[i];
        let j = i - 1;
        while (j >= 0 && bucket[j] > current) {
            bucket[j + 1] = bucket[j];
            j--;
        }
        bucket[j + 1] = current;
    }
}

// 示例用法
const unsortedArray = [29, 25, 3, 49, 9, 37, 21, 43];
console.log(bucketSort(unsortedArray)); 
  1. 基数排序(Radix Sort)
  • 原理:按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。
  • 时间复杂度:平均时间复杂度为 O(nk)O(nk),最坏情况下为 O(nk)O(nk),最好情况下为 O(nk)O(nk),其中 kk 是数字的位数。
  • 空间复杂度O(n+k)O(n + k)
  • 稳定性:稳定。
 function radixSort(arr) {
    // 找到数组中的最大值,以确定排序的轮数
    let max = Math.max(...arr);
    let digit = 1;

    // 对每一位数字进行排序
    while (Math.floor(max / digit) > 0) {
        // 使用计数排序对当前位进行排序
        countingSort(arr, digit);
        digit *= 10;
    }

    return arr;
}

function countingSort(arr, digit) {
    let output = new Array(arr.length).fill(0);
    let count = new Array(10).fill(0);

    // 统计当前位数字出现的次数
    for (let i = 0; i < arr.length; i++) {
        let index = Math.floor(arr[i] / digit) % 10;
        count[index]++;
    }

    // 计算累计次数
    for (let i = 1; i < 10; i++) {
        count[i] += count[i - 1];
    }

    // 将元素放置到正确的位置
    for (let i = arr.length - 1; i >= 0; i--) {
        let index = Math.floor(arr[i] / digit) % 10;
        output[count[index] - 1] = arr[i];
        count[index]--;
    }

    // 将排序好的数组复制回原数组
    for (let i = 0; i < arr.length; i++) {
        arr[i] = output[i];
    }
}

// 示例用法
let arr = [170, 45, 75, 90, 802, 24, 2, 66];
console.log(radixSort(arr)); // 输出: [2, 24, 45, 66, 75, 90, 170, 802]