一、🐶 冒泡排序
- 冒泡排序只会操作相邻的两个数据。
- 每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。
- 一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。代码如下
function bubbleSort(a, n) {
if (n <= 1) return;
for (let i = 0; i < n; i++) {
// 提前退出冒泡循环的标志位
let flag = false;
for (let j = 0; j < n - i - 1; j++) {
if (a[j] > a[j + 1]) { // 交换
let tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
flag = true; // 表示有数据交换
}
}
if (!flag) break; // 没有数据交换,提前退出
}
}
// 使用示例
let arr = [64, 34, 25, 12, 22, 11, 90];
let n = arr.length;
bubbleSort(arr, n);
console.log(arr); // 输出: [11, 12, 22, 25, 34, 64, 90]
冒泡排序是原地排序算法吗
冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为 O(1),是一个原地排序算法。
冒泡排序是稳定的排序算法吗
在冒泡排序中,只有交换才可以改变两个元素的前后顺序。为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,我们不做交换,相同大小的数据在排序前后不会改变顺序,所以冒泡排序是稳定的排序算法。
冒泡排序的时间复杂度是多少
- 最好情况下,要排序的数据已经是有序的了,我们只需要进行一次冒泡操作,就可以结束了,所以最好情况时间复杂度是 O(n)。
- 最坏的情况是,要排序的数据刚好是倒序排列的,我们需要进行 n 次冒泡操作,所以最坏情况时间复杂度为 O(n^2)。
- 平均时间复杂度为 O(n^2)。
二、🐶 插入排序
- 数组中的数据分为两个区间,已排序区间和未排序区间
- 初始已排序区间只有一个元素,就是数组的第一个元素
- 插入排序的核心思想就是取未排序区间的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间的数据一致有序
- 插入排序包含2种操作,一是元素的比较,二是元素的移动,代码如下:
function insertionSort(a) {
for (let i = 1; i < a.length; i++) {
// 当前需要插入的数据
let value = a[i];
// 已排序数组中从后往前找
let j = i - 1;
如果比当前插入的数据大,往后移动一位
while (j >= 0 && a[j] > value) {
a[j + 1] = a[j];
j--;
}
直到找到小于等于当前插入的值的位置,插入数据。
a[j+1] = value;
}
}
插入排序是原地排序算法吗
插入排序算法的运行并不需要额外的存储空间,所以空间复杂度是 O(1),也就是说,这是一个原地排序算法。
插入排序是稳定的排序算法吗
以上代码我们将后面出现的元素,插入到前面出现元素的后面,保持原有的前后顺序不变,所以插入排序是稳定的排序算法。
插入排序的时间复杂度是多少
- 如果要排序的数据已经是有序的,我们并不需要搬移任何数据。如果我们从尾到头在有序数据组里面查找插入位置,每次只需要比较一个数据就能确定插入的位置。所以这种情况下,最好是时间复杂度为 O(n)。注意,这里是从尾到头遍历已经有序的数据。
- 如果数组是倒序的,每次插入都相当于在数组的第一个位置插入新的数据,所以需要移动大量的数据,所以最坏情况时间复杂度为 O(n^2)。
- 插入一个数据的平均时间复杂度是 O(n)。对于插入排序来说,每次插入操作都相当于在数组中插入一个数据,循环执行 n 次插入操作,所以平均时间复杂度为 O(n^2)。
三、🐶 选择排序
- 选择排序也分为已排序和未排序区间,但是选择排序每次会从未排序区间选择一个最小的元素插入到已排序区间的末尾。
- 所以选择排序分为2步,一找到当前轮找到的最小值的索引,二如果最小值不是当前索引,则交换位置。
function selectionSort(arr) {
if (arr.length <= 1) return arr;
for (let i = 0; i < arr.length - 1; i++) {
let minIndex = i; // 记录当前轮找到的最小值的索引
for (let j = i + 1; j < arr.length; j++) {
// 如果找到更小的元素,则更新最小值的索引
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 如果最小值不是当前索引,则交换位置
if (minIndex !== i) {
let temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
return arr;
}
选择排序是原地排序算法吗
选择排序空间复杂度为 O(1),是一种原地排序算法。。
插入排序是稳定的排序算法吗
选择排序是一种不稳定的排序算法,选择排序每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置,这样破坏了稳定性。
插入排序的时间复杂度是多少
选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为 O(n2)
四、🐶 为什么插入排序要比冒泡排序更受欢迎呢?
从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个。代码如下:
冒泡排序中数据的交换操作:
if (a[j] > a[j+1]) { // 交换
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
flag = true;
}
插入排序中数据的移动操作:
if (a[j] > value) {
a[j+1] = a[j]; // 数据移动
} else {
break;
}
用冒泡排序,需要 K 次交换操作,每次需要 3 个赋值语句,所以交换操作总耗时就是 3*K 单位时间。而插入排序中数据移动操作只需要 K 个单位时间。
所以,虽然冒泡排序和插入排序在时间复杂度上是一样的,都是 O(n2),但是如果我们希望把性能优化做到极致,那肯定首选插入排序。
五、🐶 总结一下
- 要想分析、评价一个排序算法,需要从执行效率、内存消耗和稳定性三个方面来看。
- 冒泡排序、插入排序、选择排序三种时间复杂度是 O(n2)