一,排序算法
排序算法是《数据结构与算法》中最基本的算法之一。
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。
排序算法 | 平均时间复杂度 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|
冒泡排序 | O(n²) | O(1) | In - place | 稳定 |
快速排序 | O(n log n) | O(log n) | In = place | 不稳定 |
插入排序 | O(n²) | O(1) | In = place | 稳定 |
归并排序 | O(n log n) | O(n) | Out = place | 稳定 |
1.冒泡排序(Bubble Sort)
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复进行的,直到没有再需要交换的元素为止,这意味着该数列已经排序完成。
function bubbleSort(arr) {
let n = arr.length;
for (let i = 0; i < n - 1; i++) { // 外层循环控制排序的轮数
for (let j = 0; j < n - 1 - i; j++) { // 内层循环进行元素比较和交换
if (arr[j] > arr[j + 1]) {
// 交换元素
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
//以上交换可以使用ES6(解构赋值)
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
}
}
}
return arr;
}
// 示例数组
let arr = [64, 34, 25, 12, 22, 11, 90];
console.log("Sorted array: ", bubbleSort(arr));
- 代码注解:
- 函数定义:bubbleSort(arr)接收一个数组arr为参数。
- 外层循环:循环的是排序的轮数
- 内层循环:
- 从第一个元素开始遍历数组,直到n - 1 - i (减 i 是为了每一轮的最后一个元素都是当前未排序部分最大的,不需要再比较)
- 如果当前元素大于下一个元素,则交换这两个元素
- 交换元素:使用第三方变量交换值,或者ES6(解构赋值)
- 最后返回排序后的数组
2.快速排序(Quick Sort)
快速排序(Quick Sort)是一种高效的排序算法,采用分治策略来对一个数组进行排序。其基本思想是:
- 从数组中挑选一个元素,称为“基准”(pivot)。
- 重新排序数组,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数组的中间位置。
- 递归地(recursive)把小于基准值元素的子数组和大于基准值元素的子数组排序。
function quickSort(arr) {
// 基准情况:如果数组长度小于或等于1,它已经是排序好的
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]); // 大于等于基准的放在右边
}
}
// 递归排序左右数组,并将基准点放到中间
//concat拼接数组元素
return quickSort(left).concat([pivot],quickSort(right))
//ES6的 ...(展开运算符/扩展运算符)
//return [...quickSort(left), pivot, ...quickSort(right)];
}
// 示例数组
const arr = [7, 2, 1, 6, 8, 5, 3, 4];
console.log("Sorted array: ", quickSort(arr));
- 代码注解:
- 基准选择:这里选择数组的最后一个元素作为基准点。
- 分区操作:遍历数组,将小于基准的元素放入
left
数组,将大于或等于基准的元素放入right
数组。 - 递归调用:对
left
和right
数组递归地进行快速排序。 - 合并结果:使用扩展运算符
...
将排序好的left
数组、基准点和right
数组合并返回。
3.插入排序(Insertion Sort)
插入排序(Insertion Sort)是一种简单直观的排序算法,它的工作方式是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上通常使用 in-place 排序(即只需用到 O(1) 的额外空间的排序)。
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
let current = arr[i]; // 当前要插入的元素
let j = i - 1;
// 将当前元素与已排序的元素进行比较,如果已排序的元素大于当前元素,则将已排序元素向后移动
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
// 插入当前元素到正确的位置
arr[j + 1] = current;
}
return arr;
}
// 示例数组
const arr = [5, 2, 9, 1, 5, 6, 3];
console.log("Sorted array: ", insertionSort(arr));
- 代码解释
- 外层循环:从数组的第二个元素开始,逐个遍历数组。
- 当前元素:将当前元素暂存到变量
current
中。 - 内层循环:使用一个while循环,将当前元素与它前面的元素进行比较。
- 如果前面的元素大于当前元素,则将前面的元素向后移动一个位置。
- 这个循环一直执行,直到找到第一个小于或等于当前元素的已排序元素,或者到达数组的开始。
- 插入元素:将当前元素插入到已排序序列的正确位置。
4.归并排序(Merge Sort)
归并排序(Merge Sort)是一种高效的排序算法,采用分治法(Divide and Conquer)的一个典型应用。它将数组分成两半,分别对这两半进行排序,然后将排序好的两半合并在一起。归并排序的性能非常稳定,时间复杂度总是 O(n log n)。
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
// 找到中间点,将数组分成两半
const middle = Math.floor(arr.length / 2);
const left = arr.slice(0, middle);
const right = arr.slice(middle);
// 递归地对左右两半进行排序
const leftSorted = mergeSort(left);
const rightSorted = mergeSort(right);
// 合并两个已排序的数组
return merge(leftSorted, rightSorted);
}
function merge(left, right) {
let result = [];
let leftIndex = 0;
let rightIndex = 0;
// 比较左右数组的元素,将较小的元素添加到结果数组中
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}
// 当一边的元素已经完全加入结果数组后,将另一边的剩余元素加入
return [
...result,
...left.slice(leftIndex),
...right.slice(rightIndex)
];
}
// 示例数组
const arr = [38, 27, 43, 3, 9, 82, 10];
console.log("Sorted array: ", mergeSort(arr));
- 代码解释
- 基准情况:如果数组长度小于或等于1,直接返回数组,因为它已经是排序好的。
- 分割:找到数组的中间点,将数组分成两半。
- 递归排序:递归地对左右两半进行归并排序。
- 合并:使用
merge
函数合并两个已排序的数组。这个函数会遍历两个数组,比较并选择较小的元素添加到结果数组中,直到所有元素都被合并。