冒泡排序,选择排序,插入排序,归并排序,快速排序五种排序方法
一 冒泡排序
冒泡排序是一种简单的排序算法,工作原理是通过重复交换相邻的元素,将较大的元素“冒泡”到数组的末端。以下是冒泡排序的基本步骤和示例代码:
1 冒泡排序步骤
- 从数组的开始位置,依次比较相邻的两个元素。
- 如果前一个元素大于后一个元素,则交换它们。
- 一轮比较后,最大的元素会被放到数组的最后位置。
- 重复以上步骤,对剩余的元素继续排序,直到整个数组有序。
2 示例代码
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]) {
// 交换
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
// 使用示例
const array = [5, 3, 8, 4, 2];
const sortedArray = bubbleSort(array);
console.log(sortedArray); // 输出: [2, 3, 4, 5, 8]
3 时间复杂度
最坏和平均情况下的时间复杂度是 O(n²),最好情况下(数组已经有序)是 O(n)。
二 选择排序(Selection Sort)
1 原理:
选择排序(Selection Sort)是一种简单直观的排序算法,适合小规模的数据排序。它的基本思想是将待排序的数组分为已排序和未排序两部分,然后不断从未排序部分中选择最小(或最大)的元素,将其放到已排序部分的末尾。
时间复杂度:O(n²)
2 示例代码:
arr= [64, 25, 12, 22, 11]
- 第 一次循环:
- 未排序部分:[64, 25, 12, 22, 11]
- 找到最小值 11,交换 11 和 64
- 结果:[11, 25, 12, 22, 64]
- 第 二次循环:
-
未排序部分:[25, 12, 22, 64]
-
找到最小值 12,交换 12 和 25
-
结果:[11, 12, 25, 22, 64]
- 第三次循环:
- 未排序部分:[25, 22, 64]
- 找到最小值 22,交换 22 和 25
- 结果:[11, 12, 22, 25, 64]
- 第四次循环:
- 未排序部分:[25, 64]
- 找到最小值 25,无需交换
- 结果:[11, 12, 22, 25, 64]
最后,数组已完全排序。
function selectionSort(arr) {
const len = arr.length;
for (let i = 0; i < len - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 交换
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
return arr;
}
三. 插入排序(Insertion Sort)
1 原理:
将数组分为已排序和未排序两部分,从未排序部分逐个取出元素,插入到已排序部分的合适位置。
- 初始化:将第一个元素视为已排序部分。
- 遍历未排序部分:从第二个元素开始,逐个取出未排序部分的元素。
- 插入位置:将当前元素与已排序部分的元素进行比较,找到合适的位置进行插入。
- 移动元素:将已排序部分的元素向右移动,腾出位置插入当前元素。
- 重复:重复步骤2到4,直到所有元素排序完成。
时间复杂度:O(n²)
2 示例代码:
function insertionSort(arr) {
const len = arr.length;
for (let i = 1; i < len; i++) {
const key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
}
四. 归并排序(Merge Sort)
1 原理:
采用分治法,将数组分成两半,分别排序后再合并。
- 分解:将待排序数组递归地分成两半,直到每个部分只包含一个元素(一个元素本身是有序的)。
- 合并:将两个已排序的部分合并成一个更大的已排序部分。
- 重复:直到所有部分合并完成。
时间复杂度:O(n log n)
2 示例代码:
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
五. 快速排序(Quick Sort)
1 原理:
选择一个“基准”元素,分区使得比基准小的元素在左边,比基准大的元素在右边,然后递归排序。
- 选择基准:从数组中选择一个元素作为基准(pivot)。
- 分区:将数组重新排列,使得所有比基准小的元素在基准的左侧,所有比基准大的元素在基准的右侧。
- 递归排序:对基准左侧和右侧的子数组递归地应用快速排序。
平均情况下 O(n log n),最坏情况下 O(n²)。
假设我们有数组 [10, 80, 30, 90, 40, 50, 70]:
- 选择基准:假设选择最后一个元素 70 作为基准。
- 分区: 将数组重新排列为 [10, 30, 40, 50, 70, 90, 80],此时 70 处于正确位置。
- 递归排序: 对 [10, 30, 40, 50] 和 [90, 80] 进行快速排序。
继续进行分区和递归,最终得到已排序的数组 [10, 30, 40, 50, 70, 80, 90]。
2 示例代码:
function quickSort(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[arr.length - 1];
const left = [];
const right = [];
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSort(left), pivot, ...quickSort(right)];
}
六
- 冒泡排序
通过多次遍历数组,比较并交换相邻元素,简单但效率较低。
优点:
- 实现简单,易于理解。
- 适合小规模数据。
缺点:
- 效率低,时间复杂度为 O(n²)。
- 在大型数据集上性能不佳。
- 选择排序
每次选择未排序部分中的最小元素放到已排序部分,时间复杂度为 O(n²)。
优点:
- 简单易实现。
- 不需要额外的存储空间,空间复杂度为 O(1)。
缺点:
- 时间复杂度为 O(n²),效率较低。
- 不稳定排序,可能改变相同元素的相对位置。
- 插入排序
逐个将元素插入到已排序部分的正确位置,适合小规模数据,时间复杂度为 O(n²)。
优点:
- 在部分有序数据上表现良好,时间复杂度为 O(n)。
- 实现简单,适合小规模数据。
- 稳定排序。
缺点:
- 最坏情况下时间复杂度为 O(n²)。
- 对大规模数据不够高效。
- 归并排序
采用分治法,将数组分为两半递归排序,然后合并,时间复杂度为 O(n log n)。
优点:
- 时间复杂度为 O(n log n),效率较高。
- 稳定排序,适合处理大量数据。
缺点:
- 需要额外的存储空间,空间复杂度为 O(n)。
- 实现较复杂。
- 快速排序
选择基准元素,将数组分为小于和大于基准的两部分,递归排序,平均时间复杂度为 O(n log n)。
优点:
- 平均时间复杂度为 O(n log n),效率高。
- 原地排序,空间复杂度为 O(log n)。
缺点:
- 最坏情况下时间复杂度为 O(n²),但可以通过随机化基准元素来减少概率。
- 不稳定排序。