排序算法就是研究如何对一个集合进行高效排序的算法,在计算机科学所使用的排序算法通常以以下标准分类:
- 计算的时间复杂度:使用大O表示法,也就是实际测试消耗的时间
- 内存使用量:比如外部排序,使用磁盘来存储排序的数据
- 稳定性:稳定排序算法会让原本有相等键值的记录维持相对次序,通俗的讲就是排序前后的相对位置没有变化。
- 排序的方法:插入、交换、选择、合并等等
冒泡排序
冒泡排序可能是我们大多数人最早接触到的排序,因为它很好理解。它的思路是每一次遍历,元素两两比较,大的换到后面的位置,这样每一次遍历就可以把当前轮次最大的元素排到后面,如此进行n-1次就完成了排序,下面的动图演示了这个过程:
知道了思路就很好实现了,只需要两个for循环即可:
// 冒泡排序
function bubbleSort(arr: number[]) {
const len = arr.length;
// 循环次数为len - 1 - i 因为是两个值进行比较,所以最后一个值不需要进行比较
for (let i = 0; i < len - 1; i++) {
// 减i是因为前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;
}
冒泡排序优化
其实上述代码还有一定的优化空间。试想,如果在第二层for循环中,一次位置交换都没有发生,这就意味着此时已经排好序了,所以直接结束外层循环就可以了:
function bubbleSort(arr: number[]) {
const len = arr.length;
for (let i = 0; i < len - 1; i++) {
// 初始化一个变量 表示未发生交换
let isSwap = false;
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]];
// 如果发生交换 isSwap置为true
isSwap = true;
}
}
// 一轮循环结束如果isSwap为false 结束循环
if (!isSwap) break;
}
return arr;
}
冒泡排序总结
冒泡排序虽然简单好理解,但是它的时间复杂度非常高为O(n^2),在数据量较大的情况下我们基本不考虑这种排序方式。
选择排序
选择排序思路也比较简单:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。重复第二步,直到所有元素均排序完毕,如下图所示:
实现:
// 选择排序
function selectionSort(arr: number[]) {
const len = arr.length;
// 循环次数为len - 1 因为当n-1个元素都在正确的位置时 最后一个元素就也在正确位置了
for (let i = 0; i < len - 1; i++) {
// 记录最小值的索引
let minIndex = i;
// 从i+1开始遍历
for (let j = i + 1; j < len; j++) {
// 如果前一个数比后一个数大 则交换位置
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 如果最小值的索引不是i 则交换位置
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
}
return arr;
}
选择排序总结
选择排序只是在交换次数上相对于冒泡排序进行了优化,但是其时间复杂度依然是O(n^2),所以依然不算是效率高的排序算法。
插入排序
插入排序思路:将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)就像我们打扑克时,我们将新拿到的牌插到合适的位置。
代码实现:
// 插入排序
function insertionSort(arr: number[]): number[] {
const len = arr.length;
// 第一个元素当成已排序的元素 从第二个元素开始遍历
for (let i = 1; i < len; i++) {
// 记录当前元素
const newNum = arr[i];
let j = i - 1;
// 如果新元素比已排序的元素小 则将已排序的元素后移
while (j >= 0 && arr[j] > newNum) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = newNum;
}
return arr;
}
插入排序总结
插入排序在数组部分有序的时候比冒泡排序和选择排序更快。但是综合来看时间复杂度还是太高O(n^2)。